[2026] Docker Compose: Node API, PostgreSQL, Redis in One Stack | Production Template

[2026] Docker Compose: Node API, PostgreSQL, Redis in One Stack | Production Template

이 글의 핵심

Production-style Docker Compose for Node.js: define API, PostgreSQL, and Redis in one stack with docker-compose.yml, env, health checks, volumes, and restart policies.

Introduction

Docker Compose declares multiple containers as one project so docker compose up can align local and staging. A Node.js API behind PostgreSQL and Redis is a very common stack; defining it with health checks, volumes, and restart policies makes failures easier to recover from and automation simpler to wire. Production still needs secret management, resource limits, log drivers, and network isolation. This post centers on a runnable compose template plus an operations checklist on top.

After reading this post

  • Patterns for docker-compose.yml that start API · Postgres · Redis in dependency order
  • Environment separation, health checks, and named volumes for data and cache
  • How to avoid common pitfalls (hostname, port binding, migration timing)

Table of contents

  1. Concepts: Compose and production stacks
  2. Hands-on: docker-compose.yml template
  3. Advanced: resources, secrets, profiles
  4. Performance: volumes and connection pools
  5. Real-world cases
  6. Troubleshooting
  7. Wrap-up

Concepts: Compose and production stacks

Basics

  • Service: unit of execution (e.g. api, db, redis)—image, command, ports, environment.
  • Network: on the default bridge, service names resolve as DNS (api connects to postgres at postgres:5432).
  • Volume: persists data across container recreation (e.g. Postgres data directory).
  • Healthcheck: with depends_on condition: service_healthy, the app can start after dependencies are ready (Compose v2+).

Why it matters

Locally you can bind ports on localhost, but if DB version and Redis config differ per developer, you get “cannot reproduce” bugs. Compose pins versions, same network, same env in code.

Hands-on: docker-compose.yml template

Example project root. Never commit real secrets in .env—use CI/CD secrets or server env files.

.env.example (commit to the repo)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# .env.example — copy to .env
NODE_ENV=production
POSTGRES_USER=app
POSTGRES_PASSWORD=change_me
POSTGRES_DB=appdb
DATABASE_URL=postgresql://app:change_me@postgres:5432/appdb
REDIS_URL=redis://:redis_secret@redis:6379/0

docker-compose.yml

다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# docker-compose.yml
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    image: myorg/api:${IMAGE_TAG:-latest}
    restart: unless-stopped
    ports:
      - "${API_PORT:-3000}:3000"
    environment:
      NODE_ENV: ${NODE_ENV:-production}
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
    env_file:
      - .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 40s
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: >
      redis-server
      --appendonly yes
      --requirepass ${REDIS_PASSWORD}
    environment:
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
volumes:
  pgdata:
  redisdata:

Notes

  • depends_on + condition: service_healthy: API starts after DB and Redis are ready.
  • Named volumes pgdata, redisdata: data survives container removal (docker compose down -v deletes them—be careful).
  • Redis AOF (appendonly yes) improves durability a bit (add RDB snapshots if required).

API /health endpoint (Node.js example)

아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// src/health.js — Express example
import express from 'express';
export function healthRouter() {
  const r = express.Router();
  r.get('/health', (_req, res) => {
    res.status(200).json({ status: 'ok', ts: Date.now() });
  });
  return r;
}

Migration timing

Often run migrations once after DB connect in the app startup script. 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

{
  "scripts": {
    "migrate": "node scripts/migrate.js",
    "start": "node dist/index.js"
  }
}

Prefer sequential execution in code over npm run migrate && npm run start to reduce shell coupling.

Advanced: resources, secrets, profiles

Resource limits (production-oriented)

아래 코드는 yaml를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

deploy is Swarm-oriented; on plain Compose you may use other keys or move limits to Kubernetes/Nomad. On a single host, check Compose docs for cgroup compatibility.

Profiles for dev-only services

아래 코드는 yaml를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# docker-compose.dev.yml
services:
  adminer:
    image: adminer
    profiles: [dev]
    ports:
      - "8080:8080"
docker compose --profile dev up

Secrets (Swarm or external vault)

On a single server, file-based env with chmod 600 is practical. On Kubernetes, use Secret resources.

Performance: volumes and connection pools

TopicRecommendationNotes
Postgres volumenamed volumeHost bind has OS-specific perf/permission issues
Connection poolcap max in the appcontainers × pool size ≤ DB max_connections
Redismaxmemory + policye.g. allkeys-lru for cache-only
LogsJSON driver or rotationavoid disk fill from container logs
Trade-off: restart: always is convenient but can loop forever if the service is broken—pair with health checks and alerts.

Real-world cases

  • Staging = same compose as prod: reuse the file with STAGING_* variables only.
  • CI compose tests: docker compose up -d, smoke tests, docker compose down—see GitHub Actions post.
  • Nginx in front: TLS and reverse proxy in Nginx setup post.

Troubleshooting

SymptomCauseFix
API cannot reach DBusing localhostDB host must be the postgres service name
password authentication failed.env mismatchsync DATABASE_URL with Postgres env
Health check never passesmissing /health or wrong portcheck path, port, wget/curl in image
Redis NOAUTHpassword mismatchalign REDIS_URL and requirepass
Disk fulllogs / AOF growthlog rotation, Redis maxmemory
Debug: docker compose logs -f api postgres redis; docker compose exec postgres psql -U app -d appdb for DB-only checks.

Wrap-up

  • Compose defines API · Postgres · Redis together for reproducibility and faster onboarding.
  • Health checks, volumes, restart policies are close to mandatory near production.
  • Next: Nginx reverse proxy in front of Node.js for public endpoints and TLS.
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3