Remote Deploy
Deploy to a remote Linux server (VPS, dedicated server, or cloud VM) via SSH. The deploy system uses rsync (or scp fallback) to upload your project and Docker Compose to run it on the remote host.
How Remote Deploy Works
Your Machine Remote Server (VPS)
│ │
│ Step 1: Configure workflow │
│ deploy/workflow.remote.json │
│ │
│ Step 2: Run remote workflow │
│ maker deploy:workflow:remote │
│ │
│ ── ssh mkdir -p /home/deploy/app ──>│
│ │
│ ── rsync ./ user@host:/target/ ────>│
│ (excludes node_modules, .git, │
│ dist, .env*) │
│ │
│ ── ssh docker network create ─────> │
│ ── ssh docker compose up -d ───────>│
│ (server infra) │
│ ── ssh docker compose up -d ───────>│
│ --build (app) │
│ │
│ ┌───────┴────────┐
│ │ App running at │
│ │ your-domain.com │
│ └────────────────┘Prerequisites
- Remote server running Linux with Docker and Docker Compose installed
- SSH access with key-based authentication
- A domain pointing to your server's IP (for nginx-proxy + letsencrypt SSL)
TIP
You don't need to create deploy/.env or deploy/server/.env manually. Just edit the .env.example files generated by deploy:init, and the workflow will copy them to .env automatically on first run.
Step 1 — Generate Deploy Scaffolding
Run this locally (one-time):
npm run maker deploy:initpnpm maker deploy:inityarn maker deploy:initbun maker deploy:initThis generates the full deploy/ directory — including deploy/workflow.remote.json. See Deploy Overview for details.
Step 2 — Configure Remote Workflow
The deploy:init command already created deploy/workflow.remote.json. Edit it with your server's details:
{
"remote": {
"host": "203.0.113.10",
"user": "deploy",
"port": 22,
"keyPath": "~/.ssh/id_rsa",
"targetPath": "/home/deploy/nexgen"
},
"upload": {
"source": ".",
"targetSubPath": "."
},
"databaseImport": {
"enabled": false,
"file": "deploy/nexgen.sql",
"database": "nexgen",
"container": "mysql-global",
"user": "root"
},
"preDeployCommands": [
"docker rm -f old-app 2>/dev/null || true"
]
}| Field | Purpose |
|---|---|
remote.host | Server IP or hostname |
remote.user | SSH user |
remote.port | SSH port (default 22) |
remote.keyPath | Path to your SSH private key |
remote.targetPath | Directory on the remote server where the project will be uploaded |
upload.source | Local directory to upload (default .) |
upload.targetSubPath | Subdirectory under targetPath for the upload |
databaseImport.enabled | Whether to import a SQL dump after deploy |
databaseImport.file | Path to the SQL dump file |
databaseImport.database | Target database name |
databaseImport.container | Docker container name (mysql-global or postgres-global) |
databaseImport.user | Database user (root / postgres) |
preDeployCommands | Commands run on the remote host before starting the app |
rsync options
By default the CLI uses scp to upload files. To use rsync instead (faster incremental sync), add these fields to the config:
| Field | Purpose |
|---|---|
rsyncPath | Path to the rsync binary on the remote host (e.g. "rsync") |
rsyncSshPath | Path to the SSH binary used by rsync (e.g. "ssh") |
rsyncSshOptions | Extra SSH options passed to rsync (e.g. ["-o", "StrictHostKeyChecking=no"]) |
Example with rsync enabled:
{
"remote": { ... },
"rsyncPath": "rsync",
"rsyncSshPath": "ssh",
"rsyncSshOptions": ["-o", "StrictHostKeyChecking=no"]
}When rsyncPath is not set, the CLI falls back to scp. Both approaches exclude node_modules, .git, dist, and .env* from the upload.
Step 3 — Deploy
Full pipeline (upload + server infra + app)
npm run maker deploy:workflow:remotepnpm maker deploy:workflow:remoteyarn maker deploy:workflow:remotebun maker deploy:workflow:remoteThis single command:
- Creates target directory on the remote server:
ssh user@host mkdir -p /home/deploy/nexgen - Runs pre-deploy commands on the remote host (if any are defined in
preDeployCommands) - Uploads the project via rsync (excludes
node_modules,.git,dist,.env*, logs):bashrsync -avz --delete --exclude=node_modules --exclude=.git \ --exclude=dist --exclude=/.env* --exclude=*.log \ -e "ssh -p 22 -i ~/.ssh/id_rsa" \ ./ deploy@203.0.113.10:/home/deploy/nexgen/ - Creates Docker networks on remote (if missing):bash
ssh user@host docker network create nginx-proxy ssh user@host docker network create infra - Starts server infra on remote:bash
ssh user@host docker compose -f /path/to/deploy/server/docker-compose.yml up -d - Syncs pgAdmin servers.json — loads the server configuration into the pgAdmin container
- Builds and starts app on remote:bash
ssh user@host docker compose -f /path/to/deploy/docker-compose.yml up -d --build --force-recreate - Imports database (if
databaseImport.enabled: true)
Step by step
Alternatively, run parts of the pipeline:
npm run maker deploy:workflow:remote -- --server-only
npm run maker deploy:workflow:remote -- --app-only
npm run maker deploy:db:import:remote -- --file=deploy/nexgen.sql --database=nexgenpnpm maker deploy:workflow:remote --server-only
pnpm maker deploy:workflow:remote --app-only
pnpm maker deploy:db:import:remote --file=deploy/nexgen.sql --database=nexgenyarn maker deploy:workflow:remote --server-only
yarn maker deploy:workflow:remote --app-only
yarn maker deploy:db:import:remote --file=deploy/nexgen.sql --database=nexgenbun maker deploy:workflow:remote --server-only
bun maker deploy:workflow:remote --app-only
bun maker deploy:db:import:remote --file=deploy/nexgen.sql --database=nexgendeploy:db:import:remote auto-detects MySQL or PostgreSQL from the databaseImport.container value in the workflow config.
SSL with letsencrypt
The server infra includes jrcs/letsencrypt-nginx-proxy-companion for automatic SSL certificates. To use it:
Set your domain in
deploy/.env:VIRTUAL_HOST=app.example.com LETSENCRYPT_HOST=app.example.com LETSENCRYPT_EMAIL=admin@example.comConfigure the nginx vhost override in
deploy/server/nginx-vhost/app.example.com:# This file is mounted into nginx-proxy # Customize proxy settings if neededEnsure your domain's DNS points to the server's IP
Run
deploy:workflow:remote— nginx-proxy routes your domain to the app container and letsencrypt automatically obtains and renews certificates.
Promote Workflow
The promote workflow runs the full local pipeline (tests the build on your machine) then deploys to remote:
npm run maker deploy:workflow:promotepnpm maker deploy:workflow:promoteyarn maker deploy:workflow:promotebun maker deploy:workflow:promoteThis is useful for pre-production validation — if the local build succeeds, it proceeds to deploy to the remote server.
Full Architecture on Remote
Remote Server
├─ Docker networks: nginx-proxy, infra
│
├─ Server Infra Containers
│ ├─ nginx-proxy (ports 80, 443) → routes to app
│ ├─ letsencrypt → auto SSL on port 443
│ ├─ mysql-global (port 3306, infra only)
│ ├─ postgres-global (port 5432, infra only)
│ ├─ redis-global (port 6379, infra only)
│ ├─ phpmyadmin → mysql admin at http://ip:8082
│ └─ pgadmin → postgres admin at http://ip:8083
│
└─ App Container (infra + nginx-proxy networks)
├─ supervisord
│ ├─ auto-migrate.sh (one-shot)
│ ├─ maker serve --prod (port 3000)
│ ├─ maker queue:work (if Redis)
│ └─ maker schedule:work
└─ nginx-proxy routes app.example.com → app:3000Security Notes
- Never commit
deploy/.envordeploy/server/.envto version control (they contain secrets). - The server infra
.env.local.exampleis for local Docker Desktop overrides — do not use it in production. - Change default MySQL/Postgres passwords in
deploy/server/.envbefore production. - Generate strong secrets for
JWT_ACCESS_SECRET,JWT_REFRESH_SECRET, andCOOKIE_SECRET. - The app container does not expose ports directly — all traffic goes through nginx-proxy.
- Set
AUTO_MIGRATE=falsein production if you prefer to run migrations manually.
