docker

Full-Stack Docker Compose (Node + PostgreSQL + Redis)

Complete Docker Compose config for a full-stack app with Node.js API, PostgreSQL database, and Redis cache.

Overview

A complete Docker Compose stack for a typical full-stack web application. Includes a Node.js API server, PostgreSQL for persistent storage, Redis for caching and sessions, and proper service dependencies with health checks.

Configuration

# docker-compose.yml

services:
  # ── Node.js API Server ──
  api:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    container_name: fullstack-api
    restart: unless-stopped

    ports:
      - "3000:3000"                    # API port
      - "9229:9229"                    # Debugger

    environment:
      NODE_ENV: development
      PORT: 3000
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD:-secret}@postgres:5432/appdb
      REDIS_URL: redis://:${REDIS_PASSWORD:-redispass}@redis:6379/0
      SESSION_SECRET: ${SESSION_SECRET:-dev-session-secret}

    volumes:
      - .:/app
      - /app/node_modules

    command: npx nodemon --inspect=0.0.0.0:9229 src/index.ts
    init: true

    depends_on:
      postgres:
        condition: service_healthy     # Wait for DB to accept connections
      redis:
        condition: service_healthy     # Wait for Redis to respond

    networks:
      - backend

    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 30s

  # ── PostgreSQL Database ──
  postgres:
    image: postgres:16-alpine
    container_name: fullstack-postgres
    restart: unless-stopped

    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
      POSTGRES_DB: appdb

    ports:
      - "5432:5432"                    # Expose for local DB tools

    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/01-init.sql

    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

    networks:
      - backend

  # ── Redis Cache ──
  redis:
    image: redis:7-alpine
    container_name: fullstack-redis
    restart: unless-stopped

    command: redis-server --requirepass ${REDIS_PASSWORD:-redispass} --maxmemory 128mb --maxmemory-policy allkeys-lru

    ports:
      - "6379:6379"

    volumes:
      - redis_data:/data

    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redispass}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

    networks:
      - backend

  # ── Database Migrations ──
  migrate:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    profiles:
      - tools

    environment:
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD:-secret}@postgres:5432/appdb

    command: npm run db:migrate

    depends_on:
      postgres:
        condition: service_healthy

    networks:
      - backend

  # ── pgAdmin (optional) ──
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: fullstack-pgadmin
    profiles:
      - tools

    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: admin

    ports:
      - "5050:80"

    depends_on:
      postgres:
        condition: service_healthy

    networks:
      - backend

volumes:
  postgres_data:
  redis_data:

networks:
  backend:
    driver: bridge

Key Options Explained

  • depends_on with condition — Ensures the API starts only after PostgreSQL and Redis pass their health checks, preventing connection errors on startup.
  • DATABASE_URL / REDIS_URL — Connection strings use Docker service names (postgres, redis) as hostnames. Docker DNS resolves these within the network.
  • networks: backend — All services share a custom bridge network. Services can reach each other by name but are isolated from other Docker networks.
  • profiles: [tools] — Migration runner and pgAdmin only start when explicitly requested: docker compose --profile tools run migrate.
  • Anonymous volume /app/node_modules — Prevents host node_modules from shadowing the container’s installed dependencies.

Common Modifications

  • Add a frontend: Include a separate service for React/Next.js with its own build context, port (e.g., 3001), and volume mounts.
  • Add Nginx reverse proxy: Place an Nginx service in front of the API with proxy_pass http://api:3000 for SSL termination or load balancing.
  • Production override: Create docker-compose.prod.yml with docker compose -f docker-compose.yml -f docker-compose.prod.yml up to override dev settings.
  • Seed data: Add a seed service similar to migrate that runs npm run db:seed on demand.
  • Shared .env file: Create a .env file in the project root with DB_PASSWORD, REDIS_PASSWORD, and SESSION_SECRET values.