최근 프로젝트에서 Django 애플리케이션을 Docker로 컨테이너화하고, GitHub Actions와 Docker Compose를 결합해 운영 배포 파이프라인 자동화를 구성했다. 이 과정에서 겪었던 시행착오와 최종 구성을 정리해본다.
Dockerfile 하나로 다양한 프로세스 관리하기
애플리케이션에는 웹 서버(Daphne), Celery, Dramatiq, MQTT Subscriber 등 다양한 백그라운드 프로세스가 있었지만, 공통적으로 사용하는 코드베이스는 동일했다.
그래서 Dockerfile을 하나로 통합하고, 실행 커맨드만 docker run
시점에 오버라이드하는 방식으로 구성했다.
예시 Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y gcc default-libmysqlclient-dev \
&& pip install --no-cache-dir -r requirements.txt
ENTRYPOINT []
CMD ["daphne", "-b", "0.0.0.0", "-p", "8000", "project.asgi:application"]
이렇게 하면 아래와 같은 방식으로 필요할 때마다 원하는 프로세스를 실행할 수 있다.
docker run --env-file .env -p 8000:8000 my-app daphne -b 0.0.0.0 -p 8000 project.asgi:application
docker run --env-file .env my-app celery -A project worker -l info
docker run --env-file .env my-app python project/mqtt_subscriber.py
Docker Compose로 각 역할 컨테이너 관리
각 프로세스를 개별 docker-compose.yml
로 관리하되, 동일한 이미지에 커맨드만 달리 지정했다.docker-compose.yml
의 예시는 아래와 같다:
services:
worker:
image: ghcr.io/org/repo:${TAG}
env_file:
- .env
command:
- celery
- -A
- project
- worker
- -l
- INFO
이때 핵심은 TAG
변수를 사용해 배포 시점에 이미지 버전을 명확히 지정할 수 있도록 한 것이다.
GitHub Actions로 이미지 자동 빌드 & GHCR 푸시
GitHub Actions를 사용해 main 브랜치에 코드가 push되면 자동으로 Docker 이미지를 빌드하고 GitHub Container Registry(GHCR)에 업로드하도록 구성했다.
버전 태그는 날짜와 커밋 해시를 조합해 재현성과 롤백 가능성을 확보했다.
on:
push:
branches: [ "main" ]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate version tag
run: echo "TAG=$(date +%Y.%m%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Login to GHCR
run: echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build and push image
run: |
docker build -t ghcr.io/org/repo:${TAG} .
docker push ghcr.io/org/repo:${TAG}
운영 배포 스크립트 작성
운영 서버에서는 deploy.sh
스크립트를 사용해
- git pull
- 각 서비스의 docker-compose pull
- 기존 컨테이너 종료
- 새 이미지로 컨테이너 재기동
순으로 배포했다.
#!/bin/bash
set -e
TAG=$1
if [ -z "$TAG" ]; then
echo "사용법: ./deploy.sh <TAG>"
exit 1
fi
BASE_DIR="/path/to/project"
COMPOSE_FILES=(
"DOCKER/asgi/docker-compose.yml"
"DOCKER/beat_scheduler/docker-compose.yml"
"DOCKER/worker/docker-compose.yml"
"DOCKER/mqtt_subscriber/docker-compose.yml"
)
cd "$BASE_DIR"
git pull
for file in "${COMPOSE_FILES[@]}"; do
SERVICE_DIR="$(dirname "$file")"
cd "$BASE_DIR/$SERVICE_DIR"
TAG=$TAG docker compose pull
TAG=$TAG docker compose down --remove-orphans
TAG=$TAG docker compose up -d
done
TAG 변수로 안전하고 일관성 있는 배포 보장
docker-compose.yml
안에서 이미지 태그에 ${TAG}
를 사용하고, 배포 시점에
TAG=20240618-abc123 docker compose up -d
형태로 실행하면 매 배포마다 정확한 이미지 버전을 일관되게 적용할 수 있다..env
파일과 쉘 환경변수를 조합해 각종 설정은 유지하면서도 이미지 버전만 유연하게 교체할 수 있었다.
배포 파이프라인 작성 관련 기타
- Docker 이미지 태그를 명확히 관리하면 운영 환경 재현성이 좋아진다.
- GitHub Actions로 이미지 빌드부터 레지스트리 푸시까지 자동화하면 배포 준비가 훨씬 편해진다.
- docker-compose에서
TAG
변수로 이미지를 관리하면 롤백과 추적이 쉬워진다. - 배포 자동화를 고민할 때는
docker compose pull → down → up -d
순서를 지키는 것이 중요하다. - GHCR에 private 이미지로 푸시할 때는 운영 서버에서도
docker login ghcr.io
가 필요하다.
결론
Docker, docker-compose, GHCR, GitHub Actions를 조합하면 소규모 서비스라도 쉽고 안정적으로 자동화 배포 파이프라인을 구축할 수 있다.
다만 운영 환경에서 보안을 위해 이미지 pull 권한 관리와 토큰 관리도 반드시 함께 고민해야 한다.