Docker와 CI-CD — GitHub Actions에서 이미지 빌드, 테스트, 배포까지
로컬에서
docker build하고docker push하는 것을 매번 수동으로 하고 계신가요? 코드를 push하면 자동으로 이미지가 빌드되고, 테스트를 통과하면 배포까지 되는 파이프라인을 만들어봅시다.
CI/CD 파이프라인 구조
코드 Push / PR
│
↓
┌── GitHub Actions ──────────────────────┐
│ 1. 코드 체크아웃 │
│ 2. Docker Buildx 설정 │
│ 3. 레지스트리 로그인 │
│ 4. 이미지 빌드 (캐시 활용) │
│ 5. 보안 스캐닝 │
│ 6. 테스트 실행 │
│ 7. 이미지 Push │
│ 8. 배포 트리거 │
└─────────────────────────────────────────┘
기본 빌드 워크플로
# .github/workflows/docker-build.yml
name: Docker Build & Push
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
# 1. 코드 체크아웃
- name: Checkout
uses: actions/checkout@v4
# 2. Docker Buildx 설정
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 3. 레지스트리 로그인 (PR에서는 스킵)
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 4. 메타데이터 (태그/라벨 자동 생성)
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
# 5. 빌드 & Push
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
태그 전략 자동화
docker/metadata-action은 Git 이벤트에 따라 자동으로 태그를 생성합니다.
태그 생성 규칙
| Git 이벤트 | 생성되는 태그 |
|---|---|
| push to main | main, sha-a1b2c3d |
| PR #42 | pr-42 |
| tag v1.2.3 | 1.2.3, 1.2, sha-a1b2c3d |
| tag v2.0.0 | 2.0.0, 2.0, sha-a1b2c3d |
# 상세 태그 설정
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# main 브랜치 → latest
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
# SemVer 태그
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
# Git SHA (짧은 형태)
type=sha,prefix=
# 브랜치 이름
type=ref,event=branch
BuildKit 캐시 전략
GitHub Actions Cache (type=gha)
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
- GitHub Actions의 캐시 스토리지(10GB 제한)를 활용
- 설정이 가장 간단
mode=max: 모든 레이어를 캐시에 저장 (중간 스테이지 포함)
Registry Cache
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
- 레지스트리에 캐시 이미지를 별도로 저장
- 캐시 크기 제한이 레지스트리 용량에 의존
- 여러 CI 러너 간에 캐시 공유 가능
보안 스캐닝 통합
jobs:
build:
# ... (빌드 단계)
scan:
needs: build
runs-on: ubuntu-latest
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1 # CRITICAL/HIGH 발견 시 실패
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
GitHub의 Security 탭에서 취약점 결과를 확인할 수 있습니다.
테스트 통합
이미지 기반 테스트
jobs:
test:
runs-on: ubuntu-latest
services:
db:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Build test image
run: docker build --target test -t myapp:test .
- name: Run tests
run: |
docker run --rm \
--network host \
-e DB_HOST=localhost \
-e DB_PORT=5432 \
-e DB_PASSWORD=testpass \
myapp:test
Docker Compose를 사용한 통합 테스트
- name: Run integration tests
run: |
docker compose -f compose.test.yaml up -d
docker compose -f compose.test.yaml run --rm test
docker compose -f compose.test.yaml down -v
멀티 플랫폼 빌드
Apple Silicon(ARM)과 일반 서버(AMD64)를 모두 지원하는 이미지를 빌드합니다.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# QEMU 설정 (ARM 에뮬레이션)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
배포 자동화
SSH를 통한 서버 배포
deploy:
needs: [build, scan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d
docker image prune -f
Webhook 기반 배포
deploy:
needs: [build, scan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Trigger deployment
run: |
curl -X POST \
-H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"image": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"}' \
https://deploy.example.com/api/deploy
Kubernetes 배포
deploy:
needs: [build, scan]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v4
- name: Configure kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" > kubeconfig.yaml
- name: Deploy to Kubernetes
run: |
kubectl --kubeconfig=kubeconfig.yaml \
set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- name: Wait for rollout
run: |
kubectl --kubeconfig=kubeconfig.yaml \
rollout status deployment/myapp --timeout=300s
전체 파이프라인 예시
name: CI/CD Pipeline
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 1단계: 빌드 & 테스트
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 2단계: 보안 스캐닝
security-scan:
needs: build-and-test
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
# 3단계: 배포 (태그 push 시에만)
deploy:
needs: [build-and-test, security-scan]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
environment: production
steps:
- name: Deploy
run: echo "배포 로직 실행"
유용한 팁
빌드 시간 단축
# 병렬 빌드 활용
- name: Build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
# BuildKit 병렬 빌드 활성화
build-args: |
BUILDKIT_INLINE_CACHE=1
시크릿 전달
- name: Build with secrets
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
secrets: |
"npmrc=${{ secrets.NPM_TOKEN }}"
빌드 매트릭스
strategy:
matrix:
include:
- service: api
context: ./api
- service: worker
context: ./worker
- service: web
context: ./web
steps:
- name: Build ${{ matrix.service }}
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
tags: ghcr.io/myorg/${{ matrix.service }}:${{ github.sha }}
정리
- GitHub Actions에서
docker/build-push-action으로 이미지 빌드와 push를 자동화합니다. docker/metadata-action으로 Git 이벤트에 따른 태그를 자동 생성합니다. SemVer 태그, SHA, latest 등을 한 번에 관리합니다.- BuildKit 캐시(
type=gha)로 CI 환경에서도 레이어 캐시를 활용하여 빌드 시간을 줄입니다. - 빌드 후 Trivy로 보안 스캐닝을 하고, CRITICAL 취약점이 발견되면 파이프라인을 실패시킵니다.
- 배포는 SSH, Webhook, Kubernetes 등 인프라에 맞는 방식을 선택합니다.
- 전체 파이프라인은 빌드 → 스캐닝 → 배포 순서로 구성하고, 각 단계가 이전 단계의 성공에 의존하도록 합니다.
댓글 로딩 중...