CI/CD 스터디 06: Container Image Build와 보안 스캔
Dockerfile, BuildKit/buildx, image tag와 digest, container scan, SBOM 관점에서 CI/CD 이미지 빌드 보안 게이트를 정리합니다.
컨테이너 기반 CI/CD에서는 Dockerfile이 운영 이미지의 출발점입니다. Dockerfile 품질이 낮으면 파이프라인이 아무리 좋아도 배포 결과가 불안정합니다.
컨테이너 빌드 컴포넌트
- Dockerfile: runtime filesystem과 entrypoint를 정의합니다.
- builder: BuildKit, buildx, kaniko, buildah처럼 image layer를 생성합니다.
- base image: 보안 패치와 패키지 기준이 되는 부모 이미지입니다.
- registry: 빌드 결과를 저장하고 배포 시스템이 pull하는 장소입니다.
- scanner: 취약점, secret-like file, misconfiguration, license risk를 검사합니다.
좋은 Dockerfile의 기본
운영 이미지는 작고, 재현 가능하며, 권한이 낮아야 합니다. build dependency와 runtime dependency를 분리하기 위해 multi-stage build를 사용합니다.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]빌드 명령
IMAGE=registry.example.com/team/app
VERSION=v1.4.0
docker buildx create --use --name app-builder || docker buildx use app-builder
docker buildx build --platform linux/amd64 --tag ${IMAGE}:${VERSION} --tag ${IMAGE}:sha-$(git rev-parse --short=12 HEAD) --push .스캔 게이트 배치
보안 스캔은 너무 늦게 돌리면 배포 직전에 막히고, 너무 빡세게 걸면 개발 속도가 멈춥니다. 기본은 PR 단계에서 Dockerfile/static scan, main 단계에서 image scan, release 단계에서 digest 기반 evidence 저장입니다.
# 예시 도구. 실제 정책은 조직 기준에 맞게 severity와 ignore 만료일을 정한다.
trivy fs --exit-code 1 --severity HIGH,CRITICAL .
hadolint Dockerfile
trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/team/app:v1.4.0GitHub Actions 예시
jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build image
run: |
docker buildx build --load -t local/app:${{ github.sha }} .
- name: Scan image
run: |
trivy image --exit-code 1 --severity HIGH,CRITICAL local/app:${{ github.sha }}위험 신호
- Dockerfile에서 root 사용자로 runtime을 실행합니다.
- build context 전체를 무분별하게 COPY합니다.
- base image tag가 latest입니다.
- 패키지 설치 후 cache와 index 파일을 그대로 남깁니다.
- scanner 실패를 무조건 allow_failure로 처리합니다.
- image tag만 기록하고 digest를 남기지 않습니다.
실무 체크리스트
- .dockerignore를 먼저 정리합니다.
- base image는 digest 또는 명확한 version tag로 고정합니다.
- runtime stage에는 빌드 도구와 test dependency를 남기지 않습니다.
- 스캔 예외는 파일로 관리하고 만료일과 근거를 둡니다.
- 운영 배포는 image digest를 release record에 저장합니다.