Docker Compose
Deploy NexusCommerce to a single VPS with Docker Compose, Caddy auto-SSL, and zero-downtime updates.
Overview
The Docker Compose production stack runs seven services on a single server: Caddy (reverse proxy with automatic Let's Encrypt TLS), Next.js frontend, NestJS API, Python workers, Redis, ClickHouse, and Grafana. This is the fastest path to a production deployment.
Key Concepts
Caddy — Handles TLS certificate provisioning, HTTP/2, HTTP/3, and reverse proxy routing. No manual certificate management required.
Standalone Next.js — The web image uses Next.js standalone output, reducing the image from ~500 MB to ~50 MB by eliminating node_modules from the production container.
Bridge Network — All services communicate on an internal Docker bridge network (nexus_net). Only Caddy exposes ports 80 and 443 to the host.
Getting Started
Prerequisites
- A VPS with Docker 24+ and Docker Compose V2 (4 CPU, 8 GB RAM minimum)
- A domain with DNS A record pointing to your server IP
- A Supabase Cloud project
Step 1: Build images (or pull from registry)
Build locally:
docker build -f infrastructure/docker/Dockerfile.api -t ghcr.io/your-org/nexuscommerce/api:latest .
docker build -f infrastructure/docker/Dockerfile.web -t ghcr.io/your-org/nexuscommerce/web:latest .
docker build -f services/workers/Dockerfile -t ghcr.io/your-org/nexuscommerce/workers:latest .Or pull from the registry after CI/CD has run:
docker pull ghcr.io/your-org/nexuscommerce/api:latest
docker pull ghcr.io/your-org/nexuscommerce/web:latest
docker pull ghcr.io/your-org/nexuscommerce/workers:latestStep 2: Configure environment
cd infrastructure/compose
cp .env.production.template .envFill in all values marked (REQUIRED) — at minimum: DOMAIN, Supabase credentials, CLICKHOUSE_PASSWORD, and GRAFANA_ADMIN_PASSWORD.
Step 3: Deploy
docker compose -f docker-compose.prod.yml up -dCaddy will automatically provision a TLS certificate for your domain. All services should be healthy within 60 seconds.
Step 4: Verify
curl -sf https://yourdomain.com/api/v1/health
# {"status":"ok","timestamp":"...","version":"..."}Or run the health check script:
HEALTH_HOST=yourdomain.com bash infrastructure/scripts/health-check.shFeatures
Caddy Routing
| Path | Backend | Service |
|---|---|---|
/api/v1/* | api:3001 | NestJS API |
/docs* | api:3001 | Swagger/OpenAPI |
/* | web:3000 | Next.js frontend |
grafana.DOMAIN | grafana:3000 | Grafana dashboards |
Grafana runs on a subdomain (configurable via GRAFANA_SUBDOMAIN env var, defaults to grafana).
Security Headers
Caddy injects production security headers on all responses:
Strict-Transport-Securitywith HSTS preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-origin
Persistent Volumes
| Volume | Service | Purpose |
|---|---|---|
caddy_data | Caddy | TLS certificates |
caddy_config | Caddy | Configuration state |
redis_data | Redis | Job queue persistence |
clickhouse_data | ClickHouse | Analytics data |
grafana_data | Grafana | Dashboard state |
Resource Limits
Each service has configured memory and CPU limits to prevent resource contention:
| Service | Memory Limit | CPU Limit |
|---|---|---|
| Web | 512 MB | 0.5 |
| API | 768 MB | 1.0 |
| Workers | 1 GB | 1.0 |
| Redis | 320 MB | 0.25 |
| ClickHouse | 2 GB | 1.0 |
| Grafana | 512 MB | 0.5 |
Automated Deployment
Use the deploy script for SSH-based deployments:
./infrastructure/scripts/deploy-vps.sh --host your-server.com --user deploy --tag sha-abc1234The script syncs compose files, pulls new images, and performs a rolling restart. See CI/CD for automated deployments via GitHub Actions.
Configuration
Updating
To update to a new version:
cd /opt/nexuscommerce
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans
docker image prune -fScaling Workers
Increase worker replicas for higher AI job throughput:
docker compose -f docker-compose.prod.yml up -d --scale workers=3Backup
ClickHouse and Redis data are stored in named Docker volumes. Back up with:
docker run --rm -v nexuscommerce_clickhouse_data:/data -v $(pwd):/backup alpine tar czf /backup/clickhouse-backup.tar.gz /data
docker run --rm -v nexuscommerce_redis_data:/data -v $(pwd):/backup alpine tar czf /backup/redis-backup.tar.gz /data