코드를 Push하면 Docker 이미지가 자동으로 빌드되고, 테스트를 통과한 뒤 프로덕션에 배포됩니다. 이 흐름을 GitHub Actions로 구성하는 방법을 단계별로 설명합니다. 단순히 "돌아가는" 파이프라인이 아니라, 캐싱으로 빌드 시간을 줄이고, OIDC로 보안을 강화하고, 이미지 스캔으로 취약점을 차단하는 운영 수준의 워크플로우를 만듭니다.
핵심 요약
- GitHub Actions에서 Docker 이미지를 빌드하려면
docker/build-push-action을 사용합니다. BuildKit 기반으로 Multi-platform 빌드와 레이어 캐싱을 지원합니다. - AWS ECR에 Push할 때는 OIDC 인증을 사용하면 장기 Access Key 없이 임시 자격 증명으로 접근할 수 있습니다.
- 레이어 캐싱(
cache-from/cache-to)을 적용하면 빌드 시간을 50~80% 단축할 수 있습니다. - 이미지 빌드 후 취약점 스캔을 추가하면 알려진 CVE가 포함된 이미지가 배포되는 것을 차단할 수 있습니다.
- 전체 흐름: 코드 Push → 이미지 빌드 → 취약점 스캔 → ECR Push → ECS 배포
1. 전체 파이프라인 구조
3명이 백엔드 API를 개발하는 팀을 가정합니다. 코드는 GitHub에, 컨테이너 이미지는 AWS ECR에, 서비스는 ECS Fargate에서 운영합니다.
이 팀이 원하는 CI/CD 흐름은 다음과 같습니다.
- 개발자가 feature 브랜치에서 PR을 생성합니다.
- PR 이벤트로 이미지 빌드 + 테스트가 자동 실행됩니다.
- PR이 merge되면 main 브랜치에서 프로덕션 이미지를 빌드합니다.
- 빌드된 이미지를 ECR에 Push합니다.
- ECS 서비스를 새 이미지로 업데이트합니다.
2. 사전 준비
2.1 필요한 AWS 리소스
| 리소스 | 용도 | 비고 |
|---|---|---|
| ECR Repository | Docker 이미지 저장소 | 리전당 1개 생성 |
| ECS Cluster + Service | 컨테이너 실행 환경 | Fargate 또는 EC2 |
| IAM OIDC Provider | GitHub Actions 인증용 | GitHub의 OIDC 엔드포인트 등록 |
| IAM Role | ECR Push + ECS Deploy 권한 | OIDC 신뢰 정책 연결 |
2.2 ECR Repository 생성
aws ecr create-repository \
--repository-name my-api \
--region ap-northeast-2 \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=KMS
scanOnPush=true를 설정하면 이미지가 Push될 때 자동으로 취약점 스캔이 실행됩니다. CI 단계에서 별도 스캔을 추가하는 것과 중복처럼 보이지만, 두 레이어를 모두 적용하는 것이 방어 깊이(Defense in Depth) 관점에서 안전합니다.
2.3 OIDC Provider 구성
GitHub Actions에서 AWS 리소스에 접근할 때 Access Key를 Secret에 저장하는 대신 OIDC를 사용합니다.
# GitHub OIDC Provider 등록
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
OIDC를 사용하면 장기 자격 증명이 존재하지 않으므로 키 유출 위험이 구조적으로 제거됩니다. GitHub Actions가 실행될 때마다 임시 토큰을 발급받고, 작업이 끝나면 토큰이 만료됩니다.
2.4 IAM Role 생성
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-api:ref:refs/heads/main"
}
}
}
]
}
Condition의 sub 필드가 중요합니다. repo:my-org/my-api:ref:refs/heads/main으로 설정하면 main 브랜치에서 실행되는 워크플로우만 이 Role을 사용할 수 있습니다. PR이나 다른 브랜치에서는 접근이 차단됩니다.
sub 조건을 repo:my-org/my-api:*로 설정하면 해당 저장소의 모든 브랜치, 모든 이벤트에서 Role을 사용할 수 있습니다. fork한 외부 기여자의 PR에서도 접근 가능해지므로 반드시 범위를 제한해야 합니다.IAM Role에 연결할 정책:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
"Resource": "arn:aws:ecr:ap-northeast-2:123456789012:repository/my-api"
},
{
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
}
]
}
3. Dockerfile 작성 (Multi-stage Build)
운영 환경에서는 Multi-stage Build를 사용하여 최종 이미지 크기를 줄이고, 빌드 도구가 프로덕션 이미지에 포함되지 않도록 합니다.
# Stage 1: 의존성 설치 및 빌드
FROM node:20-alpine AS builder
WORKDIR /app
# 의존성 파일만 먼저 복사 (레이어 캐싱 활용)
COPY package.json package-lock.json ./
RUN npm ci --only=production && cp -R node_modules prod_modules
RUN npm ci
# 소스 복사 및 빌드
COPY . .
RUN npm run build
# Stage 2: 프로덕션 이미지
FROM node:20-alpine AS production
# 보안: root가 아닌 사용자로 실행
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
WORKDIR /app
# 프로덕션 의존성만 복사
COPY --from=builder /app/prod_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
# 불필요한 권한 제거
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/main.js"]
왜 Multi-stage Build인가
| 항목 | 단일 Stage | Multi-stage |
|---|---|---|
| 이미지 크기 | ~900MB (개발 도구 포함) | ~150MB (런타임만) |
| 공격 표면 | 빌드 도구, 소스 코드 노출 | 실행에 필요한 것만 포함 |
| 빌드 캐싱 | 소스 변경 시 전체 재빌드 | 의존성 레이어 캐싱 가능 |
package.json을 먼저 복사하고 npm ci를 실행하는 이유는 레이어 캐싱 때문입니다. 소스 코드가 변경되어도 package.json이 같으면 의존성 설치 레이어를 재사용합니다.
4. 기본 워크플로우 작성
4.1 전체 워크플로우 파일
# .github/workflows/docker-deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
AWS_REGION: ap-northeast-2
ECR_REPOSITORY: my-api
ECS_CLUSTER: prod-cluster
ECS_SERVICE: api-service
jobs:
build:
name: Build & Push Docker Image
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC 인증
contents: read # 코드 체크아웃
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Docker meta (태그 생성)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr-login.outputs.registry }}/${{ env.ECR_REPOSITORY }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=ref,event=pr
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- 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
platforms: linux/amd64
scan:
name: Vulnerability Scan
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ needs.build.outputs.image-tag }}
format: table
exit-code: '1'
severity: CRITICAL,HIGH
deploy:
name: Deploy to ECS
needs: [build, scan]
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
environment: production
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster ${{ env.ECS_CLUSTER }} \
--service ${{ env.ECS_SERVICE }} \
--force-new-deployment
4.2 각 단계 설명
| 단계 | Action | 역할 |
|---|---|---|
| Checkout | actions/checkout@v4 |
소스 코드를 Runner에 복사 |
| AWS 인증 | aws-actions/configure-aws-credentials@v4 |
OIDC로 임시 자격 증명 발급 |
| ECR 로그인 | aws-actions/amazon-ecr-login@v2 |
Docker가 ECR에 Push할 수 있도록 인증 |
| 태그 생성 | docker/metadata-action@v5 |
commit SHA, 브랜치명 기반 태그 자동 생성 |
| Buildx 설정 | docker/setup-buildx-action@v3 |
BuildKit 활성화 (캐싱, Multi-platform 지원) |
| 빌드 & Push | docker/build-push-action@v6 |
이미지 빌드 + ECR Push |
| 취약점 스캔 | aquasecurity/trivy-action |
CRITICAL/HIGH 취약점 발견 시 파이프라인 중단 |
| 배포 | aws ecs update-service |
ECS 서비스가 새 이미지를 Pull하도록 트리거 |
5. 이미지 태깅 전략
이미지 태그를 어떻게 지정하느냐에 따라 배포 추적과 롤백 용이성이 달라집니다.
5.1 태깅 방식 비교
| 방식 | 예시 | 장점 | 단점 |
|---|---|---|---|
latest |
my-api:latest |
단순함 | 어떤 커밋인지 추적 불가, 롤백 어려움 |
| Git SHA | my-api:a1b2c3d |
정확한 커밋 추적 | 사람이 읽기 어려움 |
| 시맨틱 버전 | my-api:1.2.3 |
명확한 릴리스 구분 | 태깅 프로세스 필요 |
| SHA + Branch | my-api:a1b2c3d, my-api:main |
추적 + 최신 참조 모두 가능 | 태그 2개 관리 |
운영 환경에서는 Git SHA 기반 태그를 권장합니다. 배포된 이미지가 어떤 커밋에서 빌드되었는지 즉시 확인할 수 있고, 롤백 시 이전 SHA 태그를 지정하면 됩니다.
5.2 latest 태그를 피해야 하는 이유
# ❌ 이렇게 하면 안 됩니다
tags: my-api:latest
latest는 "가장 최근에 Push된 이미지"를 가리키는 mutable 태그입니다. 문제는:
- ECS Task Definition에
latest를 지정하면 언제 어떤 버전이 배포되는지 예측할 수 없습니다. - 롤백 시 "이전 latest"가 무엇이었는지 확인할 방법이 없습니다.
- 여러 브랜치에서 빌드하면
latest가 덮어써집니다.
docker/metadata-action을 사용하면 commit SHA, 브랜치명, PR 번호 기반의 태그를 자동으로 생성할 수 있습니다.
6. 빌드 캐싱 전략
Docker 이미지 빌드에서 가장 시간이 많이 걸리는 부분은 의존성 설치입니다. 캐싱을 적용하면 변경되지 않은 레이어를 재사용하여 빌드 시간을 크게 줄일 수 있습니다.
6.1 캐싱 방식 비교
| 방식 | 설정 | 속도 | 비용 | 적합 상황 |
|---|---|---|---|---|
GitHub Actions Cache (gha) |
cache-from: type=gha |
빠름 | GitHub 캐시 용량 사용 (10GB 제한) | 대부분의 프로젝트 |
| Registry Cache | cache-from: type=registry |
중간 | ECR 저장 비용 | 여러 Runner에서 공유 필요 시 |
| 캐싱 없음 | - | 느림 | 분당 과금 증가 | - |
6.2 GitHub Actions Cache (권장)
- 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
mode=max는 빌드의 모든 레이어를 캐싱합니다. Multi-stage Build에서 중간 단계의 레이어도 캐싱되므로 다음 빌드에서 최대한 재사용할 수 있습니다.
6.3 Registry Cache (대안)
GitHub Cache 용량(10GB)이 부족하거나, Self-hosted Runner에서 캐시 공유가 필요하면 ECR에 캐시 이미지를 저장하는 방식을 사용합니다.
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=${{ steps.ecr-login.outputs.registry }}/my-api:buildcache
cache-to: type=registry,ref=${{ steps.ecr-login.outputs.registry }}/my-api:buildcache,mode=max
6.4 캐싱 효과 측정
Node.js 프로젝트 기준 일반적인 빌드 시간 비교:
| 상황 | 캐싱 없음 | GHA Cache 적용 |
|---|---|---|
| 의존성 변경 없음, 소스만 변경 | 3~5분 | 30초~1분 |
| 의존성 추가 (package.json 변경) | 3~5분 | 2~3분 |
| Dockerfile 구조 변경 | 3~5분 | 3~5분 (캐시 무효화) |
캐시가 기대만큼 동작하지 않으면 Dockerfile의 레이어 순서를 확인합니다. 자주 변경되는 파일(
COPY . .)을 가능한 뒤쪽에 배치해야 이전 레이어의 캐시가 유지됩니다.7. 보안 강화
7.1 이미지 취약점 스캔
빌드된 이미지에 알려진 취약점(CVE)이 포함되어 있으면 배포를 차단해야 합니다. Trivy를 사용하면 이미지의 OS 패키지와 애플리케이션 의존성을 스캔합니다.
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.ecr-login.outputs.registry }}/my-api:${{ github.sha }}
format: table
exit-code: '1' # 취약점 발견 시 파이프라인 실패
severity: CRITICAL,HIGH # CRITICAL, HIGH만 차단
ignore-unfixed: true # 아직 패치가 없는 취약점은 무시
exit-code: '1'은 취약점 발견 시 해당 Step을 실패로 만듭니다. deploy Job이 scan Job에 의존(needs: [build, scan])하므로 스캔 실패 시 배포가 자동으로 차단됩니다.
7.2 Action 버전 고정
# ❌ 위험: 태그는 mutable — 공급망 공격에 취약
- uses: actions/checkout@v4
# ✅ 안전: SHA 고정 — 코드가 변경되면 SHA가 달라짐
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Marketplace Action의 태그(v4)는 관리자가 언제든지 다른 커밋을 가리키도록 변경할 수 있습니다. SHA를 고정하면 의도하지 않은 코드 변경이 파이프라인에 유입되는 것을 방지합니다.
다만 모든 Action을 SHA로 고정하면 업데이트 추적이 어렵습니다. Dependabot이나 Renovate를 설정하여 Action 버전 업데이트를 PR로 받으면 보안과 유지보수를 모두 챙길 수 있습니다.
7.3 최소 권한 permissions 설정
jobs:
build:
permissions:
id-token: write # OIDC에 필요
contents: read # 코드 읽기에 필요
# packages: write # GHCR Push 시 필요 (ECR 사용 시 불필요)
GitHub Actions는 기본적으로 GITHUB_TOKEN에 넓은 권한을 부여합니다. permissions를 명시적으로 선언하면 해당 Job에서 사용할 수 있는 권한이 선언된 것만으로 제한됩니다.
7.4 Secret 관리
# ❌ 나쁨: 하드코딩
env:
AWS_ACCOUNT_ID: "123456789012"
# ✅ 좋음: GitHub Secrets 또는 Variables 사용
env:
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
OIDC를 사용하면 AWS Access Key를 Secret에 저장할 필요가 없습니다. 다만 계정 ID, 리전 등 민감하지 않지만 환경마다 다른 값은 GitHub Variables(vars.)에 저장하면 관리가 편합니다.
8. PR 환경에서의 워크플로우
PR에서는 이미지를 Push하지 않고 빌드 성공 여부만 확인합니다. 이는 push 조건으로 제어합니다.
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }} # PR에서는 Push 안 함
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
PR에서 이미지를 Push하지 않는 이유:
- 불필요한 이미지가 ECR에 쌓여 저장 비용이 증가합니다.
- merge되지 않을 수도 있는 코드의 이미지가 레지스트리에 존재하면 혼란을 줍니다.
push: false로 설정해도 빌드 자체는 실행되므로 Dockerfile 오류를 잡을 수 있습니다.
9. ECS 배포 상세
9.1 단순 배포 (force-new-deployment)
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster ${{ env.ECS_CLUSTER }} \
--service ${{ env.ECS_SERVICE }} \
--force-new-deployment
이 방식은 현재 Task Definition의 최신 리비전을 사용하여 서비스를 재시작합니다. Task Definition에 latest 태그 대신 ECR 이미지를 사용하고, ECS가 새 태그의 이미지를 Pull하도록 합니다.
9.2 Task Definition을 업데이트하는 배포 (권장)
더 안전한 방식은 Task Definition에 정확한 이미지 태그를 기록하는 것입니다.
- name: Download current task definition
run: |
aws ecs describe-task-definition \
--task-definition my-api \
--query taskDefinition \
> task-definition.json
- name: Update image in task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api
image: ${{ steps.ecr-login.outputs.registry }}/my-api:${{ github.sha }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
wait-for-service-stability: true를 설정하면 새 Task가 정상적으로 실행될 때까지 Step이 대기합니다. 배포 후 Health Check 실패로 Task가 반복 재시작되면 Step이 실패합니다.
9.3 두 방식 비교
| 항목 | force-new-deployment | Task Definition 업데이트 |
|---|---|---|
| 추적성 | 어떤 이미지가 배포되었는지 불명확 | Task Definition 리비전에 이미지 태그 기록 |
| 롤백 | 이전 이미지 태그를 알아야 함 | 이전 Task Definition 리비전으로 롤백 |
| 안정성 확인 | 별도 확인 필요 | wait-for-service-stability로 자동 확인 |
| 설정 복잡도 | 낮음 | 중간 |
운영 환경에서는 Task Definition 업데이트 방식을 권장합니다. 배포 이력이 Task Definition 리비전으로 남으므로 "언제, 어떤 이미지가 배포되었는지" 추적할 수 있습니다.
10. 비용 고려사항
10.1 GitHub Actions 비용
| 항목 | 비용 |
|---|---|
| GitHub-hosted Runner (Linux) | $0.006/분 (Free 플랜 2,000분 무료) |
| 캐시 저장소 | 최대 10GB (무료) |
| 빌드 시간 단축 효과 | 캐싱 적용 시 50~80% 절감 가능 |
일 10회 빌드, 빌드당 3분(캐싱 적용)이면 월 약 900분입니다. Free 플랜으로 충분합니다.
10.2 ECR 비용
| 항목 | 비용 |
|---|---|
| 이미지 저장 | $0.10/GB/월 |
| 데이터 전송 (같은 리전) | 무료 |
| 데이터 전송 (다른 리전) | $0.01/GB~ |
이미지 크기가 150MB이고 최근 30개 태그를 보존한다면 약 4.5GB × $0.10 = $0.45/월입니다.
10.3 비용 최적화 방법
- ECR Lifecycle Policy: 오래된 이미지를 자동 삭제합니다.
- Multi-stage Build: 이미지 크기를 줄여 저장 비용과 전송 시간을 절감합니다.
- 레이어 캐싱: 빌드 시간 = Runner 과금 시간입니다. 캐싱으로 빌드 시간을 줄이면 비용도 줄어듭니다.
{
"rules": [
{
"rulePriority": 1,
"description": "최근 10개 이미지만 보존",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["main"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "태그 없는 이미지 1일 후 삭제",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 1
},
"action": {
"type": "expire"
}
}
]
}
11. 자주 하는 실수와 해결
| 실수 | 결과 | 해결 방법 |
|---|---|---|
permissions 미선언으로 OIDC 실패 |
id-token: write 없으면 토큰 발급 불가 |
Job 레벨에서 permissions 명시 |
| Buildx 미설정으로 캐싱 불가 | docker/build-push-action은 BuildKit 필요 |
docker/setup-buildx-action 추가 |
| PR에서 이미지 Push 시도 | ECR에 불필요한 이미지 누적 | push: ${{ github.event_name != 'pull_request' }} |
latest 태그만 사용 |
롤백 불가, 배포 추적 불가 | SHA 기반 태그 사용 |
| ECR Lifecycle Policy 미설정 | 이미지 무한 누적 → 비용 증가 | Lifecycle Policy로 보존 기간 설정 |
| 캐시 key 충돌 | 다른 브랜치의 캐시를 잘못 사용 | docker/build-push-action의 GHA 캐시는 브랜치별 격리됨 |
wait-for-service-stability 미사용 |
배포 실패를 인지하지 못함 | deploy Step에서 안정성 확인 추가 |
12. 운영 환경 체크리스트
파이프라인을 프로덕션에 적용하기 전에 확인할 항목:
보안 - [ ] OIDC 인증 사용 (장기 Access Key 제거) - [ ] IAM Role의 sub 조건으로 브랜치 제한 - [ ] permissions를 최소한으로 선언 - [ ] Action 버전 SHA 고정 또는 Dependabot 설정 - [ ] Dockerfile에서 non-root 사용자 실행
빌드 - [ ] Multi-stage Build로 이미지 크기 최소화 - [ ] 레이어 캐싱(cache-from/cache-to) 적용 - [ ] Git SHA 기반 이미지 태깅
배포 - [ ] Task Definition에 정확한 이미지 태그 기록 - [ ] wait-for-service-stability로 배포 안정성 확인 - [ ] environment: production으로 수동 승인 게이트 추가
비용/운영 - [ ] ECR Lifecycle Policy 설정 - [ ] 취약점 스캔(Trivy 또는 ECR Basic Scanning) 적용 - [ ] 배포 실패 시 알림 설정 (Slack, Teams 등)
13. 정리
- GitHub Actions에서 Docker 이미지를 빌드하고 배포하는 파이프라인은
build-push-action+ ECR + ECS 조합으로 구성합니다. - OIDC 인증으로 장기 자격 증명 없이 AWS에 접근하면 보안과 운영 편의성을 모두 확보할 수 있습니다.
- 레이어 캐싱(
type=gha)을 적용하면 빌드 시간과 CI 비용을 동시에 줄일 수 있습니다. - 이미지 태그는 Git SHA를 사용하여 추적성과 롤백 용이성을 확보합니다.
- 취약점 스캔을 배포 전에 실행하면 알려진 CVE가 프로덕션에 유입되는 것을 차단할 수 있습니다.
- 단순히 "돌아가는" 파이프라인이 아니라, 보안, 캐싱, 태깅, 스캔, 안정성 확인까지 갖춘 운영 수준의 워크플로우가 필요합니다.
관련 글
- CI/CD 파이프라인 기본 구조: 코드 커밋부터 프로덕션 배포까지
- GitHub Actions와 Jenkins 차이: CI/CD 도구 선택 기준
- Terraform S3 Backend와 State Lock 구성하기
참고 문서
'DevOps' 카테고리의 다른 글
| Blue-Green Deployment와 Rolling Update 차이: 배포 전략 선택 기준 (0) | 2026.06.07 |
|---|---|
| GitHub Actions와 Jenkins 차이: CI/CD 도구 선택 기준 (0) | 2026.06.05 |
| Terraform S3 Backend와 State Lock 구성하기: 팀 협업을 위한 원격 상태 관리 (0) | 2026.05.31 |
| CI/CD 파이프라인 기본 구조: 코드 커밋부터 프로덕션 배포까지 (0) | 2026.05.29 |
| Terraform State란 무엇인가: 상태 관리의 개념과 실무 운영 전략 (0) | 2026.05.28 |