“로컬에서는 되는데 서버에서는 안 돼요”의 80%는 환경 설정 차이에서 옵니다. PHP 버전이 다르고, MySQL 설정이 다르고, 누군가의 맥에만 깔린 라이브러리가 있어서… Docker Compose는 이런 환경 불일치를 docker-compose.yml 한 파일로 통일해 줍니다.
이 글에서는 Docker Compose의 기본 문법과, 실제 바로 써먹을 수 있는 WordPress + MySQL + phpMyAdmin 구성을 다룹니다. 예제는 그대로 복사해서 로컬 PC나 Ubuntu 서버에서 테스트할 수 있습니다.
Docker Compose란?
여러 개의 Docker 컨테이너를 YAML 파일 하나로 선언적으로 정의하고 한 번에 실행하는 도구입니다. docker run을 5번 치는 대신 docker compose up 하나면 끝나죠.
- 선언적 구성 — 원하는 상태를 YAML로 정의, 명령형 스크립트 불필요
- 네트워크 자동 구성 — 같은 compose 안의 서비스끼리는 서비스명으로 통신
- 볼륨·환경변수 관리 —
.env파일과 연동해 비밀값 분리 - 버전 관리 가능 — YAML 자체를 Git에 올려 팀원과 환경 공유
참고: 예전의 docker-compose(하이픈 붙은 별도 바이너리)는 docker compose (띄어쓰기, Docker CLI 플러그인)로 통합되었습니다. Docker Desktop 또는 최신 Docker Engine을 쓰면 별도 설치가 필요 없습니다.
docker-compose.yml 핵심 문법
services:
웹:
image: nginx:alpine # 사용할 이미지
ports:
- "8080:80" # 호스트포트:컨테이너포트
volumes:
- ./html:/usr/share/nginx/html # 호스트경로:컨테이너경로
environment:
- TZ=Asia/Seoul
depends_on:
- 디비 # 디비가 먼저 뜬 뒤에 시작
restart: unless-stopped
디비:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASS}
volumes:
- db-data:/var/lib/mysql # 명명된 볼륨(영속성)
volumes:
db-data:
| 키 | 역할 |
|---|---|
services | 실행할 컨테이너들을 정의 |
image | 사용할 Docker 이미지 이름과 태그 |
build | Dockerfile로 이미지를 직접 빌드 |
ports | 포트 매핑 (호스트:컨테이너) |
volumes | 파일 공유/영속화 (바인드 마운트 or 명명된 볼륨) |
environment | 컨테이너에 주입할 환경변수 |
depends_on | 시작 순서 제어 (준비 상태까지는 보장하지 않음) |
restart | 재시작 정책 (no / always / unless-stopped / on-failure) |
실전 — WordPress + MySQL + phpMyAdmin 한 번에
로컬에서 WordPress 테마·플러그인 개발할 때 가장 많이 쓰는 스택입니다. 아래 2개 파일만 있으면 1분 안에 접속 가능한 환경이 뜹니다.
.env 파일
MYSQL_ROOT_PASSWORD=supersecret
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=wppass
docker-compose.yml
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:latest
depends_on:
db:
condition: service_healthy
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
volumes:
- ./wp-content:/var/www/html/wp-content # 테마/플러그인 개발용
phpmyadmin:
image: phpmyadmin:latest
depends_on:
- db
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: db
PMA_USER: root
PMA_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
db-data:
실행
# 백그라운드로 시작
docker compose up -d
# 로그 실시간 확인
docker compose logs -f wordpress
# 상태 확인
docker compose ps
브라우저에서 http://localhost:8080 (워드프레스) / http://localhost:8081 (phpMyAdmin) 접속하면 바로 씁니다. 중지할 땐 docker compose down. 데이터까지 완전 초기화는 docker compose down -v.
자주 쓰는 명령어 정리
# 전체 서비스 시작 / 중지
docker compose up -d
docker compose down
docker compose restart
# 특정 서비스만 재시작
docker compose restart wordpress
# 실행 중 컨테이너에 접속 (bash 진입)
docker compose exec wordpress bash
docker compose exec db mysql -u root -p
# 로그 확인 (최근 100줄)
docker compose logs --tail=100 wordpress
# 이미지 강제 재빌드 (로컬 Dockerfile 수정 후)
docker compose up -d --build
# 이미지 업데이트 (최신 pull + 재생성)
docker compose pull
docker compose up -d
# 특정 서비스만 한시적으로 실행
docker compose run --rm wordpress wp core version
# 볼륨·네트워크까지 완전 초기화
docker compose down -v --remove-orphans
운영에서 자주 놓치는 포인트 5가지
1. depends_on은 “시작 순서”만 보장
DB 컨테이너가 뜨는 것과, DB가 쿼리를 받을 준비가 된 것은 다릅니다. 초기 부팅이 10~30초 걸리기 때문에 앱이 먼저 연결을 시도하면 실패합니다. healthcheck + depends_on.condition: service_healthy 조합이 정석입니다.
2. 바인드 마운트 vs 명명된 볼륨
| 종류 | 예시 | 언제 쓸까 |
|---|---|---|
| 바인드 마운트 | ./app:/var/www/html | 개발 중 코드 실시간 반영 |
| 명명된 볼륨 | db-data:/var/lib/mysql | DB처럼 Docker가 관리할 영속 데이터 |
DB를 바인드 마운트로 잡으면 권한 문제로 시작이 안 되는 경우가 많습니다. DB 데이터는 명명된 볼륨을 권장합니다.
3. .env 파일은 반드시 .gitignore
DB 비밀번호·API 키가 들어 있습니다. 실수로 공개 저장소에 올라가면 몇 분 안에 bot이 탈취합니다. .env.example만 커밋하고 실제 값은 로컬에만 두세요.
4. 포트 충돌
로컬에서 다른 서비스가 이미 8080을 쓰고 있으면 Error: port is already allocated가 뜹니다. lsof -i :8080로 누가 잡고 있는지 확인하거나, compose에서 8090:80처럼 다른 호스트 포트로 매핑하세요.
5. 이미지 태그를 latest로 두지 마세요
운영에서는 wordpress:latest가 어느 날 메이저 업데이트되면서 DB 마이그레이션이 깨질 수 있습니다. 반드시 wordpress:6.7처럼 버전 고정 후 의도적으로 업그레이드 테스트하는 습관이 필요합니다.
FAQ
Q. 운영 서버에서도 docker compose로 띄워도 되나요?
단일 서버 + 중소 트래픽이라면 전혀 문제없습니다. 이 사이트(nalkkul.com)도 바로 이런 방식으로 여러 내부 도구를 운영합니다. 여러 서버로 확장되는 시점에서는 Swarm / Kubernetes 같은 오케스트레이션 도구를 검토하세요.
Q. docker compose에서 백업은 어떻게 하나요?
DB는 mysqldump를 cron으로 돌리고, 볼륨은 docker run --rm -v db-data:/data -v $(pwd):/backup alpine tar czf /backup/db-$(date +%F).tar.gz /data 같은 식으로 아카이빙합니다. cron 설정법은 cron 작업 스케줄링 가이드 참고.
Q. 컨테이너 하나가 메모리를 너무 많이 써요.
deploy.resources.limits로 제한할 수 있습니다. 다만 이 키는 Swarm 모드에서만 강제되므로, 단일 노드에서는 mem_limit을 쓰세요.
services:
wordpress:
image: wordpress:6.7
mem_limit: 512m
cpus: 1.0
마무리
Docker Compose는 “환경을 코드로 관리한다”는 개념을 가장 낮은 학습 비용으로 체험할 수 있는 도구입니다. 이 글의 WordPress + MySQL + phpMyAdmin 예제만 돌려 봐도, 로컬에 직접 PHP·MySQL을 설치하던 시절로 돌아가기 어려워집니다. 처음에는 공식 이미지 조합부터 시작해 보고, 익숙해지면 Dockerfile로 커스텀 이미지를 빌드하는 단계로 확장하세요.