Docker Compose로 프로덕션 스택 구축 완벽 가이드: 웹+DB+캐시 한 번에 (2026)

docker run 반복의 고통에서 벗어나기

서버에 웹, 데이터베이스, 캐시를 올려야 합니다. docker run 명령을 하나씩 쳐서 컨테이너를 띄우면 명령이 매우 길어집니다 — 이미지 이름, 포트 매핑, 환경변수, 볼륨까지 전부 적어야 하니까요. 서비스가 늘어날수록 명령도 늘어나고 실수가 생깁니다. 더 큰 문제는 켜지는 순서입니다: DB가 켜지기도 전에 웹이 먼저 뜨면 에러를 뿜고, 서버가 재부팅되면 새벽에 일어나 일일이 다시 켜야 합니다.

Docker Compose가 바로 이 문제를 해결합니다. 선언적 정의 파일 하나로 전체 스택을 관리합니다. 이 튜토리얼에서는 웹(Nginx) + DB(PostgreSQL) + 캐시(Redis) 프로덕션 멀티 컨테이너 스택을 한 번에 구축합니다. W15/W21 입문편에서 한 단계 더 나아간 오케스트레이션 심화이자 VPS 데브옵스 시리즈의 피날레입니다.

1. docker-compose.yml 기본 구조

핵심은 docker-compose.yml 파일 하나입니다. 맨 위에 services 키를 적고 그 아래에 웹·DB·캐시를 각각 정의합니다. 들여쓰기로 계층을 표현합니다.

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on:
      db:
        condition: service_healthy
    restart: always

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always

  cache:
    image: redis:7-alpine
    volumes:
      - cache_data:/data
    restart: always

volumes:
  db_data:
  cache_data:

이렇게 정의하면 서비스 관계가 한눈에 보입니다. YAML로 선언하니 변경 사항도 추적되고, git에 커밋하면 서버 구성 자체가 버전 관리됩니다. 팀원도 같은 파일을 받아 똑같은 환경을 만듭니다 — 재현 가능한 환경이 바로 여기서 나옵니다.

2. depends_on + healthcheck로 시작 순서 보장

웹은 DB가 준비된 뒤에 떠야 합니다. depends_on으로 이 순서를 선언합니다. 하지만 컨테이너가 뜬다고 바로 준비된 것은 아닙니다 — DB 프로세스가 켜져도 실제 접속을 받기까지 시간이 걸립니다. 그래서 healthcheck를 씁니다.

DB 서비스에 healthcheck를 정의하고 testpg_isready 같은 명령을 넣습니다. 이 명령은 DB가 접속을 받을 준비가 되면 참을 돌려줍니다. 그리고 depends_oncondition: service_healthy를 적으면 DB가 건강해질 때까지 웹이 기다립니다. 이제 순서 문제가 깔끔하게 해결됩니다.

3. restart always — 재부팅 자동 복구

서버가 재부팅되어도 컨테이너가 알아서 다시 살아나야 합니다. restart: always를 적어 주면 서버가 꺼져도 컨테이너가 자동으로 다시 올라옵니다. 순서 문제와 재부팅 문제가 한 번에 해결됩니다.

4. named volume으로 데이터 영속화

데이터는 컨테이너 밖에 보관해야 합니다. 컨테이너를 지우면 안의 데이터도 사라지기 때문입니다. DB 데이터가 날아가면 서비스를 다시 시작할 수 없습니다. 그래서 volume을 씁니다.

volumes 최상위 키에 db_data 같은 이름을 정의하고, DB 서비스의 volumes에서 이 이름을 /var/lib/postgresql/data에 마운트합니다. 이제 컨테이너를 지워도 데이터는 남고, 업데이트해도 데이터베이스 내용이 그대로 보존됩니다.

bind mount(호스트 폴더 직접 연결)와 named volume(Docker가 관리) 중 선택할 수 있는데, 데이터베이스처럼 Docker가 알아서 관리하면 편한 건 named volume이 낫고, 설정 파일·로그를 호스트에서 바로 보고 싶을 땐 bind mount가 낫습니다.

5. Compose 네트워크와 서비스 이름 통신

Compose는 같은 파일에 있는 서비스끼리 자동으로 네트워크를 구성합니다. 그래서 서비스 이름으로 서로 접속할 수 있습니다. 웹 컨테이너는 db라는 이름으로 DB에 연결합니다 — 호스트가 아니라 서비스 이름이 곧 주소입니다. DB 호스트에 db라고 적기만 하면 됩니다. 포트 번호는 DB 기본 포트를 그대로 쓰고 직접 IP를 적을 필요가 없습니다.

외부에 노출할 포트는 최소화합니다. 웹 포트만 열어 두고 DB와 캐시는 내부망에만 둡니다. ports에 적지 않으면 외부에서 접근할 수 없어 보안이 훨씬 강해집니다. W29에서 다룬 Nginx를 앞단에 두면 더 좋습니다 — Nginx가 요청을 받아 웹으로 넘겨주는 구조입니다.

6. .env 파일로 비밀번호 관리

비밀번호를 YAML 파일에 직접 적으면 위험합니다. git에 올라가면 누구나 볼 수 있고 한 번 노출되면 비밀번호를 전부 바꿔야 합니다. 그래서 .env 파일을 씁니다.

# .env
DB_PASSWORD=매우강력한비밀번호

이 파일은 반드시 .gitignore에 추가합니다. 절대 git에 커밋하지 않습니다. Compose는 이 파일을 자동으로 읽고, YAML에서는 달러 기호 중괄호(${DB_PASSWORD})로 변수를 참조하면 Compose가 알아서 값을 채워 넣습니다.

환경변수로 운영과 개발을 나눌 수도 있습니다 — 운영 서버와 개발 서버에 다른 .env 파일을 두고 같은 YAML을 쓰면서 환경만 바꿀 수 있습니다. 코드는 같되 설정만 다르게 동작하는 것이 선언적 정의의 또 다른 장점입니다.

7. 운영 명령 + push-to-deploy 연계

docker compose up -d      # 전체 백그라운드 실행
docker compose down        # 전체 종료 + 네트워크 정리
docker compose logs -f web # 실시간 로그
docker compose pull        # 이미지 최신화 후 up 하면 업데이트

이 명령들을 셸 스크립트로 묶으면 배포가 자동화됩니다. W27에서 만든 push-to-deploy와 결합하면 git push를 하면 서버가 pull을 하고 compose up을 실행하는 배포 자동화 루프가 완성됩니다. 사람이 직접 치지 않아도 배포가 끝납니다.

마무리: docker compose up 한 줄의 힘

오늘 배운 것을 정리하면: docker-compose.yml 하나로 전체 스택을 선언했고, depends_onhealthcheck로 순서를 잡았으며, restart always로 재부팅에 대비하고, volume으로 데이터를 보존했고, 서비스 이름으로 내부 통신을 하며, .env로 비밀번호를 안전하게 관리합니다. 이제 손으로 하나씩 켜던 시절은 끝났습니다. 이것으로 VPS 데브옵스 시리즈가 마무리됩니다.

추천 VPS: 이 튜토리얼은 Vultr(서울 리전, 100달러 크레딧), DigitalOcean, Hetzner 어디서나 동일하게 적용됩니다.

관련 글

## Docker 볼륨과 데이터 영속성 – 실전 영상 가이드

📎 이 영상의 발표 자료

누구나 무료로 다운로드할 수 있습니다.