도커 첫걸음 5편 — Docker Compose로 팀과 협업하기: compose.yaml 하나로 온보딩을 한 줄로
실제 앱은 API 서버, 데이터베이스, 캐시가 함께 돕니다. docker run을 세 번, 긴 옵션과 함께 치는 대신 compose.yaml 파일 하나에 적고 docker compose up 한 줄로 띄웁니다. 컨테이너끼리는 localhost가 아니라 서비스 이름으로 통신한다는 핵심 개념, DB가 준비될 때까지 기다리는 healthcheck, 비밀번호를 .env로 분리하는 법, 코드를 고치면 바로 반영되는 개발 모드를 익힙니다. 이어서 이미지를 GitHub Container Registry로 공유하는 흐름, GitHub Actions로 맥·서버 겸용 이미지를 자동 빌드하는 워크플로, 저장소에 무엇을 커밋할지 정하는 팀 규칙과 README 템플릿까지 — 시리즈를 협업으로 마무리합니다.
Compose는 2편의 어떤 도구(Docker Desktop, OrbStack, Rancher Desktop, Colima, WSL2 + Docker Engine)에도 포함되어 있거나 함께 설치했습니다. docker compose version이 나오면 준비된 것입니다. 옛 글에 나오는 하이픈 붙은 docker-compose는 구버전 명령이고, 지금은 띄어 쓴 docker compose를 씁니다.
1. 첫 compose.yaml — API + DB
4편의 hello-api 폴더에 compose.yaml을 만듭니다. 위의 긴 명령 네 줄이 이렇게 바뀝니다.
비밀번호는 파일에 직접 쓰지 않고 같은 폴더의 .env 파일에서 읽습니다. Compose는 .env를 자동으로 읽어 ${DB_PASSWORD} 자리에 넣습니다.
bash
echo"DB_PASSWORD=secret" > .env
이제 띄웁니다.
bash
docker compose up -d --build
이미지 빌드, 네트워크 생성, 볼륨 생성, DB 시작, DB가 준비될 때까지 대기, API 시작 — 네 줄이 하던 일을 한 줄이 순서대로 해냅니다.
compose.yaml 항목
docker run으로 치면
뜻
services: 아래 이름 (api, db)
--name
서비스 이름. 다른 컨테이너가 이 이름으로 접속한다
build: .
docker build .
현재 폴더의 Dockerfile로 이미지를 만든다
image: postgres:17
run 끝의 이미지 이름
만들어진 이미지를 받아 쓴다
ports:
-p
내 쪽:컨테이너 쪽 (3편과 같은 규칙)
environment:
-e
환경변수
volumes:
-v
볼륨·폴더 연결. 맨 아래 volumes:는 이름 붙은 볼륨 선언
depends_on + healthcheck
(없음)
DB가 진짜 준비된 뒤에 API를 시작
(자동)
docker network create + --network
프로젝트마다 전용 네트워크를 자동으로 만든다
2. 가장 중요한 개념 — 컨테이너끼리는 서비스 이름으로 부른다
위 DATABASE_URL을 다시 보세요. @localhost:5432가 아니라 @db:5432입니다. 초보자가 Compose에서 가장 많이 막히는 지점이 바로 여기입니다.
💻 내 브라우저 localhost:8000
→
api 컨테이너 ports "8000:8000"
→
db 컨테이너 db:5432 (서비스 이름)
내 컴퓨터 → 컨테이너: localhost:내쪽포트 (ports로 뚫은 통로)
컨테이너 → 컨테이너: 서비스이름:컨테이너쪽포트 (Compose 네트워크 안의 이름)
컨테이너 안에서 localhost는 그 컨테이너 자신입니다. api 컨테이너 안에서 localhost:5432를 찾으면 api 컨테이너 안에는 DB가 없으니 Connection refused가 납니다.
그래서 db 서비스에는 ports:가 아예 없어도 api는 DB에 잘 접속합니다. ports는 내 컴퓨터에서 DB 도구로 들여다보고 싶을 때만 추가하면 됩니다.
⚠️
depends_on만으로는 부족합니다.depends_on: [db]만 쓰면 DB 컨테이너가 시작된 직후 API가 뜨는데, PostgreSQL은 시작하고 몇 초가 지나야 접속을 받습니다. 그 사이 API가 접속하려다 실패하고 꺼집니다. 위 예시처럼 healthcheck로 "준비됨"을 정의하고 condition: service_healthy로 기다리게 하세요.
3. 매일 쓰는 Compose 명령
docker compose 명령은 compose.yaml이 있는 폴더에서 실행합니다.
하고 싶은 일
명령
메모
전부 띄우기
docker compose up -d
코드가 바뀌었으면 --build 추가
상태 보기
docker compose ps
이 프로젝트의 컨테이너만 보인다
로그 보기
docker compose logs -f api
서비스 이름 생략하면 전체 로그를 섞어서
컨테이너 안에서 명령
docker compose exec db psql -U postgres
3편의 docker exec와 같다. 이름 대신 서비스 이름
하나만 재시작
docker compose restart api
전부 내리기
docker compose down
컨테이너·네트워크 삭제. 볼륨(데이터)은 남는다
데이터까지 초기화
docker compose down -v
볼륨까지 삭제. DB가 텅 빈 상태로 돌아간다
설정 확인
docker compose config
.env 값이 채워진 최종 설정을 출력. 오타 찾기에 좋다
💡
"DB를 처음 상태로 돌리고 싶어요"는 docker compose down -v && docker compose up -d 한 줄입니다. 로컬에 PostgreSQL을 직접 설치했다면 반나절 걸릴 일입니다. 이 가벼움이 팀 전체가 같은 초기 데이터로 테스트할 수 있게 해 줍니다.
4. 개발 모드 — 코드를 고치면 바로 반영되게
4편에서는 코드를 고칠 때마다 다시 빌드했습니다. 개발 중에는 번거롭죠. 소스 폴더를 컨테이너에 연결(바인드 마운트)하고 서버를 자동 재시작 모드로 띄우면 재빌드가 필요 없습니다. 개발 전용 설정은 compose.override.yaml에 따로 적는 것이 관례입니다 — Compose는 이 파일이 있으면 compose.yaml 위에 자동으로 덧씌웁니다.
docker compose up -d로 다시 띄운 뒤 main.py의 메시지를 고치고 저장하면, 몇 초 안에 curl localhost:8000 결과가 바뀝니다. 2편에서 OrbStack의 장점으로 "파일 공유가 빠르다"를 꼽은 이유가 이 개발 방식 때문입니다.
🔄
docker compose watch. 최신 Compose에는 파일 변경을 감시해 컨테이너에 동기화하거나 자동 재빌드하는 develop.watch 설정과 docker compose watch 명령도 있습니다. 바인드 마운트가 느린 환경(윈도우 드라이브, 파일이 아주 많은 프로젝트)에서 대안이 됩니다. 처음에는 위의 바인드 마운트 방식이 이해하기 쉽습니다.
5. 이미지 공유 — 레지스트리로 push, 동료는 pull
지금까지는 팀원 각자가 build: .로 이미지를 직접 빌드했습니다. 소스 코드가 있는 개발자에게는 이게 가장 간단합니다. 하지만 이런 경우에는 이미 만든 이미지를 공유하는 편이 낫습니다.
이미지에 레지스트리 주소가 포함된 이름을 붙이고 올립니다. 이미지 이름은 소문자만 쓸 수 있습니다.
bash
docker tag hello-api:0.2 ghcr.io/우리조직/hello-api:0.2
docker push ghcr.io/우리조직/hello-api:0.2
맥과 x86 서버를 모두 지원해야 하면 4편의 buildx 멀티 플랫폼 빌드로 --push합니다. 동료의 compose.yaml에서는 build: 대신 image:로 받아 씁니다.
yaml
services:api:image:ghcr.io/우리조직/hello-api:0.2
bash
docker compose pull
docker compose up -d
🔒
GHCR 패키지는 기본적으로 비공개입니다. 동료도 읽기 권한(read:packages) 토큰으로 docker login ghcr.io를 해야 받을 수 있고, 조직의 패키지 설정에서 저장소나 팀에 접근 권한을 줘야 합니다. 토큰은 비밀번호와 같으니 채팅방에 붙여 넣지 마세요.
6. 사람 대신 CI가 빌드하게 — GitHub Actions
손으로 빌드하고 push하면 누군가는 잊고, 누군가는 맥에서 arm64로만 올립니다(4편의 함정). 가장 좋은 방법은 main 브랜치에 코드가 합쳐질 때마다 CI가 자동으로 빌드해 올리는 것입니다. 저장소에 .github/workflows/docker.yml을 추가합니다.
이 워크플로는 ① GitHub이 자동으로 주는 GITHUB_TOKEN으로 GHCR에 로그인하고(별도 토큰 불필요) ② amd64·arm64 두 아키텍처를 빌드해 ③ 커밋 해시(sha-3f9a2c1), 브랜치 이름(main), 버전 태그(v1.4.2를 push하면 1.4.2)를 붙여 올리고 ④ 빌드 캐시를 GitHub에 저장해 다음 빌드를 빠르게 합니다. 4편에서 배운 태그 습관과 멀티 플랫폼이 사람 손을 거치지 않고 지켜집니다.
ℹ️
액션 버전(@v3, @v6 등)은 이 글을 쓴 2026년 9월 기준 주요 버전입니다. 각 액션의 GitHub 저장소에서 최신 주요 버전을 확인해 올려 쓰세요. 이미지 레지스트리는 이름에 대문자를 허용하지 않으니, 조직·저장소 이름에 대문자가 있다면 images: 값을 소문자 이름(예: ghcr.io/myteam/hello-api)으로 직접 적으세요.
7. 팀 규칙 — 저장소에 무엇을 커밋하나
도커 협업의 약속은 2편에서 말했듯 "같은 도구"가 아니라 "같은 파일"입니다. 저장소에 들어갈 것과 들어가면 안 될 것을 정해 둡니다.
파일
커밋?
이유
Dockerfile
✅ 커밋
이미지 레시피. 코드와 함께 버전 관리
compose.yaml
✅ 커밋
팀 공통 실행 환경
compose.override.yaml
팀 합의
모두가 같은 개발 모드를 쓰면 커밋, 개인 취향이 섞이면 .gitignore
.dockerignore
✅ 커밋
이미지에 들어가면 안 되는 것 목록 (4편)
.env.example
✅ 커밋
필요한 변수 이름과 예시값. "이걸 복사해 .env를 만드세요"
.env
❌ 절대 금지
실제 비밀번호·키. .gitignore와 .dockerignore 양쪽에 등록
.env.example은 이렇게 생겼습니다.
bash
# 복사해서 .env로 만든 뒤 값을 채우세요: cp .env.example .env
DB_PASSWORD=change-me
그리고 팀 규칙 몇 가지를 README에 적어 둡니다.
버전 고정
postgres:17처럼 태그를 명시. latest 금지. 라이브러리 버전도 고정 파일(requirements.txt, package-lock.json)로.
컨테이너는 일회용
컨테이너 안에 들어가 손으로 설치한 것은 없는 것. 필요하면 Dockerfile에 적고 PR로.
데이터는 재현 가능하게
초기 데이터는 SQL 스크립트나 시드 명령으로. down -v로 지워도 다시 만들 수 있어야 한다.
이미지는 CI가
공유용 이미지는 사람이 아니라 CI가 빌드하고 push. 태그는 커밋 해시 + 버전.
8. 복사해서 쓰는 README 온보딩 절
이 시리즈의 목표였던 "신규 팀원이 한 줄로 시작"을 README로 옮기면 이렇습니다. 여러분의 프로젝트에 맞게 고쳐 쓰세요.
markdown
## 로컬 개발 환경### 1. 준비물- 도커 도구 하나 (맥: OrbStack 또는 Rancher Desktop / 윈도우: Rancher Desktop 또는 WSL2 + Docker Engine)
- 설치 확인: `docker compose version`### 2. 실행```bash
cp .env.example .env
docker compose up -d --build
```- API: http://localhost:8000/docs
### 3. 자주 쓰는 명령
| 할 일 | 명령 |
|---|---|
| 로그 | `docker compose logs -f api` |
| DB 접속 | `docker compose exec db psql -U postgres` |
| 전부 내리기 | `docker compose down` |
| DB 초기화 | `docker compose down -v && docker compose up -d` |
9. 시리즈를 마치며 — 전체 지도
다섯 편을 한 장으로 접으면 이렇습니다.
1편 이미지·컨테이너·레지스트리·볼륨
→
2편 라이선스 걱정 없는 도구 설치
→
3편 docker run으로 남의 이미지 실행
→
4편 Dockerfile로 내 이미지 build
→
5편 Compose + 레지스트리로 협업
🧠
5편 요약
· compose.yaml은 긴 docker run 명령들을 파일로 적은 것. docker compose up -d 한 줄로 전부
· 컨테이너끼리는 서비스 이름(db:5432)으로, 내 컴퓨터에서는 localhost:포트로
· DB 대기는 healthcheck + condition: service_healthy, 비밀은 .env(커밋 금지) + .env.example(커밋)
· 개발 중엔 소스 폴더를 볼륨으로 연결해 재빌드 없이
· 공유용 이미지는 GHCR 같은 레지스트리로, 빌드는 GitHub Actions가 멀티 플랫폼으로
도커를 어렵게 만드는 것은 명령어의 개수가 아니라 무엇이 어디에 있는지에 대한 그림입니다. 이미지는 틀, 컨테이너는 붕어빵, 볼륨은 냉장고, 레지스트리는 마트, compose.yaml은 팀의 약속. 이 그림만 있으면 처음 보는 옵션도 "아, 이건 내 쪽:컨테이너 쪽이구나" 하고 읽힙니다. 이제 팀 저장소에 compose.yaml 하나를 추가하는 것으로 시작해 보세요.