Production runs on Docker Swarm. A single server image contains both the
Express backend and the built React frontend (served via express.static
in production mode). An external Caddy reverse proxy reads Docker Swarm
labels for automatic HTTPS and domain routing.
Internet → Caddy (*.jtlapp.net) → server:3000 → Express (API + static files)
→ db:5432 → PostgreSQL
- Docker context
swarm1(or your prod context) configured and reachable - SOPS access to decrypt
config/prod/secrets.env - Swarm initialized on the target host (
docker swarm init)
Secrets must exist in the swarm before the stack can deploy:
npm run secrets:prodThis decrypts config/prod/secrets.env via SOPS and creates each key as a
Docker Swarm secret on the production context. See Secrets Management
for details.
npm run deployThis does two things:
- Builds the server image on the prod context (
docker compose build) - Deploys the stack (
docker stack deploy)
After the first deploy, run Prisma migrations:
DOCKER_CONTEXT=swarm1 docker exec $(DOCKER_CONTEXT=swarm1 docker ps -q -f name=myapp_server) npx prisma migrate deploynpm run deployDocker Swarm performs rolling updates by default — new containers start before old ones are drained. Run migrations if the schema changed.
The external Caddy reverse proxy reads labels from the deploy block in
docker-compose.prod.yml:
deploy:
labels:
caddy: ${APP_DOMAIN:-myapp.jtlapp.net}
caddy.reverse_proxy: "{{upstreams 3000}}"APP_DOMAIN is set in .env and sourced by the deploy script.
To roll back to a previous image, re-deploy with the previous tag:
set -a && . .env && set +a
DOCKER_CONTEXT=$PROD_DOCKER_CONTEXT TAG=v1 docker stack deploy -c docker-compose.prod.yml myapp| Script | What It Does |
|---|---|
npm run secrets:prod |
Create swarm secrets from config/prod/secrets.env |
npm run secrets:prod:rm |
Remove existing swarm secrets (needed before updating) |
npm run build:docker |
Build prod image on the dev Docker context |
npm run deploy |
Build on prod context + deploy stack |
secret not found: db_password
Swarm secrets haven't been created. Run npm run secrets:prod first.
image not found
The build step failed or hasn't run. deploy builds automatically,
but check the build output for errors.
Container won't start
Check logs: DOCKER_CONTEXT=swarm1 docker service logs myapp_server
Migration failures
Connect to the database and check state:
DOCKER_CONTEXT=swarm1 docker exec -it $(docker ps -q -f name=myapp_db) psql -U app