회사에서 자체 서버를 운영하다 보면 서비스가 하나둘 늘어난다. 처음에는 docker run 으로 하나씩 띄우다가, 어느 순간 컨테이너가 10개를 넘기면서 관리가 안 되기 시작했다. 어떤 컨테이너가 어떤 포트를 쓰는지, 볼륨은 어디에 마운트했는지, 환경변수는 뭘 넣었는지… 결국 Docker Compose로 전부 옮기고 나서야 숨통이 트였다.
기본 구조 잡기
프로젝트 루트에 docker-compose.yml 하나면 된다. 우리 팀은 보통 이런 식으로 구성한다:
/srv/
├── docker-compose.yml
├── .env
├── nginx/
│ └── conf.d/
├── data/
│ ├── postgres/
│ ├── redis/
│ └── minio/
└── logs/
docker-compose.yml 기본 뼈대는 이렇다:
version: "3.8"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./logs/nginx:/var/log/nginx
restart: unless-stopped
depends_on:
- app
app:
image: your-app-image:latest
env_file:
- .env
volumes:
- ./data/uploads:/app/uploads
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
restart: unless-stopped
핵심은 .env 파일에 민감한 정보를 빼두는 거다. docker-compose.yml은 Git에 올리되, .env는 .gitignore에 넣어야 한다.
네트워크 분리
서비스가 많아지면 네트워크를 분리하는 게 좋다. DB는 외부에서 직접 접근할 필요가 없으니까:
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
services:
nginx:
networks:
- frontend
- backend
app:
networks:
- backend
db:
networks:
- backend
internal: true로 설정하면 해당 네트워크의 컨테이너는 외부 인터넷에 접근할 수 없다. DB 서버가 인터넷에 나갈 이유는 없으니까 이렇게 막아두는 게 보안상 좋다.
환경변수 관리
.env 파일 하나로 관리하면 편하다:
# .env
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=your-password
REDIS_URL=redis://redis:6379
APP_SECRET=your-secret-key
compose 파일에서 ${변수명} 으로 참조하면 자동으로 치환된다. 개발/스테이징/프로덕션 환경별로 .env.dev, .env.prod 같이 나눠두고 --env-file 옵션으로 지정하는 방법도 있다:
docker compose --env-file .env.prod up -d
볼륨과 데이터 백업
데이터가 날아가면 큰일이니까 볼륨 관리에 신경을 써야 한다. Named volume 대신 bind mount를 쓰면 백업이 쉽다:
# 매일 새벽 3시에 DB 백업
0 3 * * * docker exec postgres pg_dump -U admin myapp | gzip > /backup/db_$(date +\%Y\%m\%d).sql.gz
crontab에 이 한 줄 넣어두면 된다. 오래된 백업은 주기적으로 정리해야 디스크가 안 찬다:
# 30일 지난 백업 삭제
find /backup -name "db_*.sql.gz" -mtime +30 -delete
로그 관리
Docker 기본 로그 드라이버는 json-file인데, 제한을 안 걸면 로그가 무한히 쌓인다. compose 파일에서 전역으로 설정하거나 서비스별로 지정할 수 있다:
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
이렇게 하면 로그 파일 하나당 10MB, 최대 3개까지만 유지한다. 디스크가 꽉 차서 서버가 먹통이 되는 사고를 예방할 수 있다.
업데이트 전략
서비스를 업데이트할 때는 무중단으로 하고 싶다. 간단한 방법은:
# 이미지 새로 받고
docker compose pull app
# 해당 서비스만 재시작
docker compose up -d --no-deps app
--no-deps 옵션을 안 붙이면 의존하는 서비스까지 전부 재시작되니 주의. 진짜 무중단이 필요하면 Blue-Green 배포를 고려해야 하지만, 사내 서비스 정도면 이 정도로 충분하다.
모니터링 팁
docker compose ps로 전체 상태를 볼 수 있고, 리소스 사용량은 docker stats로 확인한다. 특정 서비스 로그를 실시간으로 보려면:
docker compose logs -f --tail=100 app
서비스가 죽었을 때 알림을 받고 싶으면 간단한 헬스체크 스크립트를 만들어두는 것도 방법이다. restart: unless-stopped를 걸어두면 대부분 자동으로 살아나지만, 아예 복구가 안 되는 경우도 있으니까.
Docker Compose 하나로 사내 서비스를 깔끔하게 관리할 수 있다. 처음 세팅할 때 좀 귀찮지만, 한번 잡아두면 서버 이전이나 복구할 때 docker compose up -d 한 줄이면 끝이다.