Deploying a monorepo to multiple servers
If your repository contains multiple components, such as a backend API, a frontend application, an admin panel, or static assets, you can use DeployHQ to deploy each component to a different server from a single deployment. This is useful for monorepos, modular monoliths, or any project where different parts of the codebase need to go to different destinations.
How it works
DeployHQ's server groups allow you to deploy to multiple servers in a single deployment. Combined with per-server excluded files, you can control exactly which files are sent to each server. When a deployment runs:
- The repository is checked out and any build commands run once
- DeployHQ generates a file manifest for the deployment
- Each server in the group receives only the files that are not excluded for that server
This means you can have one repository, one build pipeline, and one deployment trigger, but each server only receives the files it needs.
Example: Laravel API with a React frontend
Consider a monorepo with the following structure:
/
├── app/ # Laravel backend
├── bootstrap/
├── config/
├── database/
├── public/
│ └── build/ # Vite-compiled assets
├── resources/
├── routes/
├── frontend/ # React SPA source
│ ├── src/
│ ├── dist/ # Built frontend (generated by build pipeline)
│ └── package.json
├── composer.json
├── package.json
└── vite.config.js
You want to deploy the Laravel API to a web server via SSH, the compiled React frontend to a CDN or static hosting server, and the Vite-built assets to Cloudflare R2.
Step 1: Create your servers
Add three servers to your project under Servers & Groups:
- API Server (SSH/SFTP) - Your web server running the Laravel application
- Frontend Server (SSH/SFTP or S3-compatible) - Where the compiled React SPA is served from
- Asset Storage (S3-Compatible Storage) - Cloudflare R2 bucket for CSS/JS assets
For S3-compatible storage setup, see Configuring S3-Compatible Storage.
Step 2: Create a server group
Go to Servers & Groups and click New Server Group. Give it a name like "Production" and choose your transfer order:
- Parallel deploys to all servers at the same time, which is faster
- Sequential completes one server before starting the next
Then edit each of the three servers and assign them to this group using the Group dropdown.
Step 3: Configure your build pipeline
Set up your build commands to compile all assets in a single pipeline. For example:
Command 1 - Install and build the React frontend:
cd frontend && npm ci && npm run build
Command 2 - Install Laravel dependencies and build Vite assets:
composer install --no-dev --optimize-autoloader
npm ci && npm run build
The build pipeline runs once per deployment, and the generated files are available to all servers in the group.
Step 4: Configure excluded files per server
This is the key step. Go to Settings > Excluded Files and add rules for each server. When adding each rule, uncheck Exclude this file on all current and future servers? and select only the server it applies to.
API Server - Exclude frontend source and build tooling:
| Excluded path | Applies to |
|---|---|
frontend |
API Server only |
frontend/** |
API Server only |
node_modules |
API Server only |
node_modules/** |
API Server only |
package.json |
API Server only |
package-lock.json |
API Server only |
vite.config.js |
API Server only |
Frontend Server - Exclude everything except the built React app:
| Excluded path | Applies to |
|---|---|
** |
Frontend Server only |
!frontend/dist/** |
Frontend Server only |
Asset Storage - Exclude everything except Vite-built assets:
| Excluded path | Applies to |
|---|---|
** |
Asset Storage only |
!public/build/** |
Asset Storage only |
The ** rule excludes all files, and the ! prefix whitelists specific directories back in. This approach requires fewer rules and is easier to maintain. See the Excluded Files documentation for more details on pattern matching and whitelisting.
Step 5: Deploy
When a deployment is triggered (manually or via automatic deployments):
- The build pipeline runs both build commands
- The API Server receives the Laravel application files (without frontend source)
- The Frontend Server receives only the compiled React SPA from
frontend/dist/ - The Asset Storage receives only the Vite-built CSS/JS from
public/build/
All three transfers happen within the same deployment, keeping everything in sync.
SSH commands per server
SSH commands such as cache clearing, queue restarts, or migration commands only run on servers that support shell execution (SSH/SFTP). They are automatically skipped for cloud storage servers like S3 or R2.
You can configure different commands for different servers. For example:
- API Server: Run
php artisan migrate --forceafter deployment - Frontend Server: Run
nginx -s reloadto clear the cache
When adding an SSH command, uncheck the option to run on all servers and select only the server where it should execute.
Other common patterns
Microservice-style monorepo
A monorepo containing multiple independent services:
| Server | Receives | Excludes |
|---|---|---|
| User Service (SSH) | services/users/, shared/ |
Other services |
| Order Service (SSH) | services/orders/, shared/ |
Other services |
| Gateway (SSH) | gateway/, shared/ |
All services |
WordPress with a static frontend
| Server | Receives | Excludes |
|---|---|---|
| Web Server (SSH) | WordPress theme, plugins, PHP files | src/, build tooling |
| CDN (S3/R2) | Compiled CSS/JS, optimised images | PHP files, source files |
Documentation site alongside an application
| Server | Receives | Excludes |
|---|---|---|
| App Server (SSH) | Application code | docs/ |
| Docs Server (SSH or S3) | Built documentation from docs/dist/ |
Application code |
Mixing server protocols
A server group can contain servers using different protocols. You can combine SSH/SFTP servers, FTP servers, S3 buckets, Cloudflare R2, Shopify, and other supported protocols in the same group. DeployHQ handles the file transfer using the appropriate protocol for each server.
The only restriction is that Shell servers cannot be mixed with other server types in the same group.
Tips
- Start with whitelisting: For servers that only need a small subset of files, use
**to exclude everything and then whitelist specific paths with!. This is easier to maintain than listing every directory to exclude. - Use
.deployignorefor shared rules: If some exclusions apply to all servers (likenode_modulesor.git), add them to a.deployignorefile in your repository. Use the DeployHQ interface for per-server rules only. - Test with a preview deployment: Before deploying to production, use the deployment preview to verify that each server is receiving the correct files.
- Consider build pipeline per server: If different servers need different build configurations (for example, different Node.js versions), you can set up server-specific build pipelines.