DockerDocker 기초 · 5기초

레지스트리와 배포 — 이미지 공유와 프로덕션

Docker레지스트리DockerHub배포CICD

이미지 레지스트리

flowchart LR
    DEV["로컬\n개발 환경"]
    REG["레지스트리\n(Docker Hub / GHCR)"]
    SERVER["프로덕션\n서버"]

    DEV -->|"docker push"| REG
    SERVER -->|"docker pull"| REG

Docker Hub에 이미지 푸시

# Docker Hub 로그인
docker login

# 이미지 태그 (계정명/이미지명:태그)
docker tag my-app username/my-app:latest
docker tag my-app username/my-app:1.0.0

# 푸시
docker push username/my-app:latest
docker push username/my-app:1.0.0

# 서버에서 풀
docker pull username/my-app:1.0.0

GitHub Container Registry (GHCR)

# GitHub 토큰으로 로그인
echo $GITHUB_TOKEN | docker login ghcr.io -u username --password-stdin

# 이미지 태그 (ghcr.io/계정명/이미지명:태그)
docker tag my-app ghcr.io/username/my-app:latest

# 푸시
docker push ghcr.io/username/my-app:latest

GitHub Actions CI/CD

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Docker Hub 로그인
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: 이미지 빌드 및 푸시
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            username/my-app:latest
            username/my-app:${{ github.sha }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest

    steps:
      - name: 서버 배포
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull username/my-app:latest
            docker compose -f /app/docker-compose.prod.yml up -d

프로덕션 docker-compose.yml

# docker-compose.prod.yml
services:
  app:
    image: username/my-app:latest  # 빌드 대신 이미지 사용
    restart: always
    env_file: .env.prod
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`example.com`)"

  db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
    env_file: .env.prod
    # 포트 노출하지 않음 (외부에서 직접 접근 불가)

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - certs:/etc/letsencrypt

volumes:
  postgres_data:
  certs:

헬스체크와 자동 재시작

# Dockerfile에 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1
# docker-compose.yml
services:
  app:
    restart: unless-stopped  # 수동 중지 제외 항상 재시작
    # on-failure: 에러 시만 재시작
    # always: 항상 재시작

이미지 정리

# 사용하지 않는 이미지, 컨테이너, 볼륨, 네트워크 정리
docker system prune

# 이미지까지 포함
docker system prune -a

# 사용 중인 디스크 확인
docker system df

정리

명령역할
docker tag이미지에 태그 추가
docker push레지스트리에 업로드
docker pull레지스트리에서 다운로드
restart: always자동 재시작
HEALTHCHECK컨테이너 상태 확인
docker system prune미사용 리소스 정리

다음 편에서는 Docker 실전 프로젝트 — 전체 스택을 Docker로 구성하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.