최근 프로젝트에서 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 권한 관리와 토큰 관리도 반드시 함께 고민해야 한다.

카테고리: DjangoDocker

Jay

Jay

S/W Engineer!!