Docker Compose
This guide covers a complete production deployment of TheTerms using Docker Compose. The stack runs three services: the Next.js app, PostgreSQL 16, and Redis 7.
Prerequisites
Section titled “Prerequisites”- Docker Engine 20+ and Docker Compose v2+
- A server with at least 1 CPU core, 2 GB RAM, and 10 GB disk
- A domain name with DNS pointed at your server (for HTTPS)
Quick Start
Section titled “Quick Start”-
Clone the repository
Terminal window git clone https://github.com/ashwineaso/theterms.gitcd theterms -
Create your environment file
Terminal window cp apps/web/.env.example .env -
Configure required variables
Edit
.envand set at minimum:Terminal window NEXTAUTH_SECRET= # openssl rand -base64 32NEXTAUTH_URL= # https://your-domain.comPOSTGRES_PASSWORD= # a strong unique passwordSee Environment Variables for the full reference.
-
Build and start all services
Terminal window docker compose up -d --build -
Verify
Terminal window docker compose psAll three services should show
Up (healthy). Openhttp://your-server:3000.
Service Architecture
Section titled “Service Architecture”The docker-compose.yml defines three services:
| Service | Image | Port | Purpose |
|---|---|---|---|
app | Built from Dockerfile | ${PORT:-3000}:3000 | Next.js application |
postgres | postgres:16-alpine | internal | PostgreSQL database |
redis | redis:7-alpine | internal | Redis cache |
Only the app service is exposed externally. Postgres and Redis are internal to the Docker network.
Full docker-compose.yml
Section titled “Full docker-compose.yml”services: app: build: context: . dockerfile: Dockerfile ports: - "${PORT:-3000}:3000" environment: DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD:-password}@postgres:5432/theterms" REDIS_URL: "redis://redis:6379" NEXTAUTH_URL: "${NEXTAUTH_URL}" NEXTAUTH_SECRET: "${NEXTAUTH_SECRET}" NEXT_PUBLIC_APP_URL: "${NEXTAUTH_URL}" RESEND_API_KEY: "${RESEND_API_KEY:-}" EMAIL_FROM: "${EMAIL_FROM:-TheTerms <noreply@example.com>}" GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID:-}" GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET:-}" MICROSOFT_CLIENT_ID: "${MICROSOFT_CLIENT_ID:-}" MICROSOFT_CLIENT_SECRET: "${MICROSOFT_CLIENT_SECRET:-}" depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped
postgres: image: postgres:16-alpine environment: POSTGRES_DB: theterms POSTGRES_USER: postgres POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-password}" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 10 restart: unless-stopped
redis: image: redis:7-alpine command: ["redis-server", "--appendonly", "yes"] volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 10 restart: unless-stopped
volumes: postgres_data: redis_data:Multi-Stage Docker Build
Section titled “Multi-Stage Docker Build”The Dockerfile uses a four-stage build to minimise the final image size:
base— Alpine + Node.js with corepack enabled for pnpmdeps— Installs all dependencies (including dev) with pnpmbuilder— Builds the Next.js app withpnpm build, generates Prisma clientrunner— Minimal Alpine image with only the standalone Next.js output, Prisma CLI, and schema file
The runner stage uses output: "standalone" from Next.js, which copies only the necessary files and bundles a minimal node_modules. The Prisma CLI is installed separately in runner so it can run migrations on startup.
Automatic Migrations
Section titled “Automatic Migrations”Every time the app container starts, docker-entrypoint.sh runs before the application:
prisma migrate deploy --schema=./packages/db/prisma/schema.prismaThis applies any pending migrations against the database. The app only starts serving traffic after migrations complete successfully. If migrations fail, the container exits with an error.
Healthchecks
Section titled “Healthchecks”Both postgres and redis have healthchecks configured. The app service uses depends_on: condition: service_healthy, so it won’t start until both dependencies are ready. This prevents connection errors on first boot.
Viewing Logs
Section titled “Viewing Logs”docker compose logs -f app # App logs (streaming)docker compose logs postgres # Database logsdocker compose logs redis # Redis logs