N Nexus Docs
Deployment

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

Step 2: Configure environment

cd infrastructure/compose
cp .env.production.template .env

Fill 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 -d

Caddy 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.sh

Features

Caddy Routing

PathBackendService
/api/v1/*api:3001NestJS API
/docs*api:3001Swagger/OpenAPI
/*web:3000Next.js frontend
grafana.DOMAINgrafana:3000Grafana 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-Security with HSTS preload
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin

Persistent Volumes

VolumeServicePurpose
caddy_dataCaddyTLS certificates
caddy_configCaddyConfiguration state
redis_dataRedisJob queue persistence
clickhouse_dataClickHouseAnalytics data
grafana_dataGrafanaDashboard state

Resource Limits

Each service has configured memory and CPU limits to prevent resource contention:

ServiceMemory LimitCPU Limit
Web512 MB0.5
API768 MB1.0
Workers1 GB1.0
Redis320 MB0.25
ClickHouse2 GB1.0
Grafana512 MB0.5

Automated Deployment

Use the deploy script for SSH-based deployments:

./infrastructure/scripts/deploy-vps.sh --host your-server.com --user deploy --tag sha-abc1234

The 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 -f

Scaling Workers

Increase worker replicas for higher AI job throughput:

docker compose -f docker-compose.prod.yml up -d --scale workers=3

Backup

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