VPS 한 대 빌려놓고 서비스 하나 띄우려고 매뉴얼을 30분씩 읽은 적 있죠? 패키지 설치하고, 포트 열고, systemd 서비스 파일 직접 짜고, 재부팅되면 다시 켜지게까지 설정하고. 한 번 하면 끝이 아니라, 서비스 하나 더 추가할 때마다 같은 과정을 처음부터 반복해야 합니다. 이걸 docker compose YAML 파일 하나로 끝낼 수 있습니다.
문제: 전통적 서비스 배포가 느리고 위험한 이유
VPS에 Nginx 웹 서버 하나 띄운다고 가정해 봅시다. 전통적 방식이라면 이렇게 해야 합니다.
먼저 apt update && apt install nginx로 패키지를 설치합니다. 그다음 방화벽에서 80번 포트를 열어줍니다 (ufw allow 80). systemd 서비스 파일이 제대로 됐는지 확인하고, systemctl enable nginx로 재부팅 시 자동 시작을 걸어줍니다. 설정 파일을 수정하면 systemctl restart nginx로 다시 불러와야 하죠. systemd 서비스 관리에 대해 더 자세히 알고 싶다면 이 글을 참고하세요.
여기까지만 해도 5~6단계입니다. 그런데 PostgreSQL 데이터베이스를 추가해야 한다면? Redis 캐시를 붙여야 한다면? 단계가 두 배, 세 배로 늘어납니다. 그리고 각 단계마다 오타 하나가 서버를 멈춥니다. 포트 번호를 잘못 적으면 충돌이 나고, 버전을 잘못 깔면 의존성이 꼬입니다. 이런 수동 배포 방식은 규모가 커질수록 관리가 불가능해집니다.
더 큰 문제는 재현성입니다. 개발 서버에서는 잘 되는데 프로덕션 서버에서는 안 된다? 패키지 버전이 다르거나, 시스템 설정이 미묘하게 다르기 때문입니다. “내 서버에서는 되는데”라는 말이 DevOps 세계에서 공포의 대사인 이유가 바로 이것입니다.
해결책: docker compose YAML 5줄로 끝내기
docker compose는 이 모든 복잡함을 YAML 파일 하나로 압축합니다. 공식 문서에서도 docker compose를 “멀티 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구”라고 설명합니다. 자세한 스펙은 Docker Compose 공식 문서를 확인하세요. Nginx 웹 서버를 띄우는 전체 설정은 이렇게 5줄이면 됩니다.
services:
web:
image: nginx:alpine
ports:
- "80:80"
restart: unless-stopped
이 파일이 하는 일을 한 줄씩 뜯어보겠습니다. services:는 이 파일이 정의할 서비스들의 최상위 키입니다. 그 아래 web:은 서비스 이름입니다 — 원하는 대로 지으면 됩니다 (nginx, frontend 등). image: nginx:alpine은 사용할 Docker 이미지를 지정합니다. alpine 태그는 용량이 약 10MB로 매우 가벼운 버전입니다.
ports: - "80:80"은 포트 매핑입니다. 왼쪽 80은 호스트(VPS)의 포트, 오른쪽 80은 컨테이너 내부 포트입니다. VPS의 80번 포트로 들어오는 트래픽을 Nginx 컨테이너로 전달하라는 뜻입니다. 마지막으로 restart: unless-stopped는 재시작 정책입니다. 서버가 재부팅되거나 컨테이너가 예기치 않게 종료됐도, 사용자가 명시적으로 멈추지 않는 한 자동으로 다시 켭니다.
이제 터미널에서 딱 한 줄 실행합니다.
docker compose up -d
이 명령 하나로 compose가 알아서 다음 작업을 수행합니다. Docker Hub에서 nginx:alpine 이미지를 풀(pull)해오고, 컨테이너를 생성하고, 80번 포트를 매핑하고, 백그라운드(-d)에서 실행합니다. 5초도 안 걸립니다. 브라우저에서 VPS IP를 입력하면 바로 Nginx 기본 페이지가 뜹니다.
서비스 추가는 YAML 블록 하나 더
docker compose의 진짜 힘은 확장성에 있습니다. 나중에 PostgreSQL 데이터베이스를 추가하고 싶다면? YAML 파일에 블록 하나만 더 붙이면 됩니다.
services:
web:
image: nginx:alpine
ports:
- "80:80"
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: mysecretpassword
volumes:
- db_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
db_data:
이제 docker compose up -d를 다시 실행하면, Nginx 웹 서버와 PostgreSQL 데이터베이스가 동시에 뜹니다. 두 컨테이너는 같은 Docker 네트워크에 있으므로 서비스 이름으로 서로 통신할 수 있습니다 — Nginx 컨테이너에서 db:5432로 접속하면 PostgreSQL에 연결됩니다. 별도 네트워크 설정 없이도 됩니다.
volumes: 키워드는 데이터 영속성을 보장합니다. 컨테이너를 지워도 db_data 볼륨에 저장된 데이터는 남아 있어서, 새 버전의 PostgreSQL로 교체해도 데이터가 유지됩니다. 이게 restart 정책과 volumes 마운트를 함께 쓰는 이유입니다 — 운영 환경에서 데이터 손실 없이 자동 복구되는 구조가 만들어집니다. 실제 프로덕션 수준의 스택 구성이 궁금하다면 Docker Compose 프로덕션 스택 구축 가이드를 확인하세요.
실전 팁: Caddy 역방향 프록시로 HTTPS 한 번에
Nginx 대신 Caddy를 쓰면 HTTPS 인증서 발급까지 자동화할 수 있습니다. Caddy는 Let’s Encrypt 인증서를 자동으로 발급받고 갱신하므로, 도메인만 연결하면 별도 설정 없이 HTTPS가 활성화됩니다. SSL 인증서 자동 갱신에 대한 자세한 내용은 Let’s Encrypt SSL 무료 발급 자동갱신 튜토리얼을 참고하세요.
실제 Caddyfile은 이렇게 간단합니다. 도메인 하나만 적으면 됩니다.
example.com {
reverse_proxy web:80
}
app.example.com {
reverse_proxy web2:3000
}
reverse_proxy 뒤에 오는 web:80은 docker compose에서 정의한 서비스 이름과 포트입니다. Caddy 컨테이너가 같은 네트워크에 있기만 하면, DNS 설정 없이 서비스 이름으로 바로 통신할 수 있습니다. 도메인을 추가할 때마다 이런 블록을 하나씩 붙이면 되고, Caddy가 알아서 인증서를 발급받아 적용합니다.
Redis 캐시, Minio 오브젝트 스토리지, Portainer 관리 UI까지 — 필요한 서비스가 있으면 YAML에 블록을 추가하기만 하면 됩니다. 모든 서비스 설정이 한 파일에 들어 있으니, 서버를 바꾸거나 개발 환경을 복제할 때 이 파일만 복사하면 됩니다. “내 서버에서는 되는데” 문제가 사라지는 것입니다.
docker compose vs docker run: 언제 뭘 써야 하나
컨테이너 하나만 잠깐 띄울 때는 docker run이 빠릅니다. 하지만 서비스가 2개 이상이거나, 재부팅 시 자동 시작이 필요하거나, 팀원과 설정을 공유해야 한다면 docker compose가 압도적으로 좋습니다. 실제로 같은 PostgreSQL 데이터베이스를 docker run으로 띄우려면 이렇게 긴 명령어를 매번 입력해야 합니다.
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=mysecretpassword \
-e POSTGRES_USER=myuser \
-e POSTGRES_DB=mydb \
-v pg_data:/var/lib/postgresql/data \
-p 5432:5432 \
--restart unless-stopped \
postgres:16-alpine
이 명령어 한 줄에 이미 7개의 플래그가 들어 있습니다. 서비스가 3개, 4개로 늘어나면 어떻게 될까요? 터미널 히스토리를 뒤지거나, 어딘가에 적어둔 메모를 찾아야 합니다. 반면 docker compose는 모든 설정이 YAML 파일 하나에 정리되어 있어서, 어떤 서버에서든 docker compose up -d 한 줄로 동일한 환경이 복제됩니다.
docker run docker compose
───────────────────────────────────── ────────────────────────────────────
컨테이너 1개 여러 컨테이너 동시 관리
명령어에 모든 설정 포함 YAML 파일에 설정 분리
재시작 정책 매번 지정 필요 파일 한 번 정의하면 영구 적용
팀원과 설정 공유 어려움 git으로 버전 관리 가능
서비스 간 네트워크 수동 구성 자동 네트워크 생성
YAML 파일은 버전 관리(git)에 넣을 수 있어서, 인프라 변경 이력이 코드처럼 추적됩니다. Infrastructure as Code의 가장 쉬운 시작점이 docker compose입니다.
VPS 비용을 아끼면서 여러 서비스를 운영하고 싶다면, 다음에 서비스 하나 더 띄울 때 docker run 한 줄짜리 대신 docker-compose.yml 파일로 시작해 보세요. 처음엔 2분 더 걸리지만, 두 번째 서비스부터는 시간이 절약되고 세 번째부터는 실수가 사라집니다. 서버 운영이 훨씬 단순해진 걸 느낄 겁니다.



















