Docker 핵심 가이드: 컨테이너 가상화부터 이미지 빌드까지

Docker 개요

컨테이너 기술의 탄생

DotCloud라는 스타트업이 2010년에 PaaS(Platform as a Service) 형태로 서비스를 시작하면서 등장했다. 2013년 오픈소스로 공개된 후 기업명도 Docker Inc.로 변경되면서 지금의 모습을 갖추게 되었다.

Linux Container(LXC)라는 커널 수준의 경량 가상화 기술을 기반으로 하며, Go 언어로 개발되었다. 핵심 철학은 "소프트웨어 운영 환경을 컨테이너라는 단위로 패키징하여 어디서든 동일하게 실행"하는 것이다.

가상 머신과의 차이

기존 가상화는 하이퍼바이저 위에 게스트 OS를 전부 올리는 방식이었다면, 컨테이너는 호스트 OS의 커널을 공유하면서 프로세스와 자원만 격리한다. 이로 인해 시작 속도가 훨씬 빠르고, 오버헤드가 극도로 적다.

핵심 구성 요소

  • Registry: 이미지를 저장하고 배포하는 중앙 저장소 (Docker Hub 등)
  • Image: 컨테이너 실행에 필요한 파일과 설정의 불변 스냅샷
  • Container: 이미지를 기반으로 실제 실행 중인 프로세스 단위

설치 및 환경 구성

설치 방법별 비교

스크립트 자동 설치

# 시스템 업데이트 후 Docker 공식 스크립트 실행
sudo yum update -y
curl -fsSL https://get.docker.com | sh

# 서비스 활성화 및 부팅 시 자동 시작
sudo systemctl enable --now docker

# 정상 설치 확인
sudo docker run hello-world

YUM 저장소 방식

sudo tee /etc/yum.repos.d/docker-ce.repo <<'EOF'
[docker-ce-stable]
name=Docker CE Stable
baseurl=https://download.docker.com/linux/centos/7/x86_64/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg
EOF

sudo yum install -y docker-ce
sudo systemctl enable --now docker

국내 이미지 가속 설정

Docker Hub 직접 접속 시 속도가 느릴 수 있어, 국내 미러 서버를 지정한다.

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "registry-mirrors": ["https://mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

컨테이너 운영 명령어

기본 실행 패턴

컨테이너 실행의 기본 문법은 docker [명령] [옵션] [대상] 형태이다.

# 백그라운드 실행 및 포트 매핑
docker run -d -p 호스트포트:컨테이너포트 --name 컨테이너명 이미지명

# 대화형 터미널 접속
docker run -it ubuntu:22.04 /bin/bash

# 환경변수와 볼륨 함께 지정
docker run -d \
  -e DB_PASSWORD=secret \
  -v /host/data:/app/data \
  -p 8080:80 \
  --network backend \
  myapp:latest

주요 옵션 정리

옵션의미
-d백그라운드(detached) 모드
-i표준입력(STDIN) 유지
-t가짜 터미널(TTY) 할당
-p 호스트:컨테이너포트 포워딩
-P컨테이너의 노출 포트를 호스트 임의 포트에 매핑
--name컨테이너에 고유 이름 부여
--link다른 컨테이와 네트워크 연결(레거시)
-v 호스트:컨테이너볼륨 마운트
--network사용할 네트워크 라이버 지정
-m메모리 사용 한도
--cpusCPU 사용 한도

라이프사이클 관리

# 상태 전환
docker start|stop|restart|pause|unpause 컨테이너명

# 강제 종료 (SIGKILL)
docker kill --signal=SIGTERM 웹서버

# 완전 삭제 (실행 중인 것도 강제)
docker rm -f $(docker ps -aq)

# 실행 중인 컨테이너 내부 명령 실행
docker exec -it 데이터베이스 /bin/sh -c "mysql -u root -p"

# 로그 실시간 추적
docker logs -f --tail=100 애플리케이션

정보 조회

# 실행 중인 프로세스 목록
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# 상세 메타데이터 (JSON)
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 웹서버

# 파일 시스템 변경 사항 확인
docker diff 컨테이너ID

# 리소스 사용량 통계
docker stats --no-stream

이미지 관리 심화

이미지 계층 구조 이해

Docker 이미지는 UnionFS(Union File System)를 활용한 계층(layer) 구조로 구성된다. 각 명령어(INSTRUCTION)마다 새로운 레이어가 생성되며, 이 레이어들은 읽기 전용(read-only)으로 쌓인다.

  • bootfs: 부트로더와 커널 포함. 컨테이너 시작 후 메모리에서 해제됨
  • rootfs: 실제 파일 시스템. 기본적으로 읽기 전용으로 마운트
  • Container Layer: 컨테이너 실행 시 쓰기 가능한 레이어가 위에 추가됨(Copy-on-Write)

컨테이너 → 이미지 변환

# 실행 중인 컨테이너의 현재 상태를 새 이미지로 저장
docker commit -a "작성자" -m "설명 메시지" 원본컨테이너 새이미지명:태그

# 실제 사용 예시
docker commit --pause=false nginx컨테이너 내nginx:v1.1

이미지 이전:存(save/load)

# 이미지를 tar 아카이브로 추출 (레이어 포함)
docker save -o /backup/webapp-$(date +%Y%m%d).tar myapp:1.0

# 다른 환경에서 복원
docker load -i webapp-20240115.tar

# 태그 변경 후 배포
docker tag 소스이미지:태그 목적지저장소/프로젝트/이미지:태그

Dockerfile 작성 전략

핵심 지침과 예시

Dockerfile은 이미지 빌드 자동화를 위한 선언형 스크립트이다. 각 지시어(INSTRUCTION)는 대문자로 작성하며, 캐시 효율을 고려한 순서 배치가 중요하다.

# ---------------------------
# Java Spring Boot 애플리케이션 빌드 예시
# ---------------------------

# 1단계: 빌드 환경
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests

# 2단계: 실행 환경 (멀티스테이지)
FROM eclipse-temurin:17-jre-alpine
LABEL maintainer="devops@example.com"

# 비루트 사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

WORKDIR /application
COPY --from=builder --chown=appuser:appgroup /workspace/target/*.jar app.jar

# JVM 튜닝 옵션
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENV SERVER_PORT=8080

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

지시어 상세 비교

지시어용도특징
FROM기반 이미지 지정반드시 첫 줄, 멀티스테이지 시 여러 번 사용
RUN빌드 시 명령 실행레이어 생성, &&로 연결해 캐시 최적화
CMD컨테이너 기본 실행docker run 인자로 오버라이드 가능
ENTRYPOINT고정 실행 명령exec 형태 권장, CMD와 조합 사용
COPY호스트 → 이미지 파일 복사명시적, 권한/소유자 지정 가능
ADD파일 복사 + 자동 추출tar 자동 해제, 원격 URL 지원
ENV환경변수 설정빌드 및 실행 시 모두 사용
ARG빌드 인자빌드 시 --build-arg로 전달
VOLUME영속 데이터 경로익명 볼륨 생성, 호스트 경로 미지정
WORKDIR작업 디렉터리없으면 생성, 여러 번 사용 가능
EXPOSE노출 포트 문서화실제 포트 개방은 -p 필요
ONBUILD자식 이미지 빌드 시 실행베이스 이미지용, 상위 계층에서 정의

ENTRYPOINT와 CMD의 조합 패턴

# 패턴 1: 고정 실행 + 기본 인자
ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--spring.profiles.active=prod"]

# 패턴 2: 쉘 스크립트 래퍼
ENTRYPOINT ["/entrypoint.sh"]
# entrypoint.sh 내에서 exec "$@" 로 CMD 전달

# 패턴 3: 환경변수 기반 동적 실행
ENTRYPOINT ["sh", "-c", "java $JVM_OPTS -jar app.jar $0 $@"]

docker-compose 다중 컨테이너 관리

설치 및 기본 사용

# Docker Compose 플러그인 설치 (현재 권장 방식)
docker compose version

# 또는 독립 바이너리
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

실전 구성 예시

version: "3.9"

services:
  postgres:
    image: postgres:15-alpine
    container_name: order-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: order_system
      POSTGRES_USER: app_admin
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD required}
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_admin -d order_system"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  redis:
    image: redis:7-alpine
    container_name: cache-server
    volumes:
      - redis_data:/data
    networks:
      - backend

  api-gateway:
    build:
      context: ./gateway
      dockerfile: Dockerfile
      args:
        BUILD_ENV: production
    container_name: gateway-svc
    ports:
      - "80:8080"
      - "443:8443"
    environment:
      - REDIS_HOST=cache-server
      - DB_HOST=order-db
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - backend
      - frontend

  prometheus:
    image: prom/prometheus:v2.47
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9090:9090"
    networks:
      - monitoring

volumes:
  pg_data:
  redis_data:

networks:
  backend:
    driver: bridge
  frontend:
    driver: bridge
  monitoring:
    driver: bridge

주요 명령어

# 전체 스택 기동 (백그라운드)
docker compose up -d --build

# 특정 서비스만 재빌드
docker compose up -d --no-deps --build api-gateway

# 설정 검증
docker compose config

# 로그 집중 확인
docker compose logs -f --tail=50 api-gateway

# 리소스 정리
docker compose down -v  # 볼륨까지 제거

프라이빗 레지스트리 구축

Registry 서버 배포

# 공식 Registry 이미지 실행
docker run -d \
  --name private-registry \
  -p 5000:5000 \
  -v /opt/registry-data:/var/lib/registry \
  -e REGISTRY_STORAGE_DELETE_ENABLED=true \
  --restart always \
  registry:2

# 클라이언트 신뢰 설정 (HTTP 허용)
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "insecure-registries": ["registry.internal.example.com:5000"]
}
EOF

이미지 푸시/풀 흐름

# 1. 로컬 이미지에 레지스트리 경로 태그 부여
docker tag myapp:2.1.0 registry.internal.example.com:5000/team-alpha/myapp:2.1.0

# 2. 인증
docker login registry.internal.example.com:5000 -u deployer -p $TOKEN

# 3. 업로드
docker push registry.internal.example.com:5000/team-alpha/myapp:2.1.0

# 4. 다른 환경에서 다운로드
docker pull registry.internal.example.com:5000/team-alpha/myapp:2.1.0

# 5. API로 저장소 목록 확인
curl -s http://registry.internal.example.com:5000/v2/_catalog | jq .

이미지 빌드 최적화 전략

레이어 캐시 활용

# ❌ 비효율적: 소스 변경 시 dependency도 재설치
COPY . /app
RUN npm install

# ✅ 효율적: package 파일 변경 시에만 npm install 실행
COPY package*.json /app/
RUN npm ci --only=production
COPY src /app/src
RUN npm run build

멀티스테이지 빌드로 이미지 경량화

스테이지목적결과 크기
단일 이미지 (빌드+실행)개발 편의성1.2 GB
멀티스테이지 분리빌드 도구 제외180 MB
Distroless/Alpine 기반최소 런타임만 포함45 MB

보안 고려사항

  • 루트 사용자 대신 USER 지시어로 권한 하향
  • 민감 정보는 빌드 인수(ARG)가 아닌 런타인 환경변수(ENV) 또는 외부 시크릿 관리 도구 사용
  • .dockerignore로 불필요한 파일 제외
  • 취약점 스캔: docker scan 또는 Trivy, Snyk 연동

태그: docker container Linux Container DevOps CI/CD

7월 6일 03:20에 게시됨