DeployHub supports deploying specific folders from monorepos or large repositories, allowing you to deploy only the code you need without cloning the entire repository.
Overview
Folder-based deployments use Git sparse checkout to clone only specific directories, making deployments faster and more efficient for monorepo structures.
Configuration
Enable folder deployment by setting isFolder: true and specifying the folder name:
{
"isFolder" : true ,
"folderName" : "packages/web-app"
}
Validation Rules
Folder deployment requirements:
isFolder must be a boolean value
When isFolder is true, folderName is required
folderName must be a non-empty string
From the validation schema:
body ( 'isFolder' )
. notEmpty ()
. withMessage ( "is folder is required" )
. isBoolean ()
. withMessage ( 'only boolean' )
body ( 'folderName' )
. if ( body ( 'isFolder' ). equals ( true ))
. notEmpty ()
. withMessage ( "folderName is required" )
. isString ()
. withMessage ( 'folder name must be a string' )
Database Schema
Folder settings are stored in the project model:
settings : {
repoBranchName : {
type : String ,
default : "main"
},
folder : {
enabled : {
type : Boolean ,
default : false
},
name : {
type : String ,
validate : {
validator : function ( value ) {
if ( this . folder ?. enabled && ! value ) {
return false ;
}
return true ;
},
message : "Folder name is required when folder is enabled"
}
}
}
}
How It Works
Sparse Checkout Initialization
DeployHub clones the repository with blob filtering and enables sparse checkout: git clone -b ${ branchname } --filter=blob:none --sparse ${ repoUrl } ${ buildPath }
This downloads only the repository structure, not file contents.
Configure Sparse Checkout
Sets which folder to checkout: git -C ${ buildPath } sparse-checkout set ${ folderName }
Only files in the specified folder are downloaded.
Move Folder Contents
The folder contents are moved to the build root: const folderPath = path . join ( buildFilePath , folderName );
if ( fs . existsSync ( folderPath )) {
const entries = await fs . promises . readdir ( folderPath , { withFileTypes: true });
for ( const entry of entries ) {
const src = path . join ( folderPath , entry . name );
const dest = path . join ( buildFilePath , entry . name );
await fs . promises . cp ( src , dest , { recursive: true , force: true });
}
await fs . promises . rm ( folderPath , { recursive: true , force: true });
}
Build and Deploy
The folder contents are built and deployed as if they were the repository root.
Complete Build Worker Implementation
From buildworker.js:
const branchname = projectData . settings . repoBranchName ;
const isFolder = projectData . settings . folder . enabled ;
const folderName = projectData . settings . folder ?. name ;
const repoUrl = projectData . repoLink ;
let repoUrlWithAuth ;
if ( usergithubAccessToken ) {
repoUrlWithAuth = `https:// ${ usergithubAccessToken } @github.com/ ${ owner } / ${ repo } .git` ;
} else {
repoUrlWithAuth = `https://github.com/ ${ owner } / ${ repo } .git` ;
}
if ( isFolder === true ) {
execSync (
`git clone -b ${ branchname } --filter=blob:none --sparse ${ repoUrlWithAuth } ${ buildFilePath } ` ,
{ stdio: "inherit" },
);
execSync ( `git -C ${ buildFilePath } sparse-checkout set ${ folderName } ` , {
stdio: "inherit" ,
});
const folderPath = path . join ( buildFilePath , folderName );
if ( fs . existsSync ( folderPath )) {
const entries = await fs . promises . readdir ( folderPath , { withFileTypes: true });
for ( const entry of entries ) {
const src = path . join ( folderPath , entry . name );
const dest = path . join ( buildFilePath , entry . name );
await fs . promises . cp ( src , dest , { recursive: true , force: true });
}
await fs . promises . rm ( folderPath , { recursive: true , force: true });
}
} else {
execSync ( `git clone -b ${ branchname } ${ repoUrlWithAuth } ${ buildFilePath } ` , {
stdio: "inherit" ,
});
}
Deployment Examples
Static Site in Monorepo
{
"projectId" : "507f1f77bcf86cd799439011" ,
"name" : "frontend-app" ,
"codeLink" : "https://github.com/company/monorepo.git" ,
"projectType" : "static" ,
"buildCommand" : "npm run build" ,
"publishDir" : "dist" ,
"branchname" : "main" ,
"isFolder" : true ,
"folderName" : "apps/frontend"
}
Node.js API in Monorepo
{
"projectId" : "507f1f77bcf86cd799439011" ,
"name" : "api-service" ,
"codeLink" : "https://github.com/company/monorepo.git" ,
"projectType" : "node" ,
"startCommand" : "node server.js" ,
"port" : 3000 ,
"branchname" : "main" ,
"isFolder" : true ,
"folderName" : "services/api"
}
Nested Folder Structure
{
"isFolder" : true ,
"folderName" : "packages/core/ui-components"
}
Monorepo Structures Supported
Nx Workspace
monorepo/
├── apps/
│ ├── web/ ← Deploy this
│ └── api/ ← Or this
├── libs/
│ └── shared/
└── package.json
{
"isFolder" : true ,
"folderName" : "apps/web"
}
Turborepo
monorepo/
├── apps/
│ ├── docs/ ← Deploy this
│ └── web/
├── packages/
│ └── ui/
└── turbo.json
{
"isFolder" : true ,
"folderName" : "apps/docs"
}
Lerna
monorepo/
├── packages/
│ ├── client/ ← Deploy this
│ ├── server/
│ └── shared/
└── lerna.json
{
"isFolder" : true ,
"folderName" : "packages/client"
}
Yarn Workspaces
monorepo/
├── workspace-a/ ← Deploy this
├── workspace-b/
└── package.json
{
"isFolder" : true ,
"folderName" : "workspace-a"
}
Updating Folder Settings
Update folder configuration via the settings API:
curl -X PATCH https://api.deployhub.cloud/api/projects/:id/settings/general \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"folder": {
"enabled": true,
"name": "packages/new-app"
}
}'
Changing folder settings requires a redeployment to take effect.
Benefits
Faster Cloning
Sparse checkout only downloads the files you need:
Full clone : Downloads entire repository history and all files
Sparse checkout : Downloads only specified folder with --filter=blob:none
Reduced Build Time
Smaller directory = faster builds:
Full repo: 2GB, 5 minutes to clone
Sparse checkout: 50MB, 30 seconds to clone
Lower Resource Usage
Less disk space and memory required for builds.
Redeployment with Folders
The redeploy worker also supports folder deployments:
const isFolder = projectData . settings . folder . enabled ;
const folderName = projectData . settings . folder ?. name ;
if ( isFolder === true ) {
execSync (
`git clone -b ${ branchname } --filter=blob:none --sparse ${ repoUrlWithAuth } ${ buildFilePath } ` ,
{ stdio: "inherit" },
);
execSync ( `git -C ${ buildFilePath } sparse-checkout set ${ folderName } ` , {
stdio: "inherit" ,
});
// Move folder contents to root...
}
Troubleshooting
Folder Not Found
Ensure the folder path is correct and exists in your repository.
Check your repository structure:
git ls-tree -r --name-only HEAD
Build Fails After Folder Deploy
Verify package.json exists in the folder
Check that all dependencies are properly referenced
Ensure relative imports work from the folder root
Sparse Checkout Not Working
Verify Git version supports sparse checkout (Git 2.25+)
Check that --filter=blob:none is supported
Ensure folder name doesn’t have trailing slashes
Dependencies in Parent Directory
If your folder needs files from parent directories:
// Not supported:
folderName : "apps/web"
// Needs: ../../shared/config
// Solution: Include parent in folder structure or
// use path aliases in your build config
Best Practices
Self-Contained Folders Ensure each deployable folder has its own package.json and dependencies
Relative Imports Use path aliases for shared code instead of ../../ imports
Build Scripts Keep build commands in the folder’s package.json
Environment Variables Use folder-specific env vars to avoid conflicts
Next Steps