Skip to content

필사 모드: Docker 완전 정복 2025: 컨테이너 기초부터 멀티스테이지 빌드, Docker Compose, 프로덕션 배포까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

컨테이너 기술은 현대 소프트웨어 개발의 근간이 되었습니다. "내 로컬에서는 되는데?"라는 전설적인 변명을 없앤 것이 바로 Docker입니다. 2013년 등장 이후 Docker는 개발, 테스트, 배포의 패러다임을 완전히 바꾸어 놓았고, 2025년 현재는 Kubernetes, 서버리스, AI 워크로드까지 컨테이너가 기본 단위로 자리 잡았습니다.

이 글에서는 컨테이너의 근본 원리부터 Dockerfile 최적화, 멀티스테이지 빌드, Docker Compose 실전, 보안, CI/CD 파이프라인, 프로덕션 운영, 그리고 Docker 대안과 AI 워크로드까지 체계적으로 다룹니다.

1. 컨테이너란 무엇인가 — VM과의 차이

컨테이너의 정의

컨테이너는 애플리케이션과 그 의존성을 하나의 패키지로 묶어 격리된 환경에서 실행하는 기술입니다. 가상 머신(VM)과 달리 OS 커널을 공유하기 때문에 훨씬 가볍고 빠릅니다.

Linux의 3가지 핵심 기술

컨테이너는 Linux 커널의 세 가지 기능을 조합한 것입니다.

**1) Namespaces — 격리**

프로세스가 볼 수 있는 시스템 자원의 범위를 제한합니다.

| Namespace | 격리 대상 |

| --------- | --------------------------- |

| PID | 프로세스 ID |

| NET | 네트워크 인터페이스, 라우팅 |

| MNT | 파일시스템 마운트 포인트 |

| UTS | 호스트명, 도메인명 |

| IPC | 프로세스 간 통신 |

| USER | 사용자 및 그룹 ID |

| CGROUP | cgroup 루트 디렉토리 |

**2) Cgroups — 자원 제한**

CPU, 메모리, 디스크 I/O, 네트워크 대역폭 등 자원 사용량을 제한합니다.

cgroup으로 메모리를 256MB로 제한하는 예시

sudo cgcreate -g memory:mycontainer

echo 268435456 | sudo tee /sys/fs/cgroup/memory/mycontainer/memory.limit_in_bytes

**3) Union Filesystem — 레이어 구조**

여러 파일시스템 레이어를 하나로 합쳐서 보여주는 기술입니다. Docker는 OverlayFS를 사용합니다.

- 읽기 전용(Read-Only) 레이어: 베이스 이미지, 패키지 설치 등

- 쓰기 가능(Read-Write) 레이어: 컨테이너 실행 시 변경 사항

컨테이너 vs VM 비교

┌─────────────────────────────────────────────────────────────┐

│ 컨테이너 아키텍처 │

├──────────┬──────────┬──────────┬──────────┐ │

│ App A │ App B │ App C │ App D │ │

├──────────┼──────────┼──────────┼──────────┤ │

│ Bins/ │ Bins/ │ Bins/ │ Bins/ │ │

│ Libs │ Libs │ Libs │ Libs │ │

├──────────┴──────────┴──────────┴──────────┤ │

│ Container Runtime │ │

├────────────────────────────────────────────┤ │

│ Host OS Kernel │ │

├────────────────────────────────────────────┤ │

│ Infrastructure │ │

└────────────────────────────────────────────┘ │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ VM 아키텍처 │

├──────────┬──────────┬──────────┬──────────┐ │

│ App A │ App B │ App C │ App D │ │

├──────────┼──────────┼──────────┼──────────┤ │

│ Bins/ │ Bins/ │ Bins/ │ Bins/ │ │

│ Libs │ Libs │ Libs │ Libs │ │

├──────────┼──────────┼──────────┼──────────┤ │

│ Guest OS │ Guest OS │ Guest OS │ Guest OS │ │

├──────────┴──────────┴──────────┴──────────┤ │

│ Hypervisor │ │

├────────────────────────────────────────────┤ │

│ Host OS │ │

├────────────────────────────────────────────┤ │

│ Infrastructure │ │

└────────────────────────────────────────────┘ │

└─────────────────────────────────────────────────────────────┘

| 항목 | 컨테이너 | VM |

| ------------- | ---------------- | ------------- |

| 시작 시간 | 밀리초 | 수 분 |

| 이미지 크기 | MB 단위 | GB 단위 |

| 성능 오버헤드 | 거의 없음 | 10~20% |

| 격리 수준 | 프로세스 수준 | 하드웨어 수준 |

| OS 지원 | 호스트 커널 공유 | 독립 OS 가능 |

OCI 표준

OCI(Open Container Initiative)는 컨테이너 이미지와 런타임의 개방형 표준을 정의합니다.

- **Runtime Specification**: 컨테이너 실행 방법 표준화

- **Image Specification**: 컨테이너 이미지 포맷 표준화

- **Distribution Specification**: 이미지 배포 방법 표준화

Docker, Podman, containerd 모두 OCI 표준을 따르므로 이미지가 호환됩니다.

2. Docker 아키텍처

Client-Server 모델

Docker는 클라이언트-서버 아키텍처를 사용합니다.

┌─────────────┐ REST API ┌─────────────────────┐

│ Docker │ ──────────────> │ Docker Daemon │

│ CLI │ │ (dockerd) │

│ │ │ │

│ docker run │ │ ┌─────────────────┐ │

│ docker build│ │ │ containerd │ │

│ docker pull │ │ │ │ │

└─────────────┘ │ │ ┌───────────┐ │ │

│ │ │ runc │ │ │

│ │ └───────────┘ │ │

│ └─────────────────┘ │

└─────────────────────┘

- **Docker CLI**: 사용자가 명령을 입력하는 클라이언트

- **Docker Daemon (dockerd)**: 컨테이너 라이프사이클을 관리하는 서버 프로세스

- **containerd**: OCI 호환 컨테이너 런타임 관리자

- **runc**: 실제로 컨테이너를 생성하고 실행하는 저수준 런타임

핵심 개념 4가지

**1) 이미지 (Image)**

읽기 전용 템플릿으로, 컨테이너를 만드는 청사진입니다. 레이어 구조로 되어 있어 효율적으로 저장하고 전송합니다.

이미지 관리 명령어

docker images # 로컬 이미지 목록

docker pull nginx:1.25-alpine # 이미지 다운로드

docker image inspect nginx # 이미지 상세 정보

docker image prune # 사용하지 않는 이미지 정리

**2) 컨테이너 (Container)**

이미지의 실행 인스턴스입니다. 이미지 위에 쓰기 가능한 레이어가 추가됩니다.

컨테이너 실행 및 관리

docker run -d --name my-nginx -p 8080:80 nginx:1.25-alpine

docker ps # 실행 중인 컨테이너 목록

docker logs my-nginx # 로그 확인

docker exec -it my-nginx /bin/sh # 컨테이너 내부 접속

docker stop my-nginx # 정지

docker rm my-nginx # 삭제

**3) 볼륨 (Volume)**

컨테이너의 데이터를 영구적으로 저장하는 메커니즘입니다.

볼륨 생성 및 사용

docker volume create my-data

docker run -d -v my-data:/app/data my-app

docker run -d -v /host/path:/container/path my-app # 바인드 마운트

**4) 네트워크 (Network)**

컨테이너 간 통신을 관리합니다.

네트워크 생성 및 관리

docker network create my-network

docker run -d --network my-network --name api my-api

docker run -d --network my-network --name db postgres

api 컨테이너에서 db:5432로 접근 가능

3. Dockerfile 마스터 클래스

주요 명령어 해설

베이스 이미지 선택

FROM node:20-alpine

메타데이터 추가

LABEL maintainer="dev@example.com"

LABEL version="1.0"

빌드 시 변수 (빌드 시에만 사용)

ARG NODE_ENV=production

환경 변수 (런타임에도 사용)

ENV NODE_ENV=$NODE_ENV

ENV PORT=3000

작업 디렉토리 설정

WORKDIR /app

파일 복사 (COPY 권장)

COPY package*.json ./

명령어 실행

RUN npm ci --only=production

소스 코드 복사

COPY . .

포트 문서화 (실제 포트 열지는 않음)

EXPOSE 3000

컨테이너 상태 체크

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \

CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

실행할 사용자 변경 (보안)

USER node

컨테이너 시작 시 실행할 명령

ENTRYPOINT ["node"]

CMD ["server.js"]

COPY vs ADD 차이

| 기능 | COPY | ADD |

| ------------------ | ---- | ------------- |

| 로컬 파일 복사 | O | O |

| URL 다운로드 | X | O |

| tar 자동 압축 해제 | X | O |

| 권장도 | 권장 | 특수한 경우만 |

**결론**: 대부분의 경우 `COPY`를 사용하세요. `ADD`는 tar 자동 해제가 필요할 때만 사용합니다.

CMD vs ENTRYPOINT 차이

CMD만 사용 - docker run 시 완전 대체 가능

CMD ["python", "app.py"]

docker run my-image python test.py (CMD가 test.py로 대체됨)

ENTRYPOINT만 사용 - 항상 실행됨

ENTRYPOINT ["python"]

docker run my-image app.py (python app.py로 실행)

ENTRYPOINT + CMD 조합 (가장 유연)

ENTRYPOINT ["python"]

CMD ["app.py"]

docker run my-image -> python app.py

docker run my-image test.py -> python test.py

ARG vs ENV

ARG: 빌드 시에만 존재 (런타임에는 없음)

ARG BUILD_VERSION=1.0

RUN echo "Building version: $BUILD_VERSION"

ENV: 빌드 시 + 런타임 모두 존재

ENV APP_VERSION=1.0

컨테이너 실행 시에도 APP_VERSION 사용 가능

Dockerfile 최적화 베스트 프랙티스

**1) 레이어 순서가 캐시를 결정한다**

나쁜 예: 소스 변경 시 npm install 재실행

COPY . .

RUN npm install

좋은 예: package.json 변경 시에만 npm install 재실행

COPY package*.json ./

RUN npm ci

COPY . .

**2) RUN 명령을 합치기**

나쁜 예: 3개의 레이어 생성

RUN apt-get update

RUN apt-get install -y curl

RUN rm -rf /var/lib/apt/lists/*

좋은 예: 1개의 레이어 생성 + 캐시 정리

RUN apt-get update && \

apt-get install -y --no-install-recommends curl && \

rm -rf /var/lib/apt/lists/*

**3) .dockerignore 활용**

.dockerignore

node_modules

.git

.env

*.md

Dockerfile

docker-compose*.yml

.github

coverage

dist

**4) 작은 베이스 이미지 선택**

| 이미지 | 크기 | 용도 |

| -------------- | ------ | ------------------- |

| ubuntu:22.04 | ~77MB | 풀 OS 필요 시 |

| node:20-slim | ~200MB | 대부분의 Node.js 앱 |

| node:20-alpine | ~130MB | 경량 Node.js 앱 |

| alpine:3.19 | ~7MB | 최소 베이스 |

| distroless | ~2MB | 최소한의 런타임만 |

| scratch | 0MB | 정적 바이너리 전용 |

4. 멀티스테이지 빌드 — 이미지 90% 줄이기

멀티스테이지 빌드는 빌드 도구와 최종 실행 환경을 분리하여 이미지 크기를 극적으로 줄이는 기법입니다.

Node.js 예제: 1.2GB에서 120MB로

Stage 1: 빌드 환경

FROM node:20 AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

Stage 2: 프로덕션 환경

FROM node:20-alpine AS production

WORKDIR /app

프로덕션 의존성만 설치

COPY package*.json ./

RUN npm ci --only=production && npm cache clean --force

빌드 결과물만 복사

COPY --from=builder /app/dist ./dist

보안: non-root 사용자

USER node

EXPOSE 3000

CMD ["node", "dist/server.js"]

**결과**: `node:20` 풀 이미지(~1.1GB) 대신 Alpine 기반(~130MB) + 빌드 도구 제외로 최종 120MB

Go 예제: 800MB에서 15MB로

Stage 1: 빌드

FROM golang:1.22-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

Stage 2: 최소 이미지

FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

COPY --from=builder /app/server /server

EXPOSE 8080

ENTRYPOINT ["/server"]

**결과**: Go 빌드 환경(~800MB)에서 정적 바이너리만 추출하여 15MB 이하

Java/Spring Boot 예제: 400MB에서 80MB로

Stage 1: 빌드

FROM eclipse-temurin:21-jdk AS builder

WORKDIR /app

COPY gradlew build.gradle settings.gradle ./

COPY gradle ./gradle

RUN ./gradlew dependencies --no-daemon

COPY src ./src

RUN ./gradlew bootJar --no-daemon

Stage 2: 런타임

FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

COPY --from=builder /app/build/libs/*.jar app.jar

RUN addgroup -S spring && adduser -S spring -G spring

USER spring:spring

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

Python 예제

Stage 1: 의존성 빌드

FROM python:3.12-slim AS builder

WORKDIR /app

RUN pip install --no-cache-dir poetry

COPY pyproject.toml poetry.lock ./

RUN poetry export -f requirements.txt -o requirements.txt --without-hashes

RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

Stage 2: 런타임

FROM python:3.12-slim

WORKDIR /app

COPY --from=builder /install /usr/local

COPY . .

RUN useradd --create-home appuser

USER appuser

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app.main:app"]

빌드 결과 비교 요약

| 언어 | 싱글 스테이지 | 멀티스테이지 | 절감률 |

| ------- | ------------- | ------------ | ------ |

| Node.js | 1.2GB | 120MB | 90% |

| Go | 800MB | 15MB | 98% |

| Java | 400MB | 80MB | 80% |

| Python | 900MB | 150MB | 83% |

5. Docker Compose 실전

docker-compose.yml 기본 구조

Docker Compose는 여러 컨테이너를 YAML 파일 하나로 정의하고 관리합니다.

docker-compose.yml

version: '3.9'

services:

api:

build:

context: ./api

dockerfile: Dockerfile

ports:

- '3000:3000'

environment:

- NODE_ENV=production

- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp

- REDIS_URL=redis://redis:6379

depends_on:

db:

condition: service_healthy

redis:

condition: service_started

healthcheck:

test: ['CMD', 'wget', '--spider', 'http://localhost:3000/health']

interval: 30s

timeout: 10s

retries: 3

start_period: 40s

restart: unless-stopped

networks:

- app-network

db:

image: postgres:16-alpine

volumes:

- postgres-data:/var/lib/postgresql/data

- ./init-scripts:/docker-entrypoint-initdb.d

environment:

POSTGRES_DB: myapp

POSTGRES_USER: postgres

POSTGRES_PASSWORD: secret

ports:

- '5432:5432'

healthcheck:

test: ['CMD-SHELL', 'pg_isready -U postgres']

interval: 10s

timeout: 5s

retries: 5

networks:

- app-network

redis:

image: redis:7-alpine

command: redis-server --appendonly yes --maxmemory 256mb

volumes:

- redis-data:/data

ports:

- '6379:6379'

networks:

- app-network

nginx:

image: nginx:1.25-alpine

ports:

- '80:80'

- '443:443'

volumes:

- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro

- ./nginx/ssl:/etc/nginx/ssl:ro

depends_on:

- api

networks:

- app-network

volumes:

postgres-data:

driver: local

redis-data:

driver: local

networks:

app-network:

driver: bridge

환경 변수와 Secrets 관리

.env 파일 사용

services:

api:

env_file:

- .env

- .env.production

Docker Secrets (Swarm 모드)

services:

api:

secrets:

- db_password

- api_key

secrets:

db_password:

file: ./secrets/db_password.txt

api_key:

external: true

Compose 주요 명령어

서비스 시작 (백그라운드)

docker compose up -d

서비스 로그 확인

docker compose logs -f api

서비스 스케일링

docker compose up -d --scale api=3

서비스 재빌드 + 재시작

docker compose up -d --build

서비스 중지 및 리소스 정리

docker compose down

볼륨까지 모두 삭제

docker compose down -v

특정 서비스만 재시작

docker compose restart api

실행 중인 서비스 상태 확인

docker compose ps

개발 환경과 프로덕션 환경 분리

docker-compose.yml (공통)

services:

api:

build: ./api

docker-compose.override.yml (개발 환경 - 자동 로드)

services:

api:

volumes:

- ./api:/app # 핫 리로드용 마운트

environment:

- DEBUG=true

command: npm run dev

docker-compose.prod.yml (프로덕션)

services:

api:

environment:

- NODE_ENV=production

deploy:

replicas: 3

resources:

limits:

memory: 512M

cpus: "0.5"

개발 환경 (자동으로 override 파일 로드)

docker compose up

프로덕션 환경

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

6. 네트워킹 심화

Docker 네트워크 드라이버

| 드라이버 | 설명 | 사용 사례 |

| -------- | ------------------------------------------- | --------------------- |

| bridge | 기본 드라이버, 동일 호스트 내 컨테이너 통신 | 단일 호스트 개발 환경 |

| host | 호스트 네트워크 스택 직접 사용 | 성능이 중요한 경우 |

| overlay | 여러 Docker 호스트 간 통신 | Swarm, 멀티 호스트 |

| macvlan | 컨테이너에 실제 MAC 주소 할당 | 레거시 네트워크 통합 |

| none | 네트워크 비활성화 | 보안 격리 |

컨테이너 DNS 해석

같은 Docker 네트워크에 있는 컨테이너들은 서비스 이름으로 서로를 찾을 수 있습니다.

커스텀 네트워크 생성

docker network create my-app

같은 네트워크에서 컨테이너 실행

docker run -d --name api --network my-app my-api-image

docker run -d --name db --network my-app postgres

api 컨테이너 내부에서 db 컨테이너에 접근

호스트명 "db"가 자동으로 해석됨

curl http://db:5432

포트 매핑 전략

특정 포트 매핑

docker run -p 8080:80 nginx

특정 인터페이스에만 바인딩

docker run -p 127.0.0.1:8080:80 nginx

랜덤 호스트 포트 할당

docker run -p 80 nginx

여러 포트 매핑

docker run -p 80:80 -p 443:443 nginx

서비스 디스커버리 패턴

Docker Compose에서 자동 서비스 디스커버리

services:

api-gateway:

image: nginx

depends_on:

- user-service

- order-service

user-service:

image: my-user-service

api-gateway에서 http://user-service:3000 으로 접근 가능

order-service:

image: my-order-service

api-gateway에서 http://order-service:3000 으로 접근 가능

7. 보안 베스트 프랙티스

1) Non-root 사용자로 실행

Node.js - 내장 node 사용자 활용

FROM node:20-alpine

WORKDIR /app

COPY --chown=node:node . .

RUN npm ci --only=production

USER node

CMD ["node", "server.js"]

커스텀 사용자 생성

FROM python:3.12-slim

RUN groupadd -r appgroup && useradd -r -g appgroup appuser

WORKDIR /app

COPY --chown=appuser:appgroup . .

USER appuser

CMD ["python", "app.py"]

2) 읽기 전용 파일시스템

docker run --read-only --tmpfs /tmp --tmpfs /var/run my-app

Docker Compose에서

services:

api:

read_only: true

tmpfs:

- /tmp

- /var/run

3) 이미지 보안 스캐닝

Trivy로 취약점 스캔

trivy image my-app:latest

심각도 기준 필터링

trivy image --severity HIGH,CRITICAL my-app:latest

Docker Scout (Docker Desktop 내장)

docker scout cves my-app:latest

docker scout recommendations my-app:latest

Snyk

snyk container test my-app:latest

4) Secrets 관리 (절대 Dockerfile에 넣지 말 것)

절대 하지 마세요

ENV API_KEY=sk-1234567890

COPY .env /app/.env

올바른 방법: 빌드 시 시크릿 마운트 (BuildKit)

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

빌드 시 시크릿 전달

DOCKER_BUILDKIT=1 docker build --secret id=npmrc,src=.npmrc -t my-app .

5) Rootless Docker

Rootless 모드로 Docker 설치

dockerd-rootless-setuptool.sh install

확인

docker info | grep -i rootless

Security Options: rootless

6) Content Trust와 이미지 서명

Docker Content Trust 활성화

export DOCKER_CONTENT_TRUST=1

서명된 이미지만 Pull 가능

docker pull my-registry/my-app:latest # 서명 없으면 실패

Cosign으로 이미지 서명 (Sigstore)

cosign sign my-registry/my-app:latest

cosign verify my-registry/my-app:latest

7) 보안 체크리스트

[보안 체크리스트]

- Non-root 사용자로 실행 (USER 지시어)

- 최소 베이스 이미지 사용 (Alpine, distroless, scratch)

- 이미지 취약점 정기 스캔 (Trivy, Snyk, Scout)

- Dockerfile에 시크릿/자격 증명 절대 포함 금지

- 읽기 전용 파일시스템 사용

- 네트워크 노출 최소화

- 리소스 제한 설정 (메모리, CPU)

- Docker Content Trust 활성화

- 정기적으로 베이스 이미지 업데이트

- 불필요한 패키지/도구 제거

8. CI/CD 파이프라인

GitHub Actions + Docker 빌드/푸시

.github/workflows/docker-build.yml

name: Docker Build and Push

on:

push:

branches: [main]

pull_request:

branches: [main]

env:

REGISTRY: ghcr.io

IMAGE_NAME: my-org/my-app

jobs:

build-and-push:

runs-on: ubuntu-latest

permissions:

contents: read

packages: write

steps:

- name: Checkout

uses: actions/checkout@v4

- name: Set up Docker Buildx

uses: docker/setup-buildx-action@v3

- name: Login to Container Registry

uses: docker/login-action@v3

with:

registry: ghcr.io

username: my-username

password: my-token

- name: Extract metadata

id: meta

uses: docker/metadata-action@v5

with:

images: ghcr.io/my-org/my-app

tags: |

type=ref,event=branch

type=sha,prefix=

type=semver,pattern=v{{version}}

- name: Build and Push

uses: docker/build-push-action@v5

with:

context: .

push: true

tags: steps.meta.outputs.tags

labels: steps.meta.outputs.labels

cache-from: type=gha

cache-to: type=gha,mode=max

platforms: linux/amd64,linux/arm64

멀티 플랫폼 빌드 (amd64 + arm64)

Buildx 빌더 생성

docker buildx create --name multiarch --use

멀티 플랫폼 빌드 및 푸시

docker buildx build \

--platform linux/amd64,linux/arm64 \

--tag my-registry/my-app:latest \

--push .

CI에서 레이어 캐싱

GitHub Actions Cache

- name: Build with cache

uses: docker/build-push-action@v5

with:

cache-from: type=gha

cache-to: type=gha,mode=max

Registry 기반 캐시

- name: Build with registry cache

uses: docker/build-push-action@v5

with:

cache-from: type=registry,ref=ghcr.io/my-org/my-app:buildcache

cache-to: type=registry,ref=ghcr.io/my-org/my-app:buildcache,mode=max

주요 컨테이너 레지스트리 비교

| 레지스트리 | 무료 티어 | 특징 |

| -------------------------------- | ----------------- | ------------------------- |

| Docker Hub | 1개 프라이빗 리포 | 가장 큰 퍼블릭 레지스트리 |

| GitHub Container Registry (GHCR) | 무제한 퍼블릭 | GitHub Actions 통합 |

| Amazon ECR | 500MB 프리 티어 | AWS 서비스 통합 |

| Google Artifact Registry | 500MB 프리 티어 | GCP 서비스 통합 |

| Azure Container Registry | 없음 | Azure 서비스 통합 |

9. 프로덕션 운영

로깅 전략

컨테이너 로그 드라이버 설정

docker run -d \

--log-driver=json-file \

--log-opt max-size=10m \

--log-opt max-file=3 \

my-app

syslog 드라이버

docker run -d \

--log-driver=syslog \

--log-opt syslog-address=tcp://logserver:514 \

my-app

Docker Compose에서 로깅 설정

services:

api:

logging:

driver: json-file

options:

max-size: '10m'

max-file: '3'

**로깅 베스트 프랙티스**: 애플리케이션은 항상 stdout/stderr로 로그를 출력하세요. 파일에 직접 쓰지 마세요. Docker가 로그 드라이버를 통해 수집합니다.

모니터링: Prometheus + cAdvisor

docker-compose.monitoring.yml

services:

prometheus:

image: prom/prometheus:latest

volumes:

- ./prometheus.yml:/etc/prometheus/prometheus.yml

ports:

- '9090:9090'

cadvisor:

image: gcr.io/cadvisor/cadvisor:latest

volumes:

- /:/rootfs:ro

- /var/run:/var/run:ro

- /sys:/sys:ro

- /var/lib/docker/:/var/lib/docker:ro

ports:

- '8080:8080'

grafana:

image: grafana/grafana:latest

volumes:

- grafana-data:/var/lib/grafana

ports:

- '3000:3000'

environment:

- GF_SECURITY_ADMIN_PASSWORD=admin

volumes:

grafana-data:

리소스 제한

메모리 제한: 512MB (초과 시 OOM Kill)

docker run -m 512m my-app

CPU 제한: 1.5 코어

docker run --cpus="1.5" my-app

조합

docker run -m 512m --cpus="1.5" --memory-swap=1g my-app

Docker Compose에서

services:

api:

deploy:

resources:

limits:

memory: 512M

cpus: '1.5'

reservations:

memory: 256M

cpus: '0.5'

Graceful Shutdown (SIGTERM 처리)

// Node.js 예시

process.on('SIGTERM', async () => {

console.log('SIGTERM received. Graceful shutdown...')

// 새 요청 거부

server.close(async () => {

// DB 연결 종료

await db.close()

// Redis 연결 종료

await redis.quit()

console.log('All connections closed. Exiting.')

process.exit(0)

})

// 30초 후 강제 종료

setTimeout(() => {

console.error('Forced shutdown after timeout')

process.exit(1)

}, 30000)

})

Dockerfile에서 올바른 ENTRYPOINT 형식

exec 형식 사용 (SIGTERM이 직접 앱에 전달됨)

ENTRYPOINT ["node", "server.js"]

shell 형식은 사용 금지 (SIGTERM이 sh에만 전달됨)

ENTRYPOINT node server.js # 이렇게 하지 마세요

Health Check 패턴

Dockerfile에서 헬스체크

HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \

CMD curl -f http://localhost:3000/health || exit 1

Docker Compose에서 헬스체크

services:

api:

healthcheck:

test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']

interval: 30s

timeout: 10s

retries: 3

start_period: 40s

db:

healthcheck:

test: ['CMD-SHELL', 'pg_isready -U postgres']

interval: 10s

timeout: 5s

retries: 5

10. Docker 대안: Podman, containerd, nerdctl

Podman — 루트리스, 데몬리스

Podman은 Red Hat이 개발한 Docker 호환 컨테이너 엔진입니다.

Docker와 거의 동일한 CLI

podman run -d -p 8080:80 nginx

podman ps

podman images

Docker와의 호환성을 위한 별칭

alias docker=podman

| 특징 | Docker | Podman |

| ------------ | ------------------- | ----------------------- |

| 아키텍처 | 데몬 기반 (dockerd) | 데몬 없음 (포크 모델) |

| 루트 필요 | 기본적으로 필요 | 기본 루트리스 |

| Compose 지원 | docker compose | podman-compose |

| 시스템 통합 | 독자적 | systemd 통합 |

| Pod 지원 | X | O (Kubernetes Pod 호환) |

containerd — Kubernetes 기본 런타임

containerd는 Docker에서 분리된 컨테이너 런타임으로 Kubernetes의 기본 런타임입니다.

ctr - containerd 네이티브 CLI (저수준)

ctr images pull docker.io/library/nginx:latest

ctr run docker.io/library/nginx:latest my-nginx

nerdctl — containerd용 Docker 호환 CLI

Docker와 동일한 UX

nerdctl run -d -p 8080:80 nginx

nerdctl compose up -d

nerdctl build -t my-app .

추가 기능: 이미지 암호화

nerdctl image encrypt --recipient jwe:public-key.pem my-app encrypted-app

도구 선택 가이드

| 시나리오 | 추천 도구 |

| ------------------------ | -------------------------- |

| 로컬 개발 | Docker Desktop 또는 Podman |

| CI/CD 파이프라인 | Docker (BuildKit) |

| Kubernetes 런타임 | containerd |

| 보안 중시 환경 | Podman (루트리스) |

| Docker 호환 + containerd | nerdctl |

11. Docker + AI 워크로드

NVIDIA Container Toolkit

AI/ML 워크로드에서 GPU를 컨테이너에 전달하려면 NVIDIA Container Toolkit이 필요합니다.

NVIDIA Container Toolkit 설치

distribution=ubuntu22.04

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \

sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

패키지 설치

sudo apt-get update

sudo apt-get install -y nvidia-container-toolkit

Docker 런타임 설정

sudo nvidia-ctk runtime configure --runtime=docker

sudo systemctl restart docker

GPU 패스스루

모든 GPU 사용

docker run --gpus all nvidia/cuda:12.3-base nvidia-smi

특정 GPU만 사용

docker run --gpus '"device=0,1"' my-ml-app

Docker Compose에서

services:

ml-training:

image: my-ml-app

deploy:

resources:

reservations:

devices:

- driver: nvidia

count: 2

capabilities: [gpu]

ML 모델 서빙 컨테이너

PyTorch 모델 서빙 Dockerfile

FROM pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY model/ ./model/

COPY serve.py .

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \

CMD curl -f http://localhost:8000/health || exit 1

CMD ["python", "serve.py"]

TensorFlow Serving

services:

tf-serving:

image: tensorflow/serving:latest-gpu

ports:

- '8501:8501'

volumes:

- ./models:/models

environment:

- MODEL_NAME=my_model

deploy:

resources:

reservations:

devices:

- driver: nvidia

count: 1

capabilities: [gpu]

triton-server:

image: nvcr.io/nvidia/tritonserver:24.01-py3

ports:

- '8000:8000'

- '8001:8001'

- '8002:8002'

volumes:

- ./model-repository:/models

command: tritonserver --model-repository=/models

deploy:

resources:

reservations:

devices:

- driver: nvidia

count: all

capabilities: [gpu]

주요 ML 프레임워크 컨테이너

| 프레임워크 | 공식 이미지 | GPU 지원 |

| ---------------- | --------------------------------------------- | -------- |

| PyTorch | pytorch/pytorch | O |

| TensorFlow | tensorflow/tensorflow | O |

| NVIDIA Triton | nvcr.io/nvidia/tritonserver | O |

| Hugging Face TGI | ghcr.io/huggingface/text-generation-inference | O |

| vLLM | vllm/vllm-openai | O |

퀴즈

Docker와 컨테이너에 대한 이해를 확인해 보세요.

**정답**: 컨테이너는 호스트 OS의 커널을 공유하기 때문입니다. VM은 각각 독립된 게스트 OS를 가지고 있어 수 GB의 오버헤드가 있지만, 컨테이너는 Linux namespaces와 cgroups를 사용하여 프로세스 수준에서 격리하므로 MB 단위의 크기와 밀리초 단위의 시작 시간을 가집니다.

**정답**: COPY는 로컬 파일/디렉토리를 이미지로 복사하는 단순한 기능만 제공합니다. ADD는 추가로 URL 다운로드와 tar 자동 압축 해제 기능이 있습니다. COPY를 권장하는 이유는 (1) 동작이 명시적이고 예측 가능하며, (2) ADD의 URL 다운로드는 캐시 무효화를 일으킬 수 있고, (3) 의도치 않은 tar 해제를 방지하기 위해서입니다. tar 자동 해제가 필요한 경우에만 ADD를 사용하세요.

**정답**: Go는 정적 바이너리(static binary)를 컴파일할 수 있기 때문입니다. CGO_ENABLED=0으로 빌드하면 외부 C 라이브러리 의존성이 없는 독립 실행 파일이 만들어지며, 이를 scratch(빈 이미지) 위에 복사하면 바이너리 크기 그대로가 최종 이미지 크기가 됩니다. 빌드 도구(컴파일러, 패키지 매니저 등)가 모두 제거되므로 크기가 극적으로 줄어듭니다.

**정답**: Docker는 각 레이어를 캐시하고, 한 레이어가 변경되면 그 이후의 모든 레이어를 재빌드합니다. 예를 들어 Node.js 앱에서 COPY . . 후에 RUN npm install을 하면, 소스 코드가 한 글자만 바뀌어도 npm install이 재실행됩니다. 대신 package.json만 먼저 COPY하고 npm install을 실행한 뒤 소스를 복사하면, package.json이 변경되지 않는 한 의존성 설치 레이어가 캐시에서 재사용되어 빌드 시간이 크게 단축됩니다.

**정답**: 컨테이너가 root로 실행되면, 컨테이너 탈출(container escape) 취약점 발생 시 공격자가 호스트 시스템의 root 권한을 획득할 수 있습니다. Dockerfile에서 USER 지시어를 사용하여 non-root 사용자를 지정해야 합니다. Node.js 이미지는 내장 node 사용자가 있고, 다른 이미지는 RUN groupadd/useradd로 사용자를 생성한 뒤 USER 지시어로 전환합니다. 파일 소유권은 COPY --chown 옵션으로 설정합니다.

참고 자료

1. [Docker 공식 문서](https://docs.docker.com/) — 가장 신뢰할 수 있는 레퍼런스

2. [Dockerfile Best Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) — 공식 최적화 가이드

3. [Docker Compose 스펙](https://docs.docker.com/compose/compose-file/) — Compose 파일 레퍼런스

4. [OCI 스펙](https://opencontainers.org/) — 컨테이너 표준 사양

5. [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/overview.html) — GPU 컨테이너 가이드

6. [Trivy 보안 스캐너](https://aquasecurity.github.io/trivy/) — 컨테이너 취약점 스캐닝

7. [Podman 공식 문서](https://podman.io/) — Docker 대안 컨테이너 엔진

8. [containerd 공식 문서](https://containerd.io/) — 산업 표준 컨테이너 런타임

9. [Docker BuildKit 가이드](https://docs.docker.com/build/buildkit/) — 고급 빌드 기능

10. [Sigstore/Cosign](https://docs.sigstore.dev/) — 컨테이너 이미지 서명

11. [cAdvisor](https://github.com/google/cadvisor) — 컨테이너 리소스 모니터링

12. [Distroless 이미지](https://github.com/GoogleContainerTools/distroless) — 최소 컨테이너 이미지

13. [Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html) — OWASP 보안 체크리스트

14. [Multi-platform Docker Builds](https://docs.docker.com/build/building/multi-platform/) — 크로스 플랫폼 빌드 가이드

15. [Docker + AI/ML 가이드](https://docs.docker.com/guides/use-case/genai-pdf-bot/) — Docker GenAI 스택

16. [Hugging Face TGI](https://huggingface.co/docs/text-generation-inference/index) — LLM 모델 서빙

17. [vLLM 프로젝트](https://docs.vllm.ai/) — 고성능 LLM 추론 엔진

현재 단락 (1/698)

컨테이너 기술은 현대 소프트웨어 개발의 근간이 되었습니다. "내 로컬에서는 되는데?"라는 전설적인 변명을 없앤 것이 바로 Docker입니다. 2013년 등장 이후 Docker는 개...

작성 글자: 0원문 글자: 21,428작성 단락: 0/698