이 글에는 제휴 링크(affiliate link)가 포함되어 있습니다. Vultr·DigitalOcean 가입 링크로 가입하시면 추가 비용 없이 크레딧 혜택을 받으실 수 있으며, 블로그 운영에 도움이 됩니다.
docker run 반복의 고통에서 벗어나기
서버에 웹, 데이터베이스, 캐시를 올려야 합니다. docker run 명령을 하나씩 쳐서 컨테이너를 띄우면 명령이 매우 길어집니다 — 이미지 이름, 포트 매핑, 환경변수, 볼륨까지 전부 적어야 하니까요. 서비스가 늘어날수록 명령도 늘어나고 실수가 생깁니다. 더 큰 문제는 켜지는 순서입니다: DB가 켜지기도 전에 웹이 먼저 뜨면 에러를 뿜고, 서버가 재부팅되면 새벽에 일어나 일일이 다시 켜야 합니다.

Docker Compose가 바로 이 문제를 해결합니다. 선언적 정의 파일 하나로 전체 스택을 관리합니다. 이 튜토리얼에서는 웹(Nginx) + DB(PostgreSQL) + 캐시(Redis) 프로덕션 멀티 컨테이너 스택을 한 번에 구축합니다. 입문편에서 한 단계 더 나아간 오케스트레이션 심화입니다.
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를 정의하고 test에 pg_isready 같은 명령을 넣습니다. 이 명령은 DB가 접속을 받을 준비가 되면 참을 돌려줍니다. 그리고 depends_on에 condition: 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에 적지 않으면 외부에서 접근할 수 없어 보안이 훨씬 강해집니다. 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 하면 업데이트
이 명령들을 셸 스크립트로 묶으면 배포가 자동화됩니다. push-to-deploy와 결합하면 git push를 하면 서버가 pull을 하고 compose up을 실행하는 배포 자동화 루프가 완성됩니다. 사람이 직접 치지 않아도 배포가 끝납니다.
마무리: docker compose up 한 줄의 힘
오늘 배운 것을 정리하면: docker-compose.yml 하나로 전체 스택을 선언했고, depends_on과 healthcheck로 순서를 잡았으며, restart always로 재부팅에 대비하고, volume으로 데이터를 보존했고, 서비스 이름으로 내부 통신을 하며, .env로 비밀번호를 안전하게 관리합니다. 이제 compose 파일 하나로 전체 스택을 올리고 내릴 수 있습니다.
추천 VPS: 이 튜토리얼은 Vultr(서울 리전, 신규 고객 첫 입금 최대 100달러 매칭), DigitalOcean, Hetzner 어디서나 동일하게 적용됩니다.
자주 묻는 질문 (FAQ)
Q1. docker run을 반복하는 것과 비교해 뭐가 나아지나요?
이미지·포트·환경변수·볼륨을 매번 긴 명령으로 치는 대신 docker-compose.yml 한 파일에 선언해 두고 docker compose up -d 한 줄로 전체 스택을 띄웁니다. YAML을 git에 커밋하면 서버 구성 자체가 버전 관리되고, 팀원도 같은 파일로 동일한 환경을 재현합니다.
Q2. DB보다 웹이 먼저 떠서 에러가 납니다.
컨테이너가 떴다고 DB가 접속을 받을 준비가 된 것은 아닙니다. db 서비스에 healthcheck(pg_isready)를 정의하고 web의 depends_on에 condition: service_healthy를 적으면, DB가 건강해질 때까지 웹이 기다립니다.
Q3. 서버가 재부팅되면 컨테이너를 다시 켜야 하나요?
각 서비스에 restart: always를 적어 두면 서버가 꺼졌다 켜져도 컨테이너가 자동으로 다시 올라옵니다.
Q4. named volume과 bind mount 중 뭘 써야 하나요?
데이터베이스처럼 Docker가 알아서 관리하면 편한 데이터는 named volume이 낫고, 설정 파일이나 로그를 호스트에서 바로 열어보고 싶을 때는 bind mount가 낫습니다.
관련 글
- PostgreSQL VPS 배포 + 백업 자동화 완벽 가이드: 프로덕션 DB 직접 운영 (2026)
- Docker Compose 다중 컨테이너 운영 완벽 가이드 2026: VPS에서 웹+DB+캐시까지
- k3s 경량 Kubernetes로 VPS 미니 클러스터 구축: 단일 노드 시작 완벽 가이드 (2026)



















