Skip to content

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 .env configured with DATABASE_URL and REDIS (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:

bash
npm run maker deploy:init
bash
pnpm maker deploy:init
bash
yarn maker deploy:init
bash
bun maker deploy:init

This 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

bash
npm run maker deploy:workflow -- --server-only
bash
pnpm maker deploy:workflow --server-only
bash
yarn maker deploy:workflow --server-only
bash
bun maker deploy:workflow --server-only

This single command:

  1. Creates Docker networks if missing:

    • nginx-proxy — reverse proxy network (port 80/443)
    • infra — internal network for database/Redis communication
  2. Ensures bind-mount files — creates placeholder files for pgAdmin config and Redis config if they don't exist.

  3. Ensures .env — copies .env.example to .env in deploy/server/ if missing.

  4. 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:

bash
npm run maker deploy:init -- --server-only --dev --force
bash
pnpm maker deploy:init --server-only --dev --force
bash
yarn maker deploy:init --server-only --dev --force
bash
bun maker deploy:init --server-only --dev --force

The --dev flag exposes these ports to your host:

ContainerHost PortDefaultPurpose
mysql-global33060.0.0.0:3306Connect via any MySQL client
postgres-global54320.0.0.0:5432Connect via any Postgres client
redis-global63790.0.0.0:6379Connect via RedisInsight, redis-cli, etc.

Start server infra

bash
npm run maker deploy:workflow -- --server-only
bash
pnpm maker deploy:workflow --server-only
bash
yarn maker deploy:workflow --server-only
bash
bun maker deploy:workflow --server-only

Configure your local .env

Point your local .env to localhost so your dev server connects to Docker containers:

bash
DATABASE_URL=mysql://root:password@localhost:3306/nexgen
REDIS=true
REDIS_URL=redis://localhost:6379

Or for PostgreSQL:

bash
DATABASE_URL=postgres://postgres:password@localhost:5432/nexgen

Develop 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-global

This 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

bash
npm run maker deploy:workflow -- --app-only
bash
pnpm maker deploy:workflow --app-only
bash
yarn maker deploy:workflow --app-only
bash
bun maker deploy:workflow --app-only

This:

  1. Syncs DATABASE_URL — reads the database name from deploy/server/.env and writes it into deploy/.env so the app connects to the correct database.

  2. Ensures MySQL database exists — if using MySQL, creates the target database inside mysql-global if it doesn't exist yet.

  3. Builds the Docker image — runs docker compose -f deploy/docker-compose.yml build using the multi-stage Dockerfile (install deps → schema gen → build → production image).

  4. 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 scheduler

Step 4 — Import Database (Optional)

If you have an existing SQL dump:

bash
npm run maker deploy:db:import -- --file=deploy/nexgen.sql --database=nexgen
bash
pnpm maker deploy:db:import --file=deploy/nexgen.sql --database=nexgen
bash
yarn maker deploy:db:import --file=deploy/nexgen.sql --database=nexgen
bash
bun maker deploy:db:import --file=deploy/nexgen.sql --database=nexgen

The command auto-detects the database dialect (MySQL or PostgreSQL) from deploy/server/.env and streams the SQL into the right container:

bash
# 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.sql

For 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:

bash
npm run maker deploy:workflow
bash
pnpm maker deploy:workflow
bash
yarn maker deploy:workflow
bash
bun maker deploy:workflow

You can customize the steps by editing deploy/workflow.local.json:

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:

bash
npm run maker deploy:workflow -- --config=deploy/workflow.local.json --dry-run
bash
pnpm maker deploy:workflow --config=deploy/workflow.local.json --dry-run
bash
yarn maker deploy:workflow --config=deploy/workflow.local.json --dry-run
bash
bun maker deploy:workflow --config=deploy/workflow.local.json --dry-run

The workflow config is created automatically by deploy:init.

Environment Variables

App (deploy/.env)

VariableAuto-detectedPurpose
APP_ENVSet to production in deploy
APP_NAMEContainer name prefix
APP_HOSTBind address (default localhost)
APP_PORTPort exposed (default 3000)
APP_URLPublic URL of your app (links, redirects, CORS)
DATABASE_URLYesDatabase connection string (synced with server)
AUTO_MIGRATEtrue to auto-run db:migrate --seed on container start
VIRTUAL_HOSTDomain routed by nginx-proxy (remote only)
VIRTUAL_PORTPort nginx-proxy forwards to (default 3000)
LETSENCRYPT_HOSTDomain for auto SSL (remote only)
LETSENCRYPT_EMAILEmail for SSL certificate notifications
JWT_ACCESS_SECRETJWT access token secret (must set before production)
JWT_REFRESH_SECRETJWT refresh token secret (must set before production)
COOKIE_SECRETCookie signing secret (must set before production)
STORAGE_ACCESS_KEY_IDObject storage key (S3/MinIO)
STORAGE_SECRET_ACCESS_KEYObject storage secret (S3/MinIO)
MAIL_USERNAMESMTP username
MAIL_PASSWORDSMTP password
REDISYesEnable Redis (true/false)
REDIS_URLYesSet to redis://redis-global:6379 when Redis enabled
REDIS_PREFIXKey prefix for Redis (multi-tenant isolation)
UIYesEnable UI build (true/false, default true)
OPEN_APIYesEnable OpenAPI docs at /api-docs (true/false)
SOCKETYesEnable Socket.IO realtime (true/false)

Server (deploy/server/.env)

VariablePurpose
LETSENCRYPT_EMAILEmail for SSL certificate notifications
PROXY_HTTP_PORTnginx-proxy HTTP port (default 80)
PROXY_HTTPS_PORTnginx-proxy HTTPS port (default 443)
MYSQL_ROOT_PASSWORDMySQL root password
MYSQL_DATABASEDefault MySQL database name
MYSQL_PORTMySQL host port (default 4000)
POSTGRES_USERPostgres superuser
POSTGRES_PASSWORDPostgres password
POSTGRES_DBDefault Postgres database name
POSTGRES_PORTPostgres host port (default 4001)
REDISEnable Redis service in server infra
PHPMYADMIN_DOMAINphpMyAdmin virtual host domain
PHPMYADMIN_LOCAL_PORTphpMyAdmin local port (default 127.0.0.1:8082)
PGADMIN_DOMAINpgAdmin virtual host domain
PGADMIN_DEFAULT_EMAILpgAdmin login email
PGADMIN_DEFAULT_PASSWORDpgAdmin login password
PGADMIN_LOCAL_PORTpgAdmin local port (default 127.0.0.1:8083)

Troubleshooting

View app logs

bash
docker compose -f deploy/docker-compose.yml logs -f app

View server infra logs

bash
docker compose -f deploy/server/docker-compose.yml logs -f mysql-global

Rebuild without cache

bash
docker compose -f deploy/docker-compose.yml build --no-cache app
docker compose -f deploy/docker-compose.yml up -d --force-recreate app

Enter the app container

bash
docker exec -it <app-container-name> sh

Released under the MIT License.