Docker Compose로 Node API·PostgreSQL·Redis 한 번에 띄우기 | 프로덕션 템플릿
이 글의 핵심
docker-compose로 Node API, PostgreSQL, Redis를 묶고 환경 변수·헬스체크·영속 볼륨·재시작 정책까지 프로덕션에 가깝게 구성합니다.
들어가며
Docker Compose는 여러 컨테이너를 한 프로젝트로 선언해 docker compose up 한 번에 로컬·스테이징 환경을 맞출 수 있게 합니다. Node.js API 뒤에 PostgreSQL과 Redis를 붙이는 구성은 실무에서 매우 흔하며, 이를 헬스체크·볼륨·재시작 정책까지 포함해 정의해두면 장애 시 자동 복구와 배포 자동화가 쉬워집니다.
다만 프로덕션에서는 비밀 번호 관리, 리소스 한도, 로그 드라이버, 네트워크 격리가 추가로 필요합니다. 이 글은 실행 가능한 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 이름으로 해석됩니다(
api가postgres에postgres:5432로 접속). - Volume: 컨테이너 재생성 후에도 유지할 데이터를 저장합니다(Postgres 데이터 디렉터리 등).
- Healthcheck:
depends_on의condition: 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_URL과 requirepass 동일 |
| 디스크 꽉 참 | 로그·AOF 증가 | 로그 로테이션, Redis maxmemory |
디버깅 팁: docker compose logs -f api postgres redis로 동시에 보고, docker compose exec postgres psql -U app -d appdb로 DB만 따로 확인합니다.
마무리
- Compose로 API·Postgres·Redis를 한 번에 정의하시면 환경 재현성과 온보딩이 빨라집니다.
- 헬스체크·볼륨·재시작 정책은 프로덕션 인근 환경에서 필수에 가깝습니다.
- 다음으로 Nginx 리버스 프록시로 Node.js 서비스 앞단 구성하기에서 공개 엔드포인트와 TLS를 정리해 보세요.
프로덕션 배포 전 체크리스트
.env·비밀번호가 Git에 올라가지 않았는지, 스테이징·프로덕션 값이 섞이지 않았는지 확인합니다.- DB·Redis 백업 주기와
down -v실수 시 복구 절차를 문서화합니다. - API
max_connections대비 연결 풀 합이 DB 한도를 넘지 않는지 점검합니다.