본문으로 건너뛰기
Previous
Next
Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기

Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기

Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기

이 글의 핵심

Docker Compose Node.js 프로덕션: API·DB·Redis를 한 스택으로 정의하는 docker-compose.yml, env·헬스체크·볼륨·재시작 정책까지 실무 템플릿으로 정리합니다.

들어가며

Docker Compose는 여러 컨테이너를 한 프로젝트로 선언해 docker compose up 한 번에 로컬·스테이징 환경을 맞출 수 있게 합니다. Node.js API 뒤에 PostgreSQLRedis를 붙이는 구성은 실무에서 매우 흔하며, 이를 헬스체크·볼륨·재시작 정책까지 포함해 정의해두면 장애 시 자동 복구와 배포 자동화가 쉬워집니다. 다만 프로덕션에서는 비밀 번호 관리, 리소스 한도, 로그 드라이버, 네트워크 격리가 추가로 필요합니다. 이 글은 실행 가능한 compose 템플릿을 중심으로, 그 위에 얹을 운영 체크리스트를 덧붙입니다. 이미지를 빌드·배포하기 전에 Node.js 테스트GitHub Actions CI/CD로 같은 스택을 검증하는 편이 안전합니다. Kubernetes로 넘어가려면 minikube 배포를, C++·멀티 스테이지 패턴은 C++ Docker 가이드·C++ GitHub Actions와 비교해 보세요. 애플리케이션에서 DB에 붙는 방법은 Node.js 데이터베이스 연동·C++ DB 연동(libpq 등)을, 엔진 선택은 PostgreSQL vs MySQL을, 캐시 패턴은 Redis 캐싱을 함께 보시면 코드 ↔ 스택이 맞물립니다. 호스트 디스크·inode 이슈는 Linux 트러블슈팅과 겹칠 수 있습니다. 비유로 말씀드리면, Docker Compose는 악단을 한 번에 맞추는 지휘자의 점수(누가 먼저 올라오고, 어떤 순서로 연주할지)에 가깝고, 동시에 재료와 단계가 적힌 레시피북(이미지·환경·볼륨을 한 파일에 고정)이기도 합니다. 요청 흐름은 대략 클라이언트 → (호스트 포트) → api 컨테이너 → (DNS 이름 postgres/redis) → DB·캐시 순으로 이해하시면 됩니다.

이 글을 읽으면

  • API·Postgres·Redis를 의존 순서에 맞게 기동하는 docker-compose.yml 패턴을 익히실 수 있습니다
  • 환경 변수 분리, 헬스체크, named volume으로 데이터·캐시를 안전하게 다루는 요령을 파악하실 수 있습니다
  • 흔한 함정(호스트명, 포트 바인딩, 마이그레이션 타이밍)을 피하는 방법을 정리해 두었습니다

개념: Compose와 프로덕션 스택

기본 개념

  • Service: 실행 단위(예: api, db, redis). 이미지·명령·포트·환경을 묶습니다.
  • Network: 기본 브리지 네트워크에서 서비스 이름이 DNS 이름으로 해석됩니다(apipostgrespostgres:5432로 접속).
  • Volume: 컨테이너 재생성 후에도 유지할 데이터를 저장합니다(Postgres 데이터 디렉터리 등).
  • Healthcheck: depends_oncondition: service_healthy와 함께 쓰면 준비된 뒤에 앱을 띄울 수 있습니다(Compose v2+).

왜 필요한가

로컬에서는 localhost에 각각 포트를 열어도 되지만, 팀원마다 DB 버전·Redis 설정이 달라지면 “재현 불가” 버그가 납니다. Compose는 버전 고정·동일 네트워크·동일 env를 코드로 고정합니다.

실전: docker-compose.yml 템플릿

프로젝트 루트 예시입니다. 실제 비밀번호는 .env를 Git에 넣지 말고 CI/CD 시크릿이나 서버의 env 파일로 주입하세요.

.env.example (저장소에 커밋)

# .env.example — 복사 후 .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:

주석 포인트

  • build.context / dockerfile: 이미지를 저장소의 Dockerfile로 빌드합니다. image는 빌드 결과에 붙일 이름·태그입니다.
  • restart: unless-stopped: 데몬 재시작·비정상 종료 후에 컨테이너가 다시 뜨도록 합니다(수동으로 멈춘 경우는 제외).
  • ports: "${API_PORT:-3000}:3000": 호스트의 포트(기본 3000)를 컨테이너 3000으로 넘깁니다. ${VAR:-기본값}은 셸과 비슷하게 환경 변수 미설정 시 기본값을 씁니다.
  • depends_on + condition: service_healthy: DB·Redis가 헬스체크를 통과한 뒤 API가 시작됩니다(단순 depends_on만으로는 “준비 완료”를 보장하지 않습니다).
  • healthcheck(interval/timeout/retries/start_period): 프로브 주기·실패 판정·기동 유예를 정합니다. API는 wget으로 로컬 /health를 두드립니다.
  • named volume pgdata, redisdata: 데이터가 도커 볼륨 영역에 남아 컨테이너를 재생성해도 유지됩니다(docker compose down -v는 볼륨까지 지우므로 주의하십시오).
  • Redis AOF(appendonly yes): 명령을 추가 로그로 남겨 재시작 시 복구력을 높입니다(요구 수준에 따라 RDB 스냅샷 병행을 검토합니다).
  • command: > redis-server ....--requirepass: 비밀번호 인증을 켭니다. REDIS_URL의 비밀번호와 반드시 맞춰야 합니다.

API의 /health 엔드포인트 (Node.js 예시)

// src/health.js — Express 예시
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;
}

마이그레이션 실행 타이밍

앱 기동 스크립트에서 DB 연결 후 마이그레이션을 한 번 실행하는 패턴이 흔합니다.

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

엔트리에서 npm run migrate && npm run start 대신 코드에서 순차 실행하면 셸 의존을 줄일 수 있습니다.

고급: 리소스·시크릿·프로파일

리소스 제한 (프로덕션 권장)

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

Swarm 전용 deploy가 아닌 일반 compose에서는 비 Swarm 환경에선 mem_limit 등 호환 키를 쓰거나, Kubernetes/Nomad로 넘어가며 제한을 거는 경우가 많습니다. 호스트 한 대에서 돌릴 때는 docker run 대신 compose에 cgroup 관련 옵션을 맞추는지 문서를 확인하세요.

프로파일로 dev만 부가 서비스

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

시크릿 (Docker Swarm 또는 외부 비밀 저장소)

단일 서버라면 파일 기반 env + 권한 chmod 600이 현실적입니다. Kubernetes를 쓰면 Secret 리소스로 치환합니다.

성능: 볼륨과 연결 풀

항목권장비고
Postgres 볼륨named volume호스트 바인드는 OS별 성능·권한 이슈
연결 풀앱에서 max 제한컨테이너 수 × 풀 크기 ≤ DB max_connections
Redis적절한 maxmemory + 정책캐시 전용이면 allkeys-lru
로그JSON 드라이버 또는 파일 로테이션컨테이너 로그 디스크 폭주 방지
트레이드오프: restart: always는 편하지만 재시작 루프에 빠지면 서비스가 계속 죽을 수 있으므로 헬스체크·로그 알림과 함께 써야 합니다.

실무 사례

  • 스테이징 = 프로덕션과 동일 compose: 변수만 STAGING_*로 바꿔 동일 파일을 재사용합니다.
  • CI에서 compose 테스트: docker compose up -d 후 스모크 테스트·docker compose down으로 통합 검증을 자동화합니다(GitHub Actions 글 참고).
  • Nginx 앞단: TLS 종료와 리버스 프록시는 Nginx 구성 글에서 다룹니다.

트러블슈팅

증상원인해결
API가 DB에 연결 실패localhost 사용DB 호스트는 postgres 서비스명
password authentication failed.env 불일치DATABASE_URL과 Postgres env 동기화
헬스체크 무한 실패/health 없음 또는 포트 불일치경로·포트·wget/curl 설치 확인
Redis NOAUTH비밀번호 불일치REDIS_URLrequirepass 동일
디스크 꽉 참로그·AOF 증가로그 로테이션, Redis maxmemory
디버깅 팁: docker compose logs -f api postgres redis로 동시에 보고, docker compose exec postgres psql -U app -d appdb로 DB만 따로 확인합니다.

내부 동작과 핵심 메커니즘

이 글의 주제는 「Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기 | 프로덕션 템플릿」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 요청 경로와 상태 전이를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

알고리즘·프로토콜 관점에서의 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.

프로덕션 운영 패턴

실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.

영역운영 관점에서의 질문
관측성요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가
안전성입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가
신뢰성재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가
성능캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가

운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.


문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스 컨디션, 타임아웃, 외부 의존성 불안정최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인
성능 저하N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사상한·TTL·스냅샷 비교(힙 덤프/트레이스)
빌드·배포만 실패환경 변수·권한·플랫폼 차이CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin)

권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.

마무리

  • Compose로 API·Postgres·Redis를 한 번에 정의하시면 환경 재현성과 온보딩이 빨라집니다.
  • 헬스체크·볼륨·재시작 정책은 프로덕션 인근 환경에서 필수에 가깝습니다.
  • 다음으로 Nginx 리버스 프록시로 Node.js 서비스 앞단 구성하기에서 공개 엔드포인트와 TLS를 정리해 보세요.

프로덕션 배포 전 체크리스트

  • .env·비밀번호가 Git에 올라가지 않았는지, 스테이징·프로덕션 값이 섞이지 않았는지 확인합니다.
  • DB·Redis 백업 주기down -v 실수 시 복구 절차를 문서화합니다.
  • API max_connections 대비 연결 풀 합이 DB 한도를 넘지 않는지 점검합니다.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Docker Compose Node.js 프로덕션: API·DB·Redis를 한 스택으로 정의하는 docker-compose.yml, env·헬스체크·볼륨·재시작 정책까지 실무 템플릿으로 정리합니다. Start no… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Node.js, Docker, Docker Compose, PostgreSQL, Redis, 프로덕션, 환경 설정 등으로 검색하시면 이 글이 도움이 됩니다.