본문으로 건너뛰기 Docker Compose: Node API, PostgreSQL, Redis in One Stack

Docker Compose: Node API, PostgreSQL, Redis in One Stack

Docker Compose: Node API, PostgreSQL, Redis in One Stack

이 글의 핵심

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)

# .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

# 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)

// 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.

{
  "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)

    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

# 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.

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Production-style Docker Compose for Node.js: define API, PostgreSQL, and Redis in one stack with docker-compose.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Node.js, Docker, Docker Compose, PostgreSQL, Redis, Container, Production.