Local Deploy
Deploy locally using Docker Desktop to run the full stack — database, Redis, proxy, and your app — in containers on your machine.
Prerequisites
- Docker Desktop installed and running
- Docker Compose v2 (included with Docker Desktop)
- Your project's
.envconfigured withDATABASE_URLandREDIS(if used)
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 once per project:
npm run maker deploy:initpnpm maker deploy:inityarn maker deploy:initbun maker deploy:initThis reads your .env, detects your database (MySQL/Postgres/SQLite) and Redis settings, and generates the entire deploy/ directory — including the shared infrastructure compose file and the two workflow configs.
TIP
The generated files are .env.example templates, not .env files. Edit the .example files with your settings, then run deploy:workflow — it will create the actual .env files automatically.
See Deploy Overview for the full file tree.
Step 2 — Start Server Infrastructure
npm run maker deploy:workflow -- --server-onlypnpm maker deploy:workflow --server-onlyyarn maker deploy:workflow --server-onlybun maker deploy:workflow --server-onlyThis single command:
Creates Docker networks if missing:
nginx-proxy— reverse proxy network (port 80/443)infra— internal network for database/Redis communication
Ensures bind-mount files — creates placeholder files for pgAdmin config and Redis config if they don't exist.
Ensures
.env— copies.env.exampleto.envindeploy/server/if missing.Starts containers — runs
docker compose -f deploy/server/docker-compose.yml up -d
What starts
Container Image Hostname Purpose
─────────────────────────────────────────────────────────────────────
nginx-proxy jwilder/nginx-proxy — Reverse proxy (ports 80, 443)
letsencrypt jrcs/letsencrypt — Auto SSL companion
mysql-global mysql:8.4 mysql-global MySQL database
postgres-global postgres:16-alpine postgres-global PostgreSQL database
redis-global redis:7-alpine redis-global Redis (if enabled)
phpmyadmin phpmyadmin:5-apache — MySQL admin UI (port 8082)
pgadmin dpage/pgadmin4 — Postgres admin UI (port 8083)All database/Redis containers are reachable from the app container by hostname (mysql-global:3306, redis-global:6379, etc.) via the infra network.
Dev mode (Docker Desktop)
If you don't have MySQL, PostgreSQL, or Redis installed locally, use this workflow to run all infrastructure in Docker Desktop while developing on your host machine.
Generate dev server config
This generates server infra files with Redis port exposed to the host:
npm run maker deploy:init -- --server-only --dev --forcepnpm maker deploy:init --server-only --dev --forceyarn maker deploy:init --server-only --dev --forcebun maker deploy:init --server-only --dev --forceThe --dev flag exposes these ports to your host:
| Container | Host Port | Default | Purpose |
|---|---|---|---|
mysql-global | 3306 | 0.0.0.0:3306 | Connect via any MySQL client |
postgres-global | 5432 | 0.0.0.0:5432 | Connect via any Postgres client |
redis-global | 6379 | 0.0.0.0:6379 | Connect via RedisInsight, redis-cli, etc. |
Start server infra
npm run maker deploy:workflow -- --server-onlypnpm maker deploy:workflow --server-onlyyarn maker deploy:workflow --server-onlybun maker deploy:workflow --server-onlyConfigure your local .env
Point your local .env to localhost so your dev server connects to Docker containers:
DATABASE_URL=mysql://root:password@localhost:3306/nexgen
REDIS=true
REDIS_URL=redis://localhost:6379Or for PostgreSQL:
DATABASE_URL=postgres://postgres:password@localhost:5432/nexgenDevelop normally
Now run maker dev as usual — your local API server connects to the Docker-hosted database and Redis:
Your Machine (host) Docker Desktop
│ │
│ maker dev │
│ ├─ serve (port 3000) ──┐ │
│ ├─ queue:work │ │
│ └─ ui:dev │ │
│ │ │
│ localhost:3306 ────────┼────────>│ mysql-global
│ localhost:6379 ────────┼────────>│ redis-global
│ localhost:5432 ────────┼────────>│ postgres-global
│ │ │
│ RedisInsight ──────────┼────────>│ redis-global
│ TablePlus / DBeaver ───┼────────>│ mysql-global / postgres-globalThis way you don't need to install or manage databases on your host — Docker handles everything, and you still get hot-reload and local debugging from your IDE.
Step 3 — Build and Start the App
npm run maker deploy:workflow -- --app-onlypnpm maker deploy:workflow --app-onlyyarn maker deploy:workflow --app-onlybun maker deploy:workflow --app-onlyThis:
Syncs DATABASE_URL — reads the database name from
deploy/server/.envand writes it intodeploy/.envso the app connects to the correct database.Ensures MySQL database exists — if using MySQL, creates the target database inside
mysql-globalif it doesn't exist yet.Builds the Docker image — runs
docker compose -f deploy/docker-compose.yml buildusing the multi-stage Dockerfile (install deps → schema gen → build → production image).Starts the container — runs
docker compose -f deploy/docker-compose.yml up -d --force-recreate
What happens inside
Container starts →
supervisord reads deploy/supervisor/supervisord.conf →
├─ auto-migrate.sh (one-shot)
│ └─ Reads AUTO_MIGRATE=true from .env
│ └─ Runs: node maker-cli db:migrate --seed
├─ maker serve --prod
│ └─ Starts HTTP server on port 3000
├─ maker queue:work (only if Redis enabled)
│ └─ Processes default, mail and maintenance queues
└─ maker schedule:work
└─ Runs cron schedulerStep 4 — Import Database (Optional)
If you have an existing SQL dump:
npm run maker deploy:db:import -- --file=deploy/nexgen.sql --database=nexgenpnpm maker deploy:db:import --file=deploy/nexgen.sql --database=nexgenyarn maker deploy:db:import --file=deploy/nexgen.sql --database=nexgenbun maker deploy:db:import --file=deploy/nexgen.sql --database=nexgenThe command auto-detects the database dialect (MySQL or PostgreSQL) from deploy/server/.env and streams the SQL into the right container:
# MySQL
docker exec -i mysql-global mysql -u root -p<password> -e "CREATE DATABASE IF NOT EXISTS nexgen"
docker exec -i mysql-global mysql -u root -p<password> nexgen < deploy/nexgen.sql
# PostgreSQL (defaults to a clean restore — drops & recreates the database)
docker exec -i postgres-global psql -U postgres -d postgres -v ON_ERROR_STOP=1 ...
docker exec -i postgres-global psql -U postgres -d nexgen < deploy/nexgen.sqlFor PostgreSQL, pass --no-drop to keep the existing database instead of dropping it for a clean restore.
One-Shot Workflow
For convenience, a single command runs steps 2 + 3 sequentially:
npm run maker deploy:workflowpnpm maker deploy:workflowyarn maker deploy:workflowbun maker deploy:workflowYou can customize the steps by editing deploy/workflow.local.json:
{
"steps": [
{ "name": "Generate deploy files", "run": "deploy:init --force", "enabled": false },
{ "name": "Start shared infra", "run": "deploy:workflow --server-only", "enabled": true },
{ "name": "Import database dump (optional)", "run": "deploy:db:import --file=deploy/nexgen.sql --database=nexgen", "enabled": false },
{ "name": "Start app stack", "run": "deploy:workflow --app-only", "enabled": true }
]
}Set "enabled": false to skip steps. Preview what a workflow would run without executing it:
npm run maker deploy:workflow -- --config=deploy/workflow.local.json --dry-runpnpm maker deploy:workflow --config=deploy/workflow.local.json --dry-runyarn maker deploy:workflow --config=deploy/workflow.local.json --dry-runbun maker deploy:workflow --config=deploy/workflow.local.json --dry-runThe workflow config is created automatically by deploy:init.
Environment Variables
App (deploy/.env)
| Variable | Auto-detected | Purpose |
|---|---|---|
APP_ENV | — | Set to production in deploy |
APP_NAME | — | Container name prefix |
APP_HOST | — | Bind address (default localhost) |
APP_PORT | — | Port exposed (default 3000) |
APP_URL | — | Public URL of your app (links, redirects, CORS) |
DATABASE_URL | Yes | Database connection string (synced with server) |
AUTO_MIGRATE | — | true to auto-run db:migrate --seed on container start |
VIRTUAL_HOST | — | Domain routed by nginx-proxy (remote only) |
VIRTUAL_PORT | — | Port nginx-proxy forwards to (default 3000) |
LETSENCRYPT_HOST | — | Domain for auto SSL (remote only) |
LETSENCRYPT_EMAIL | — | Email for SSL certificate notifications |
JWT_ACCESS_SECRET | — | JWT access token secret (must set before production) |
JWT_REFRESH_SECRET | — | JWT refresh token secret (must set before production) |
COOKIE_SECRET | — | Cookie signing secret (must set before production) |
STORAGE_ACCESS_KEY_ID | — | Object storage key (S3/MinIO) |
STORAGE_SECRET_ACCESS_KEY | — | Object storage secret (S3/MinIO) |
MAIL_USERNAME | — | SMTP username |
MAIL_PASSWORD | — | SMTP password |
REDIS | Yes | Enable Redis (true/false) |
REDIS_URL | Yes | Set to redis://redis-global:6379 when Redis enabled |
REDIS_PREFIX | — | Key prefix for Redis (multi-tenant isolation) |
UI | Yes | Enable UI build (true/false, default true) |
OPEN_API | Yes | Enable OpenAPI docs at /api-docs (true/false) |
SOCKET | Yes | Enable Socket.IO realtime (true/false) |
Server (deploy/server/.env)
| Variable | Purpose |
|---|---|
LETSENCRYPT_EMAIL | Email for SSL certificate notifications |
PROXY_HTTP_PORT | nginx-proxy HTTP port (default 80) |
PROXY_HTTPS_PORT | nginx-proxy HTTPS port (default 443) |
MYSQL_ROOT_PASSWORD | MySQL root password |
MYSQL_DATABASE | Default MySQL database name |
MYSQL_PORT | MySQL host port (default 4000) |
POSTGRES_USER | Postgres superuser |
POSTGRES_PASSWORD | Postgres password |
POSTGRES_DB | Default Postgres database name |
POSTGRES_PORT | Postgres host port (default 4001) |
REDIS | Enable Redis service in server infra |
PHPMYADMIN_DOMAIN | phpMyAdmin virtual host domain |
PHPMYADMIN_LOCAL_PORT | phpMyAdmin local port (default 127.0.0.1:8082) |
PGADMIN_DOMAIN | pgAdmin virtual host domain |
PGADMIN_DEFAULT_EMAIL | pgAdmin login email |
PGADMIN_DEFAULT_PASSWORD | pgAdmin login password |
PGADMIN_LOCAL_PORT | pgAdmin local port (default 127.0.0.1:8083) |
Troubleshooting
View app logs
docker compose -f deploy/docker-compose.yml logs -f appView server infra logs
docker compose -f deploy/server/docker-compose.yml logs -f mysql-globalRebuild without cache
docker compose -f deploy/docker-compose.yml build --no-cache app
docker compose -f deploy/docker-compose.yml up -d --force-recreate appEnter the app container
docker exec -it <app-container-name> sh