Post

Docker

Docker

목차

Part 1: Docker 기초

  1. Docker 개요
  2. Docker 이미지
  3. Dockerfile 작성

Part 2: Docker 컨테이너

  1. Docker 컨테이너 관리
  2. Docker 네트워킹
  3. Docker 볼륨과 스토리지

Part 3: Docker 고급

  1. Docker Compose
  2. Docker 레지스트리
  3. Docker 보안
  4. Docker 모니터링 및 로깅

Part 1: Docker 기초

1. Docker 개요

1.1 Docker란?

Docker의 정의

Docker는 컨테이너 기반 애플리케이션 개발, 배포, 실행 플랫폼이다.

Docker의 주요 가치

  • Build once, run anywhere: 한 번 빌드하면 어디서나 실행
  • Isolation: 애플리케이션 간 격리
  • Portability: 환경 독립적 실행
  • Lightweight: VM보다 가벼운 리소스 사용
  • Fast: 빠른 시작과 배포

1.2 Docker 아키텍처

Docker 전체 아키텍처

Docker는 여러 컴포넌트가 계층적으로 구성된 클라이언트-서버 아키텍처다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
┌─────────────────────────────────────────────────────────┐
│                   Docker Client                         │
│  (docker CLI: docker run, docker build, docker ps ...)  │
└────────────────────┬────────────────────────────────────┘
                     │ REST API / Unix Socket
                     │ (/var/run/docker.sock)
                     ↓
┌─────────────────────────────────────────────────────────┐
│                  Docker Daemon (dockerd)                │
│  ┌──────────────────────────────────────────────────┐   │
│  │  - 이미지 관리 (pull, push, build)               │   │
│  │  - 네트워크 관리 (bridge, overlay, host)         │   │
│  │  - 볼륨 관리 (local, nfs, etc)                   │   │
│  │  - API 서버                                      │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │ gRPC
                     ↓
┌─────────────────────────────────────────────────────────┐
│                    containerd                           │
│  ┌──────────────────────────────────────────────────┐   │
│  │  - 컨테이너 생명주기 관리 (create, start, stop)  │   │
│  │  - 이미지 pull/push                              │   │
│  │  - 스냅샷 관리                                   │   │
│  │  - 네트워크 연결                                 │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │ Shim API
                     ↓
┌─────────────────────────────────────────────────────────┐
│              containerd-shim (프로세스별)                │
│  ┌──────────────────────────────────────────────────┐   │
│  │  - runc의 부모 프로세스                          │   │
│  │  - STDIO, 시그널 전달                           │   │
│  │  - 컨테이너 종료 후 정리                        │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │ exec
                     ↓
┌─────────────────────────────────────────────────────────┐
│                  runc (컨테이너별)                       │
│  ┌──────────────────────────────────────────────────┐   │
│  │  - Namespace 생성 (PID, Net, Mount, UTS...)     │   │
│  │  - Cgroup 설정 (CPU, Memory 제한)               │   │
│  │  - Rootfs 마운트 (OverlayFS)                    │   │
│  │  - 컨테이너 프로세스 실행 (PID 1)               │   │
│  └──────────────────────────────────────────────────┘   │
└────────────────────┬────────────────────────────────────┘
                     │
                     ↓
               ┌──────────┐
               │Container │
               │ Process  │
               └──────────┘

Docker 실행 흐름 (docker run의 내부 동작)

사용자가 docker run nginx를 실행할 때:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
1. Docker Client
   └─> docker run nginx 명령 파싱
   └─> REST API 요청 생성: POST /containers/create
   └─> Unix socket으로 dockerd에 전송

2. Docker Daemon (dockerd)
   └─> 요청 수신 및 검증
   └─> 이미지 존재 확인
        ├─ 없으면: Registry에서 pull (docker.io/library/nginx:latest)
        └─ 있으면: 다음 단계
   └─> 네트워크 할당 (기본: bridge)
   └─> 볼륨 준비
   └─> gRPC로 containerd에 컨테이너 생성 요청

3. containerd
   └─> 이미지 스냅샷 준비 (OverlayFS 레이어 설정)
   └─> 컨테이너 번들 생성 (config.json, rootfs)
   └─> containerd-shim 프로세스 생성
   └─> shim에게 runc 실행 지시

4. containerd-shim
   └─> runc 실행: runc create <container-id>
   └─> runc이 종료되어도 컨테이너 유지 (daemonless 컨테이너)
   └─> STDIO 스트림 관리
   └─> 종료 시그널 전달

5. runc
   └─> Namespace 생성 (clone() 시스템 콜)
        ├─ PID Namespace: 독립 프로세스 트리
        ├─ Network Namespace: 독립 네트워크 스택
        ├─ Mount Namespace: 독립 파일시스템
        ├─ UTS Namespace: 독립 호스트명
        ├─ IPC Namespace: 독립 IPC
        ├─ User Namespace: UID/GID 매핑 (선택)
        └─ Cgroup Namespace: Cgroup 격리
   └─> Cgroup 설정 (/sys/fs/cgroup/...)
        ├─ CPU: cpu.cfs_quota_us, cpu.cfs_period_us
        ├─ Memory: memory.limit_in_bytes
        └─ Block I/O: blkio.throttle.read_bps_device
   └─> Rootfs 마운트 (OverlayFS)
        ├─ LowerDir: 이미지 레이어 (읽기 전용)
        ├─ UpperDir: 컨테이너 쓰기 레이어
        └─> MergedDir: 통합 파일시스템
   └─> Capabilities 설정 (CAP_NET_BIND_SERVICE 등)
   └─> Seccomp 필터 적용 (시스템 콜 제한)
   └─> pivot_root() 또는 chroot()
   └─> execve(): nginx 프로세스 실행 (PID 1)

6. 컨테이너 프로세스
   └─> nginx 실행 (컨테이너 내부 PID 1)
   └─> 호스트에서는 다른 PID (예: 12345)

각 컴포넌트의 역할 상세

1. Docker Client (docker CLI)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 역할:
- 사용자 명령을 REST API 요청으로 변환
- dockerd와 Unix socket 또는 TCP로 통신
- 원격 dockerd 접근 가능 (DOCKER_HOST 환경변수)

# 예시:
docker run nginx
  ↓
POST /v1.41/containers/create
{
  "Image": "nginx",
  "HostConfig": { "NetworkMode": "bridge" }
}

2. Docker Daemon (dockerd)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 역할:
- Docker API 서버
- 고수준 컨테이너 관리 (이미지, 네트워크, 볼륨)
- Registry와 통신 (이미지 pull/push)
- containerd에 저수준 작업 위임

# 책임:
- Dockerfile 빌드 (buildkit 사용)
- Docker Compose 지원
- Docker Swarm 오케스트레이션 (선택)
- 플러그인 시스템 (네트워크, 볼륨 드라이버)

# 설정 파일:
/etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

3. containerd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 역할:
- 업계 표준 컨테이너 런타임
- Docker 외에도 Kubernetes에서 직접 사용 가능
- 이미지 관리 (pull, push, 스냅샷)
- 컨테이너 생명주기 (create, start, stop, delete)

# CRI 플러그인:
- Kubernetes가 containerd를 직접 호출 가능
- dockerd 없이 Kubernetes 실행 가능 (경량화)

# 명령어:
ctr images pull docker.io/library/nginx:latest
ctr run --rm -t docker.io/library/nginx:latest nginx-test

# 데이터 위치:
/var/lib/containerd/
├── io.containerd.content.v1.content/  # 이미지 레이어 (blob)
├── io.containerd.snapshotter.v1.overlayfs/  # 스냅샷 (레이어 관리)
└── io.containerd.metadata.v1.bolt/  # 메타데이터 (bolt DB)

4. containerd-shim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 역할:
- runc와 containerd 사이의 중간 프로세스
- Daemonless 컨테이너: dockerd/containerd 재시작 시에도 컨테이너 유지
- runc는 컨테이너 시작 후 종료되지만 shim은 계속 실행

# 책임:
1. STDIO 스트림 관리 (docker logs)
2. 종료 시그널 전달 (docker stop → SIGTERM)
3. 종료 상태 보고 (exit code)
4. TTY 관리 (docker exec -it)

# 프로세스 트리:
dockerd (PID 1000)
  └─> containerd (PID 1100)
      └─> containerd-shim (PID 1200, 컨테이너 A)
      │    └─> nginx (PID 1201, 컨테이너 A 내부 PID 1)
      └─> containerd-shim (PID 1300, 컨테이너 B)
           └─> redis (PID 1301, 컨테이너 B 내부 PID 1)

# containerd 재시작해도 shim과 nginx는 계속 실행됨!

5. runc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 역할:
- OCI Runtime Specification 구현
- 실제 리눅스 커널 기능 호출 (Namespace, Cgroup)
- 컨테이너 프로세스 실행 후 자신은 종료

# 동작 단계:
runc create <container-id>  # Namespace/Cgroup 설정
runc start <container-id>   # 컨테이너 프로세스 시작
runc delete <container-id>  # 정리

# OCI 번들 구조:
/var/run/docker/containerd/<container-id>/
├── config.json  # OCI 설정 (Namespace, Cgroup, Capabilities)
└── rootfs/      # 컨테이너 루트 파일시스템 (OverlayFS 마운트)

# config.json 예시:
{
  "ociVersion": "1.0.0",
  "process": {
    "args": ["nginx", "-g", "daemon off;"],
    "cwd": "/",
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:..."]
  },
  "root": {
    "path": "rootfs",
    "readonly": true  # 이미지는 읽기 전용, UpperDir로 쓰기
  },
  "linux": {
    "namespaces": [
      {"type": "pid"},
      {"type": "network"},
      {"type": "mount"},
      {"type": "uts"}
    ],
    "resources": {
      "memory": {"limit": 536870912},  # 512MB
      "cpu": {"quota": 50000, "period": 100000}  # 50% CPU
    },
    "capabilities": [
      "CAP_CHOWN",
      "CAP_NET_BIND_SERVICE"
    ]
  }
}

Docker vs containerd vs runc 비교

컴포넌트레벨역할사용자
dockerd고수준이미지 빌드, 네트워크, 볼륨, ComposeDocker CLI
containerd중간컨테이너 생명주기, 이미지 관리dockerd, Kubernetes (CRI)
runc저수준Namespace/Cgroup 생성, 프로세스 실행containerd (via shim)

Kubernetes에서 Docker 제거 (Dockershim Deprecation)

1
2
3
4
5
6
7
8
9
10
[이전] Kubernetes + Docker:
kubelet → dockerd → containerd → runc

[현재] Kubernetes + containerd (직접):
kubelet → containerd (CRI 플러그인) → runc

이유:
- dockerd는 Kubernetes에 불필요한 기능 포함 (빌드, Compose 등)
- containerd가 CRI를 직접 구현하여 중간 단계 제거
- 더 빠르고 경량화

Docker Objects (리소스)

Images (이미지):

  • 읽기 전용 템플릿
  • 레이어드 파일시스템 (OverlayFS)
  • Dockerfile로 빌드
  • Registry에 저장 (Docker Hub, ECR, GCR 등)

Containers (컨테이너):

  • 이미지의 실행 가능한 인스턴스
  • 읽기/쓰기 레이어 추가 (UpperDir)
  • 독립된 Namespace와 Cgroup
  • Stateless (기본), Volume으로 상태 유지

Networks (네트워크):

  • bridge: 기본, 단일 호스트
  • host: 호스트 네트워크 직접 사용
  • overlay: 다중 호스트 (Swarm)
  • macvlan: MAC 주소 할당

Volumes (볼륨):

  • 영구 데이터 저장
  • 컨테이너 삭제 후에도 유지
  • 드라이버: local, nfs, cifs, etc.

Docker Registry (이미지 저장소)

1
2
3
4
5
6
7
8
9
10
Docker Hub (공개):
  - docker pull nginx
  - docker push myuser/myapp:v1.0

프라이빗 레지스트리:
  - AWS ECR: <account-id>.dkr.ecr.<region>.amazonaws.com/myapp
  - GCR: gcr.io/<project-id>/myapp
  - Harbor: harbor.example.com/myproject/myapp
  - Docker Registry (오픈소스):
      docker run -d -p 5000:5000 registry:2

면접 질문 예시

Q1: “docker run 명령이 실행되는 전체 흐름을 설명하라”

A:

  1. Docker Client가 명령을 REST API로 변환 → dockerd
  2. dockerd가 이미지 확인 (없으면 pull), 네트워크/볼륨 준비 → containerd에 gRPC 요청
  3. containerd가 이미지 스냅샷 준비, containerd-shim 생성
  4. shim이 runc 실행 지시
  5. runc가 Namespace/Cgroup 생성, OverlayFS 마운트, 컨테이너 프로세스 실행 (PID 1)
  6. runc 종료, shim이 컨테이너 관리 유지

Q2: “containerd-shim의 역할은 무엇인가?”

A:

  • runc와 containerd 사이의 중간 프로세스
  • Daemonless 컨테이너: dockerd/containerd 재시작 시에도 컨테이너 유지
  • STDIO 관리 (docker logs), 시그널 전달 (docker stop), 종료 상태 보고
  • runc는 컨테이너 시작 후 종료되지만 shim은 계속 실행되어 컨테이너의 부모 프로세스 역할

Q3: “Kubernetes가 Docker를 제거한 이유는?”

A:

  • dockerd는 Kubernetes에 불필요한 기능 포함 (빌드, Compose, Swarm 등)
  • containerd가 CRI 플러그인으로 Kubernetes와 직접 통신 가능
  • kubelet → dockerd → containerd → runc 대신, kubelet → containerd → runc로 단순화
  • 더 빠르고 경량화, 메모리 사용량 감소

1.3 Docker 설치

Linux (Ubuntu)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 오래된 버전 제거
sudo apt-get remove docker docker-engine docker.io containerd runc

# 필수 패키지 설치
sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# Docker 공식 GPG 키 추가
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Repository 추가
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker 설치
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 설치 확인
sudo docker run hello-world

일반 사용자 권한 부여

1
2
3
4
5
6
7
8
9
# docker 그룹에 사용자 추가
sudo usermod -aG docker $USER

# 로그아웃 후 재로그인
# 또는 현재 세션에 적용
newgrp docker

# 권한 확인
docker run hello-world

1.4 Docker 기본 명령어

이미지 관련

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 이미지 검색
docker search nginx

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

# 이미지 목록
docker images
docker image ls

# 이미지 삭제
docker rmi nginx
docker image rm nginx

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

컨테이너 관련

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 컨테이너 실행
docker run nginx
docker run -d nginx  # 백그라운드
docker run -it ubuntu bash  # 대화형

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

# 모든 컨테이너 (중지된 것 포함)
docker ps -a
docker container ls -a

# 컨테이너 중지
docker stop <container>

# 컨테이너 시작
docker start <container>

# 컨테이너 재시작
docker restart <container>

# 컨테이너 삭제
docker rm <container>

# 강제 삭제 (실행 중이어도)
docker rm -f <container>

시스템 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 디스크 사용량
docker system df

# 전체 정리 (사용하지 않는 모든 리소스)
docker system prune

# 모든 리소스 강제 정리 (볼륨 포함)
docker system prune -a --volumes

# 로그 확인
docker logs <container>
docker logs -f <container>  # 실시간

# 리소스 사용량 모니터링
docker stats
docker stats <container>

2. Docker 이미지

2.1 이미지 개념

레이어 기반 아키텍처

Docker 이미지는 여러 읽기 전용 레이어로 구성된다:

1
2
3
4
5
6
7
8
9
10
11
Image: nginx:latest
    ↓
Layer 5: nginx 설정 파일
    ↓
Layer 4: nginx 설치
    ↓
Layer 3: 패키지 업데이트
    ↓
Layer 2: 베이스 파일시스템
    ↓
Layer 1: 커널 부트 파일시스템

OverlayFS (Union Mount)

  • 여러 레이어를 단일 디렉토리 구조로 제공
  • 읽기 전용 베이스 레이어와 쓰기 가능한 컨테이너 레이어 분리
  • 효율적인 메모리 사용

이미지 레이어 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 이미지 히스토리
docker history nginx:latest

# 각 레이어의 크기와 생성 명령 확인
docker history --no-trunc nginx:latest

# 이미지 inspect
docker inspect nginx:latest

# JSON 형식으로 모든 메타데이터 출력
# - 레이어 정보
# - 환경 변수
# - 포트 설정
# - 볼륨 마운트 포인트
# - 엔트리포인트/CMD

2.2 이미지 레지스트리

Docker Hub

공식 공개 레지스트리:

1
2
3
4
5
6
7
8
# Docker Hub에서 pull (기본)
docker pull nginx

# 명시적 지정
docker pull docker.io/library/nginx

# 사용자 이미지
docker pull username/myimage

프라이빗 레지스트리

1
2
3
4
5
6
7
8
9
# 로그인
docker login myregistry.com
# Username, Password 입력

# 프라이빗 레지스트리에서 pull
docker pull myregistry.com/myapp:latest

# 로그아웃
docker logout myregistry.com

Harbor, GitLab Registry 등

엔터프라이즈 레지스트리:

  • 접근 제어
  • 이미지 스캐닝
  • 복제 및 백업
  • 취약점 관리

2.3 이미지 태그

태그의 개념

태그는 이미지의 버전을 식별하는 레이블이다.

태그 형식

1
2
3
4
5
6
7
[registry/][namespace/]repository[:tag]

예시:
nginx:latest
nginx:1.25
docker.io/library/nginx:1.25-alpine
myregistry.com/myapp:v1.0.0

태그 규칙

1
2
3
4
5
6
7
8
9
10
11
12
# 태그 없이 pull하면 latest
docker pull nginx
# = docker pull nginx:latest

# 특정 버전
docker pull nginx:1.25

# Alpine 기반 (경량)
docker pull nginx:alpine

# 특정 OS/아키텍처
docker pull nginx:1.25-alpine-arm64

이미지 태깅 및 공유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 로컬 이미지에 태그 추가
docker tag nginx:latest myregistry.com/nginx:v1.0

# 여러 태그 가능
docker tag nginx:latest nginx:production
docker tag nginx:latest nginx:stable

# Docker Hub에 푸시
docker login
docker tag myapp:latest username/myapp:latest
docker push username/myapp:latest

# 프라이빗 레지스트리에 푸시
docker login myregistry.com
docker tag myapp:latest myregistry.com/myapp:latest
docker push myregistry.com/myapp:latest

이미지 저장/로드

1
2
3
4
5
6
7
8
9
10
11
12
# 이미지를 tar 파일로 저장
docker save -o myapp.tar myapp:latest

# tar 파일에서 이미지 로드
docker load -i myapp.tar

# 여러 이미지 저장
docker save -o images.tar nginx:latest alpine:latest

# 압축
docker save myapp:latest | gzip > myapp.tar.gz
gunzip -c myapp.tar.gz | docker load

이미지 익스포트/임포트 (컨테이너)

1
2
3
4
5
6
7
8
9
10
11
12
# 실행 중인 컨테이너를 이미지로
docker commit <container> myapp:snapshot

# 컨테이너를 tar로 익스포트
docker export <container> -o container.tar

# tar에서 이미지 생성
docker import container.tar myapp:imported

# 차이점:
# - save/load: 이미지 레이어 보존
# - export/import: 단일 레이어로 압축

2.4 이미지 빌드

docker build 기본

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 현재 디렉토리의 Dockerfile 사용
docker build .

# 태그 지정
docker build -t myapp:latest .

# 특정 Dockerfile 지정
docker build -f Dockerfile.prod -t myapp:prod .

# 빌드 컨텍스트 제외 (.dockerignore)
# .dockerignore 파일 작성:
# node_modules
# .git
# *.log

빌드 캐시

Docker는 레이어를 캐시하여 빌드 속도를 향상시킨다:

1
2
3
4
5
# 캐시 무시하고 빌드
docker build --no-cache -t myapp:latest .

# 특정 레이어부터 다시 빌드
# (Dockerfile 수정 시 자동)

멀티 스테이지 빌드

빌드 단계를 분리하여 최종 이미지 크기 축소:

1
2
3
4
5
6
7
8
9
10
11
12
# 빌드 스테이지
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 실행 스테이지
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]

# 최종 이미지는 alpine만 포함 (작음)

BuildKit

차세대 빌드 엔진:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BuildKit 활성화
export DOCKER_BUILDKIT=1

# 또는 /etc/docker/daemon.json
{
  "features": {
    "buildkit": true
  }
}

# 장점:
# - 병렬 빌드
# - 빌드 캐시 개선
# - 비밀 정보 안전 처리
# - 더 나은 성능

3. Dockerfile 작성

3.1 Dockerfile 기본 구조

Dockerfile이란?

Dockerfile은 Docker 이미지를 빌드하는 명령어 스크립트이다.

기본 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 베이스 이미지
FROM node:18-alpine

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 복사
COPY package*.json ./

# 의존성 설치
RUN npm install --production

# 애플리케이션 코드 복사
COPY . .

# 포트 노출
EXPOSE 3000

# 실행 명령
CMD ["node", "server.js"]

3.2 Dockerfile 주요 명령어

FROM - 베이스 이미지

1
2
3
4
5
6
7
8
9
10
11
# 공식 이미지
FROM ubuntu:22.04

# 경량 이미지
FROM alpine:3.18

# 멀티 스테이지의 특정 스테이지
FROM node:18 AS builder

# Scratch (빈 이미지)
FROM scratch

WORKDIR - 작업 디렉토리

1
2
3
4
5
6
7
8
9
10
# 작업 디렉토리 설정
WORKDIR /app

# 이후 명령은 모두 /app에서 실행
# 디렉토리가 없으면 자동 생성

# 중첩 가능
WORKDIR /app
WORKDIR src
# 현재 위치: /app/src

COPY vs ADD

1
2
3
4
5
6
7
8
9
10
11
12
13
# COPY: 단순 파일/디렉토리 복사
COPY package.json /app/
COPY src/ /app/src/

# ADD: 복사 + 추가 기능
ADD https://example.com/file.tar.gz /app/
# URL에서 다운로드

ADD archive.tar.gz /app/
# 자동 압축 해제

# 권장: 일반적으로 COPY 사용
# ADD는 특별한 경우에만

RUN - 명령 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Shell 형식
RUN apt-get update && apt-get install -y nginx

# Exec 형식
RUN ["apt-get", "update"]

# 여러 명령을 하나로 (레이어 최소화)
RUN apt-get update && \
    apt-get install -y \
      nginx \
      curl && \
    rm -rf /var/lib/apt/lists/*

# 각 RUN은 새 레이어 생성
# 최적화: 관련 명령을 하나의 RUN으로

ENV - 환경 변수

1
2
3
4
5
6
7
8
9
10
# 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000

# 여러 변수 한번에
ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info

# 빌드 시 및 런타임에 모두 사용

ARG - 빌드 인수

1
2
3
4
5
6
7
8
9
10
# 빌드 시에만 사용하는 변수
ARG VERSION=latest
ARG BUILD_DATE

# 사용
FROM node:${VERSION}
LABEL build-date=${BUILD_DATE}

# 빌드 시 전달
# docker build --build-arg VERSION=18 .

EXPOSE - 포트 노출

1
2
3
4
5
6
7
# 포트 노출 (문서화 목적)
EXPOSE 80
EXPOSE 443/tcp
EXPOSE 53/udp

# 실제 포트 매핑은 실행 시:
# docker run -p 8080:80 myapp

CMD vs ENTRYPOINT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# CMD: 기본 명령 (덮어쓰기 가능)
CMD ["nginx", "-g", "daemon off;"]

# 실행 시 덮어쓰기:
# docker run myapp echo "hello"

# ENTRYPOINT: 항상 실행되는 명령
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 실행 시:
# docker run myapp -c /custom/nginx.conf
# = nginx -c /custom/nginx.conf

# 조합:
# ENTRYPOINT: 실행 파일
# CMD: 기본 인수

VOLUME - 볼륨 마운트 포인트

1
2
3
4
5
# 볼륨 마운트 포인트 선언
VOLUME /data
VOLUME ["/var/log", "/var/db"]

# 컨테이너 실행 시 익명 볼륨 자동 생성

USER - 실행 사용자

1
2
3
4
5
6
7
8
9
# 사용자 생성
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup

# 사용자 전환
USER appuser

# 이후 명령은 appuser로 실행
# 보안 모범 사례: root 사용 지양

LABEL - 메타데이터

1
2
3
4
5
6
7
8
9
# 메타데이터 추가
LABEL version="1.0"
LABEL description="My Application"
LABEL maintainer="devops@example.com"

# 여러 레이블
LABEL version="1.0" \
      description="My Application" \
      maintainer="devops@example.com"

3.3 Dockerfile 최적화

레이어 최소화

1
2
3
4
5
6
7
8
9
10
11
12
13
# 나쁜 예: 레이어가 많음
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get install -y curl
RUN apt-get clean

# 좋은 예: 하나의 레이어
RUN apt-get update && \
    apt-get install -y \
      nginx \
      curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

캐시 활용

1
2
3
4
5
6
7
8
9
10
# 나쁜 예: 코드 변경 시 의존성도 재설치
COPY . /app
RUN npm install

# 좋은 예: 의존성 파일만 먼저 복사
COPY package*.json /app/
RUN npm install
COPY . /app

# package.json 변경 시에만 npm install 재실행

베이스 이미지 선택

1
2
3
4
5
6
7
8
9
10
11
12
# 크기 비교:
# node:18 (약 1GB)
FROM node:18

# node:18-slim (약 200MB)
FROM node:18-slim

# node:18-alpine (약 170MB)
FROM node:18-alpine

# Distroless (최소, 보안 강화)
FROM gcr.io/distroless/nodejs18

불필요한 파일 제외 (.dockerignore)

1
2
3
4
5
6
7
8
9
10
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.DS_Store
*.md
tests/
docs/

3.4 Dockerfile 보안 모범 사례

최소 권한 원칙

1
2
3
4
5
6
7
8
# 나쁜 예: root로 실행
FROM nginx

# 좋은 예: 일반 사용자
FROM nginx
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup
USER appuser

민감 정보 제외

1
2
3
4
5
6
7
# 나쁜 예: 비밀키 포함
ENV API_KEY=secret123

# 좋은 예: 런타임에 주입
# docker run -e API_KEY=secret123 myapp

# 또는 Docker Secrets 사용

최신 패치

1
2
3
4
5
6
7
# 베이스 이미지 업데이트
FROM alpine:3.18

# 시스템 패키지 업데이트
RUN apk update && \
    apk upgrade && \
    rm -rf /var/cache/apk/*

취약점 스캔

1
2
3
4
5
# Trivy로 이미지 스캔
trivy image myapp:latest

# Docker Scout (Docker Desktop)
docker scout cves myapp:latest

Part 2: Docker 컨테이너

4. Docker 컨테이너 관리

4.1 컨테이너 생명주기

컨테이너 상태

  • Created: 생성됨, 아직 시작 안 함
  • Running: 실행 중
  • Paused: 일시 정지
  • Stopped: 중지됨
  • Deleted: 삭제됨

생명주기 명령

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 생성 (시작 안 함)
docker create nginx

# 생성 + 시작
docker run nginx

# 시작
docker start <container>

# 중지 (SIGTERM, 10초 후 SIGKILL)
docker stop <container>

# 강제 중지 (SIGKILL)
docker kill <container>

# 재시작
docker restart <container>

# 일시 정지
docker pause <container>

# 재개
docker unpause <container>

# 삭제
docker rm <container>

# 강제 삭제
docker rm -f <container>

4.2 docker run 옵션

기본 실행

1
2
3
4
5
6
7
8
9
10
11
# 포그라운드 실행
docker run nginx

# 백그라운드 실행 (-d, --detach)
docker run -d nginx

# 컨테이너 이름 지정
docker run --name mynginx nginx

# 실행 후 자동 삭제 (--rm)
docker run --rm nginx

대화형 모드

1
2
3
4
5
6
7
8
9
10
11
# 대화형 모드 (-it)
docker run -it ubuntu bash

# -i: STDIN 유지
# -t: TTY 할당

# 실행 중인 컨테이너에 접속
docker exec -it mynginx bash

# 특정 사용자로 실행
docker exec -it -u root mynginx bash

포트 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 단일 포트
docker run -p 8080:80 nginx

# 여러 포트
docker run -p 8080:80 -p 4443:443 nginx

# 호스트 IP 지정
docker run -p 127.0.0.1:8080:80 nginx

# UDP 포트
docker run -p 53:53/udp dns-server

# 호스트 포트 자동 할당
docker run -p 80 nginx

환경 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
# 단일 환경 변수
docker run -e NODE_ENV=production myapp

# 여러 환경 변수
docker run -e NODE_ENV=production -e PORT=3000 myapp

# 파일에서 읽기
docker run --env-file .env myapp

# .env 파일 형식:
# NODE_ENV=production
# PORT=3000
# DB_HOST=db.example.com

볼륨 마운트

1
2
3
4
5
6
7
8
9
10
11
# Named 볼륨
docker run -v myvolume:/data nginx

# Bind mount
docker run -v /host/path:/container/path nginx

# 읽기 전용
docker run -v myvolume:/data:ro nginx

# tmpfs (메모리)
docker run --tmpfs /tmp nginx

네트워크

1
2
3
4
5
6
7
8
9
10
11
# 특정 네트워크
docker run --network mynetwork nginx

# 호스트 네트워크
docker run --network host nginx

# 네트워크 없음
docker run --network none nginx

# 네트워크 별칭
docker run --network mynetwork --network-alias db postgres

리소스 제한

1
2
3
4
5
6
7
8
9
10
11
12
# 메모리 제한
docker run -m 512m nginx
docker run --memory=512m nginx

# CPU 제한
docker run --cpus=1.5 nginx

# CPU 공유
docker run --cpu-shares=512 nginx

# 블록 I/O
docker run --device-write-bps=/dev/sda:10mb nginx

재시작 정책

1
2
3
4
5
6
7
8
9
10
11
# 항상 재시작
docker run --restart=always nginx

# 실패 시에만 재시작
docker run --restart=on-failure nginx

# 최대 재시작 횟수
docker run --restart=on-failure:3 nginx

# 명시적으로 중지하지 않으면 재시작
docker run --restart=unless-stopped nginx

4.3 컨테이너 모니터링

로그 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 로그 출력
docker logs <container>

# 실시간 로그 (tail -f)
docker logs -f <container>

# 마지막 N줄
docker logs --tail 100 <container>

# 타임스탬프 포함
docker logs -t <container>

# 특정 시간 이후
docker logs --since 2023-01-01T00:00:00 <container>
docker logs --since 1h <container>

리소스 사용량

1
2
3
4
5
6
7
8
9
10
11
# 실시간 통계
docker stats

# 특정 컨테이너
docker stats <container>

# 스트리밍 없이 한 번만
docker stats --no-stream

# 출력:
# CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O

프로세스 확인

1
2
3
4
5
6
7
8
# 컨테이너 내 프로세스
docker top <container>

# ps aux 형식
docker top <container> aux

# 실행 중인 프로세스 확인
docker exec <container> ps aux

파일 시스템 변경사항

1
2
3
4
5
6
7
# 이미지 대비 변경된 파일
docker diff <container>

# 출력:
# A: 추가된 파일
# C: 변경된 파일
# D: 삭제된 파일

이벤트 모니터링

1
2
3
4
5
6
7
8
9
# 실시간 Docker 이벤트
docker events

# 특정 컨테이너
docker events --filter container=<container>

# 특정 이벤트 타입
docker events --filter event=start
docker events --filter event=stop

4.4 컨테이너 디버깅

실행 중인 컨테이너 접속

1
2
3
4
5
6
7
8
9
# Bash 실행
docker exec -it <container> bash

# sh 실행 (Alpine 등)
docker exec -it <container> sh

# 특정 명령 실행
docker exec <container> cat /etc/hosts
docker exec <container> env

컨테이너 inspect

1
2
3
4
5
6
7
# 전체 정보 (JSON)
docker inspect <container>

# 특정 필드 추출
docker inspect --format='{{.State.Status}}' <container>
docker inspect --format='{{.NetworkSettings.IPAddress}}' <container>
docker inspect --format='{{json .Config.Env}}' <container> | jq

파일 복사

1
2
3
4
5
6
7
8
# 컨테이너 → 호스트
docker cp <container>:/path/to/file ./local/path

# 호스트 → 컨테이너
docker cp ./local/file <container>:/path/to/

# 디렉토리 복사
docker cp <container>:/app ./app-backup

네트워크 디버깅

1
2
3
4
5
6
7
8
9
10
11
# 네트워크 정보 확인
docker exec <container> ip addr
docker exec <container> ip route

# 연결 테스트
docker exec <container> ping google.com
docker exec <container> curl http://api.example.com

# DNS 확인
docker exec <container> nslookup google.com
docker exec <container> cat /etc/resolv.conf

헬스체크

1
2
3
# Dockerfile에서 헬스체크 정의
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost/ || exit 1
1
2
3
4
5
6
7
8
9
# 실행 시 헬스체크 추가
docker run --health-cmd='curl -f http://localhost/ || exit 1' \
           --health-interval=30s \
           --health-timeout=3s \
           --health-retries=3 \
           nginx

# 헬스 상태 확인
docker inspect --format='{{.State.Health.Status}}' <container>

4.5 컨테이너 정리

중지된 컨테이너 삭제

1
2
3
4
5
6
7
8
# 모든 중지된 컨테이너 삭제
docker container prune

# 확인 없이 삭제
docker container prune -f

# 특정 레이블 필터
docker container prune --filter "label=env=test"

자동 삭제 (–rm)

1
2
3
4
5
# 종료 시 자동 삭제
docker run --rm nginx

# 임시 작업에 유용
docker run --rm -it ubuntu bash

대량 정리

1
2
3
4
5
# 실행 중이지 않은 모든 컨테이너 삭제
docker ps -aq -f status=exited | xargs docker rm

# 특정 이미지의 모든 컨테이너 삭제
docker ps -a --filter ancestor=nginx -q | xargs docker rm -f

5. Docker 네트워킹

5.1 Docker 네트워크 드라이버

Bridge 네트워크 (기본)

Docker의 기본 네트워크 모드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 기본 브리지 네트워크
docker network inspect bridge

# 특징:
# - 네트워크: 172.17.0.0/16
# - 게이트웨이: 172.17.0.1 (docker0 브리지)
# - 컨테이너 간 IP로만 통신 (이름 해석 안 됨)

# 사용자 정의 브리지 생성
docker network create --driver bridge mybridge

# 서브넷 지정
docker network create --driver bridge \
  --subnet=192.168.100.0/24 \
  --gateway=192.168.100.1 \
  mybridge

# 장점:
# - 자동 DNS 해석 (컨테이너 이름으로 통신)
# - 네트워크 격리
# - 동적 연결/해제 가능

사용자 정의 브리지 vs 기본 브리지

1
2
3
4
5
6
7
8
9
10
# 기본 브리지
docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker exec web1 ping web2  # 실패 (IP로만 가능)

# 사용자 정의 브리지
docker network create mynet
docker run -d --name web1 --network mynet nginx
docker run -d --name web2 --network mynet nginx
docker exec web1 ping web2  # 성공 (DNS 해석됨)

Host 네트워크

컨테이너가 호스트의 네트워크 스택을 직접 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Host 네트워크로 실행
docker run --network host nginx

# 특징:
# - 최고 성능 (네트워크 오버헤드 없음)
# - 포트 매핑 불필요 (컨테이너 포트 = 호스트 포트)
# - 네트워크 격리 없음
# - 호스트의 모든 네트워크 인터페이스 접근

# 주의:
# - 여러 컨테이너가 같은 포트 사용 불가
# - 보안 위험 (호스트 네트워크 노출)
# - Linux에서만 완전 지원 (Mac/Windows는 제한적)

None 네트워크

네트워크를 완전히 비활성화한다.

1
2
3
4
5
6
7
8
9
10
11
12
# None 네트워크로 실행
docker run --network none alpine

# 특징:
# - lo (루프백)만 존재
# - 외부 통신 완전 차단
# - 최대 격리

# 용도:
# - 네트워크가 필요 없는 배치 작업
# - 보안이 매우 중요한 작업
# - 커스텀 네트워크 설정

Overlay 네트워크

여러 Docker 호스트에 걸친 네트워크를 구성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Docker Swarm 초기화 필요
docker swarm init

# Overlay 네트워크 생성
docker network create \
  --driver overlay \
  --subnet 10.0.9.0/24 \
  myoverlay

# 서비스 배포
docker service create \
  --name web \
  --network myoverlay \
  --replicas 3 \
  nginx

# 특징:
# - 멀티 호스트 통신
# - 자동 서비스 디스커버리
# - 내장 로드 밸런싱
# - 암호화 가능 (--opt encrypted)

# Kubernetes에서는 CNI 플러그인 사용

Macvlan 네트워크

컨테이너에 고유한 MAC 주소를 할당한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Macvlan 네트워크 생성
docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  mymacvlan

# 컨테이너 실행
docker run --network mymacvlan \
  --ip=192.168.1.100 \
  nginx

# 특징:
# - 컨테이너가 물리 네트워크에 직접 연결된 것처럼 보임
# - 각 컨테이너가 고유 MAC 주소 가짐
# - 라우터/스위치가 컨테이너를 별도 호스트로 인식

# 용도:
# - 레거시 애플리케이션 (MAC 주소 기반 라이센싱)
# - 네트워크 모니터링 도구
# - DHCP 서버

# 주의:
# - 프로미스큐어스 모드 필요할 수 있음
# - 클라우드 환경에서는 지원 안 될 수 있음

5.2 네트워크 관리

네트워크 목록 및 정보

1
2
3
4
5
6
7
8
9
10
11
# 네트워크 목록
docker network ls

# 네트워크 상세 정보
docker network inspect bridge

# 특정 컨테이너의 네트워크 정보
docker inspect <container> --format '{{json .NetworkSettings.Networks}}' | jq

# 네트워크에 연결된 컨테이너 목록
docker network inspect mynet --format '{{range .Containers}}{{.Name}} {{end}}'

네트워크 생성 옵션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 기본 생성
docker network create mynet

# 드라이버 지정
docker network create --driver bridge mynet

# 서브넷 및 게이트웨이
docker network create \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  mynet

# IP 범위 지정
docker network create \
  --subnet 172.20.0.0/16 \
  --ip-range 172.20.240.0/20 \
  mynet

# IPv6 활성화
docker network create \
  --ipv6 \
  --subnet 2001:db8::/64 \
  mynet

# Internal 네트워크 (외부 통신 차단)
docker network create --internal mynet

# 레이블
docker network create \
  --label environment=production \
  mynet

컨테이너 네트워크 연결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 생성 시 네트워크 지정
docker run --network mynet nginx

# 실행 중인 컨테이너에 네트워크 추가
docker network connect mynet <container>

# IP 주소 지정
docker network connect --ip 172.20.0.10 mynet <container>

# 네트워크 별칭
docker network connect --alias db mynet postgres

# 네트워크 연결 해제
docker network disconnect mynet <container>

# 강제 연결 해제
docker network disconnect -f mynet <container>

네트워크 삭제

1
2
3
4
5
6
7
8
# 네트워크 삭제
docker network rm mynet

# 사용하지 않는 네트워크 정리
docker network prune

# 확인 없이 정리
docker network prune -f

5.3 포트 매핑 상세

포트 매핑 방식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 기본 매핑 (모든 인터페이스)
docker run -p 8080:80 nginx
# 0.0.0.0:8080 → 컨테이너:80

# 특정 IP에만 바인딩
docker run -p 127.0.0.1:8080:80 nginx
# localhost에서만 접근 가능

# 특정 IP 범위
docker run -p 192.168.1.10:8080:80 nginx

# UDP 포트
docker run -p 53:53/udp dns-server

# TCP와 UDP 모두
docker run -p 8080:80/tcp -p 53:53/udp myapp

# 포트 범위
docker run -p 8000-8010:8000-8010 myapp

# 호스트 포트 자동 할당
docker run -P nginx
# Dockerfile의 EXPOSE 포트를 랜덤 포트로 매핑

포트 확인

1
2
3
4
5
6
7
8
# 컨테이너의 포트 매핑 확인
docker port <container>

# 특정 포트 확인
docker port <container> 80

# 모든 매핑 정보
docker inspect <container> --format '' | jq

포트 매핑과 iptables

1
2
3
4
5
6
7
8
# Docker가 생성한 iptables 규칙 확인
sudo iptables -t nat -L -n

# DOCKER 체인 확인
sudo iptables -t nat -L DOCKER -n

# 예시 규칙:
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80

5.4 컨테이너 간 통신

같은 네트워크 내 통신

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 네트워크 생성
docker network create myapp-net

# 데이터베이스 컨테이너
docker run -d \
  --name db \
  --network myapp-net \
  -e POSTGRES_PASSWORD=secret \
  postgres

# 애플리케이션 컨테이너
docker run -d \
  --name web \
  --network myapp-net \
  -e DATABASE_URL=postgresql://postgres:secret@db:5432/mydb \
  myapp

# web에서 db로 접근
docker exec web ping db  # 성공
docker exec web psql -h db -U postgres  # 성공

다중 네트워크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 프론트엔드 네트워크
docker network create frontend

# 백엔드 네트워크
docker network create backend

# 웹 서버 (프론트엔드만)
docker run -d --name web --network frontend nginx

# API 서버 (양쪽 모두)
docker run -d --name api --network frontend myapi
docker network connect backend api

# 데이터베이스 (백엔드만)
docker run -d --name db --network backend postgres

# 결과:
# web → api (가능)
# api → db (가능)
# web → db (불가능, 격리됨)

네트워크 별칭

1
2
3
4
5
6
7
8
9
10
11
12
# 여러 별칭으로 참조
docker run -d \
  --network mynet \
  --network-alias db \
  --network-alias database \
  --network-alias postgres \
  postgres

# 모든 별칭으로 접근 가능
docker exec web ping db
docker exec web ping database
docker exec web ping postgres

서비스 디스커버리

1
2
3
4
5
6
7
8
# 같은 서비스의 여러 인스턴스
docker run -d --name web1 --network mynet --network-alias web nginx
docker run -d --name web2 --network mynet --network-alias web nginx
docker run -d --name web3 --network mynet --network-alias web nginx

# 라운드 로빈 DNS
# "web"을 조회하면 세 인스턴스의 IP가 번갈아 반환됨
docker exec client nslookup web

5.5 Docker DNS

내장 DNS 서버

Docker는 127.0.0.11에 내장 DNS 서버를 제공한다.

1
2
3
4
5
6
7
8
9
10
# 컨테이너 내 DNS 설정 확인
docker exec web cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0

# Docker DNS 서버가 다음을 해석:
# 1. 컨테이너 이름 → IP
# 2. 네트워크 별칭 → IP
# 3. 서비스 이름 → 여러 IP (라운드 로빈)
# 4. 외부 도메인 → 호스트 DNS로 전달

커스텀 DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 컨테이너별 DNS 서버 지정
docker run --dns 8.8.8.8 nginx

# 여러 DNS 서버
docker run --dns 8.8.8.8 --dns 8.8.4.4 nginx

# DNS 검색 도메인
docker run --dns-search example.com nginx
# ping server → server.example.com 시도

# DNS 옵션
docker run --dns-option ndots:2 nginx

# /etc/hosts 추가
docker run --add-host myhost:192.168.1.100 nginx

전역 DNS 설정

1
2
3
4
5
6
7
8
9
# /etc/docker/daemon.json
{
  "dns": ["8.8.8.8", "8.8.4.4"],
  "dns-search": ["example.com"],
  "dns-opts": ["ndots:2"]
}

# Docker 재시작
sudo systemctl restart docker

5.6 네트워크 트러블슈팅

연결 문제 진단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 컨테이너 IP 확인
docker inspect <container> --format '{{.NetworkSettings.IPAddress}}'

# 네트워크 인터페이스 확인
docker exec <container> ip addr

# 라우팅 테이블
docker exec <container> ip route

# 연결 테스트
docker exec <container> ping google.com
docker exec <container> ping 8.8.8.8

# 포트 열림 확인
docker exec <container> nc -zv db 5432

# DNS 확인
docker exec <container> nslookup google.com
docker exec <container> dig google.com

네트워크 디버깅 컨테이너

1
2
3
4
5
6
7
8
9
10
# nicolaka/netshoot 사용 (추천)
docker run -it --rm --network container:<container> nicolaka/netshoot

# 사용 가능한 도구:
# - tcpdump: 패킷 캡처
# - curl, wget: HTTP 테스트
# - nmap: 포트 스캔
# - iperf: 대역폭 테스트
# - traceroute: 경로 추적
# - mtr: 네트워크 진단

패킷 캡처

1
2
3
4
5
6
7
8
9
10
11
12
13
# 컨테이너 내부에서
docker exec <container> tcpdump -i eth0 -w /tmp/capture.pcap

# 호스트의 veth에서
# veth 인터페이스 찾기
docker exec <container> cat /sys/class/net/eth0/iflink
ip link | grep <번호>

# 캡처
sudo tcpdump -i <veth> -w capture.pcap

# Wireshark로 분석
wireshark capture.pcap

일반적인 문제와 해결

문제: 컨테이너가 외부와 통신 안 됨

1
2
3
4
5
6
7
8
9
10
11
12
# 1. IP 포워딩 확인
cat /proc/sys/net/ipv4/ip_forward
# 1이어야 함

# 2. NAT 규칙 확인
sudo iptables -t nat -L POSTROUTING -n

# 3. 방화벽 확인
sudo iptables -L -n

# 4. DNS 확인
docker exec <container> cat /etc/resolv.conf

문제: 컨테이너 간 통신 안 됨

1
2
3
4
5
6
7
8
9
# 1. 같은 네트워크인지 확인
docker network inspect <network>

# 2. ICC(Inter-Container Communication) 활성화 확인
docker network inspect <network> | grep "com.docker.network.bridge.enable_icc"
# true여야 함

# 3. 방화벽 규칙 확인
sudo iptables -L DOCKER-ISOLATION -n

문제: DNS 해석 실패

1
2
3
4
5
6
7
8
9
10
11
12
# 1. Docker DNS 작동 확인
docker exec <container> nslookup google.com 127.0.0.11

# 2. 호스트 DNS 확인
cat /etc/resolv.conf

# 3. 컨테이너 DNS 설정 확인
docker exec <container> cat /etc/resolv.conf

# 4. 네트워크 재생성
docker network rm <network>
docker network create <network>

6. Docker 볼륨과 스토리지

6.1 스토리지 옵션 비교

Docker는 데이터 영속성을 위해 세 가지 마운트 타입을 제공한다.

볼륨 (Volumes) - 권장

Docker가 관리하는 영구 저장소:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 볼륨 생성
docker volume create myvolume

# 볼륨 위치
ls /var/lib/docker/volumes/myvolume/_data

# 장점:
# - Docker가 완전히 관리
# - 백업/복원 용이
# - 플랫폼 독립적
# - 여러 컨테이너가 안전하게 공유 가능
# - 볼륨 드라이버로 원격 스토리지 지원

# 단점:
# - 호스트에서 직접 접근하기 어려움

Bind Mount

호스트 디렉토리를 직접 마운트:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Bind mount
docker run -v /host/path:/container/path nginx

# 장점:
# - 호스트 파일시스템에 직접 접근
# - 개발 시 편리 (코드 변경 즉시 반영)
# - 호스트의 모든 경로 사용 가능

# 단점:
# - 호스트 경로 의존성
# - 플랫폼 종속적
# - 보안 위험 (호스트 파일시스템 노출)
# - Docker가 관리하지 않음

tmpfs Mount

메모리에만 존재하는 임시 저장소:

1
2
3
4
5
6
7
8
9
10
11
12
# tmpfs mount
docker run --tmpfs /app/cache nginx

# 장점:
# - 매우 빠름 (메모리 속도)
# - 민감한 정보 임시 저장 (컨테이너 종료 시 자동 삭제)
# - 디스크 I/O 없음

# 단점:
# - 메모리 사용
# - 영구적이지 않음
# - Linux에서만 사용 가능

비교표

특성VolumeBind Mounttmpfs
Docker 관리OXO
백업 용이성OXX
성능좋음좋음매우 좋음
플랫폼 독립OXX
영구성OOX
개발 편의성중간높음낮음

6.2 볼륨 관리

볼륨 생성 및 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 볼륨 생성
docker volume create myvolume

# 드라이버 지정
docker volume create --driver local myvolume

# 옵션 지정
docker volume create \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/dir \
  nfs-volume

# 레이블
docker volume create --label env=prod myvolume

# 볼륨 목록
docker volume ls

# 볼륨 상세 정보
docker volume inspect myvolume

# 볼륨 사용 중인 컨테이너 확인
docker ps --filter volume=myvolume

볼륨 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 명명된 볼륨 마운트
docker run -v myvolume:/data nginx

# 익명 볼륨 (자동 생성)
docker run -v /data nginx

# 읽기 전용
docker run -v myvolume:/data:ro nginx

# --mount 사용 (더 명시적)
docker run \
  --mount type=volume,source=myvolume,target=/data \
  nginx

# 읽기 전용 (--mount)
docker run \
  --mount type=volume,source=myvolume,target=/data,readonly \
  nginx

# 볼륨 드라이버 옵션
docker run \
  --mount type=volume,source=myvolume,target=/data,volume-driver=local \
  nginx

볼륨 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 볼륨 삭제
docker volume rm myvolume

# 여러 볼륨 삭제
docker volume rm vol1 vol2 vol3

# 사용하지 않는 볼륨 정리
docker volume prune

# 확인 없이 정리
docker volume prune -f

# 특정 레이블 필터
docker volume prune --filter "label=env=test"

6.3 Bind Mount 상세

Bind Mount 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 기본 bind mount
docker run -v /host/path:/container/path nginx

# 절대 경로 필요
docker run -v $(pwd)/app:/app nginx

# 읽기 전용
docker run -v /host/path:/container/path:ro nginx

# --mount 사용 (권장)
docker run \
  --mount type=bind,source=/host/path,target=/container/path \
  nginx

# 읽기 전용 (--mount)
docker run \
  --mount type=bind,source=/host/path,target=/container/path,readonly \
  nginx

개발 환경에서 활용

1
2
3
4
5
6
7
8
9
10
# 소스 코드 동기화
docker run -d \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/package.json:/app/package.json \
  -p 3000:3000 \
  node:18 \
  npm run dev

# 코드 변경 시 자동 반영 (nodemon, webpack-dev-server 등)
# 컨테이너 재시작 불필요

권한 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 문제: 컨테이너 내부 사용자와 호스트 사용자 UID 불일치
# 호스트: UID 1000
# 컨테이너: UID 33 (www-data)

# 해결 1: 컨테이너 사용자를 호스트 UID와 맞춤
docker run --user $(id -u):$(id -g) myapp

# 해결 2: Dockerfile에서 사용자 생성
RUN addgroup -g 1000 appgroup && \
    adduser -u 1000 -G appgroup -D appuser
USER appuser

# 해결 3: chown으로 소유권 변경 (엔트리포인트 스크립트)
chown -R appuser:appgroup /data

보안 주의사항

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 위험: 호스트 루트 마운트
docker run -v /:/host nginx  # 위험

# 위험: 민감한 디렉토리 마운트
docker run -v /etc:/host-etc nginx  # 위험

# 위험: Docker 소켓 마운트
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp
# Docker API 완전 접근 = 호스트 제어 가능

# 안전한 사용:
# - 필요한 최소 경로만 마운트
# - 가능하면 읽기 전용
# - 민감한 디렉토리 피하기

6.4 tmpfs Mount

tmpfs 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# tmpfs mount
docker run --tmpfs /tmp nginx

# 크기 제한
docker run --tmpfs /tmp:size=100M nginx

# --mount 사용
docker run \
  --mount type=tmpfs,target=/tmp,tmpfs-size=100M \
  nginx

# 여러 tmpfs
docker run \
  --tmpfs /tmp \
  --tmpfs /var/cache \
  nginx

사용 사례

1
2
3
4
5
6
7
8
9
10
11
# 1. 캐시 디렉토리
docker run --tmpfs /app/cache myapp

# 2. 임시 파일 처리
docker run --tmpfs /tmp myapp

# 3. 민감한 정보 (비밀번호, 토큰 등)
docker run --tmpfs /secrets myapp

# 4. 세션 데이터
docker run --tmpfs /var/lib/sessions myapp

6.5 볼륨 드라이버

로컬 드라이버 옵션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# NFS 볼륨
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/dir \
  nfs-volume

# CIFS/SMB 볼륨
docker volume create \
  --driver local \
  --opt type=cifs \
  --opt o=username=user,password=pass,addr=192.168.1.100 \
  --opt device=//192.168.1.100/share \
  cifs-volume

# tmpfs 볼륨
docker volume create \
  --driver local \
  --opt type=tmpfs \
  --opt device=tmpfs \
  --opt o=size=100m \
  tmpfs-volume

서드파티 볼륨 드라이버

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# REX-Ray (클라우드 스토리지)
docker plugin install rexray/ebs

docker volume create \
  --driver rexray/ebs \
  --opt size=10 \
  ebs-volume

# Convoy (스냅샷 지원)
docker volume create \
  --driver convoy \
  convoy-volume

# Flocker (컨테이너 이동 시 데이터 함께 이동)
docker volume create \
  --driver flocker \
  flocker-volume

6.6 데이터 백업 및 복원

볼륨 백업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 방법 1: tar로 압축
docker run --rm \
  -v myvolume:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/myvolume-backup.tar.gz /data

# 방법 2: 컨테이너를 통한 백업
docker run --rm \
  -v myvolume:/source:ro \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /source && tar czf /backup/backup.tar.gz ."

# 방법 3: 직접 복사
sudo cp -r /var/lib/docker/volumes/myvolume/_data ./backup

볼륨 복원

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# tar에서 복원
docker run --rm \
  -v myvolume:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/myvolume-backup.tar.gz -C /

# 새 볼륨에 복원
docker volume create myvolume-restored

docker run --rm \
  -v myvolume-restored:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/backup.tar.gz -C /data

데이터베이스 백업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PostgreSQL 백업
docker exec postgres pg_dump -U postgres mydb > backup.sql

# 볼륨을 통한 백업
docker run --rm \
  -v postgres-data:/var/lib/postgresql/data \
  -v $(pwd):/backup \
  postgres \
  pg_dump -U postgres -f /backup/mydb.sql mydb

# MySQL 백업
docker exec mysql mysqldump -u root -p mydb > backup.sql

# MongoDB 백업
docker exec mongo mongodump --out /backup

Part 3: Docker 고급

7. Docker Compose

7.1 Docker Compose란?

Docker Compose의 목적

Docker Compose는 여러 컨테이너 애플리케이션을 정의하고 실행하는 도구이다.

주요 기능

  • 멀티 컨테이너 애플리케이션 정의 (YAML 파일)
  • 한 번의 명령으로 전체 스택 시작/중지
  • 환경별 설정 관리 (개발, 테스트, 프로덕션)
  • 서비스 스케일링
  • 의존성 관리

설치

1
2
3
4
5
6
7
8
9
# Docker Desktop은 Compose 포함

# Linux에서 별도 설치
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

# 버전 확인
docker-compose --version

7.2 docker-compose.yml 기본

기본 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3.8'

services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    networks:
      - frontend

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  db-data:

버전

1
2
3
4
5
6
7
# Compose 파일 버전 (3.8 권장)
version: '3.8'

# 버전별 주요 차이:
# - 3.x: Docker Swarm 지원
# - 2.x: 로컬 개발 중심
# - 1.x: 레거시 (사용 안 함)

7.3 서비스 정의

이미지 지정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  # 이미지 사용
  web:
    image: nginx:latest

  # Dockerfile 빌드
  app:
    build: .

  # Dockerfile 경로 지정
  app:
    build:
      context: ./app
      dockerfile: Dockerfile.dev

  # 빌드 인수
  app:
    build:
      context: .
      args:
        - VERSION=1.0
        - BUILD_DATE=2024-01-01

포트 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
services:
  web:
    ports:
      # 호스트:컨테이너
      - "8080:80"

      # IP 지정
      - "127.0.0.1:8080:80"

      # 여러 포트
      - "8080:80"
      - "4443:443"

      # UDP
      - "53:53/udp"

      # 포트 범위
      - "8000-8010:8000-8010"

환경 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    environment:
      # 직접 지정
      NODE_ENV: production
      PORT: 3000

        # 배열 형식
        - NODE_ENV=production
        - PORT=3000

    # 파일에서 로드
    env_file:
      - .env
      - .env.prod

  db:
    environment:
      # 호스트 환경 변수 참조
      DB_PASSWORD: ${DB_PASSWORD}

볼륨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    volumes:
      # Named volume
      - data:/app/data

      # Bind mount
      - ./app:/app
      - ./config:/etc/nginx

      # 읽기 전용
      - ./config:/etc/nginx:ro

      # tmpfs
      - type: tmpfs
        target: /tmp

volumes:
  data:
    driver: local

네트워크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      backend:
        # IP 주소 지정
        ipv4_address: 172.20.0.10

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

의존성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
  web:
    depends_on:
      - db
      - redis

  db:
    image: postgres

  redis:
    image: redis

# depends_on은 시작 순서만 제어
# 서비스가 준비될 때까지 기다리지 않음
# 준비 여부는 healthcheck나 wait-for-it 스크립트 사용

헬스체크

1
2
3
4
5
6
7
8
services:
  web:
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost" ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

재시작 정책

1
2
3
4
5
6
7
services:
  web:
    restart: always
    # no: 재시작 안 함 (기본값)
    # always: 항상 재시작
    # on-failure: 실패 시에만
    # unless-stopped: 명시적 중지 전까지

7.4 Docker Compose 명령어

기본 명령

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 서비스 시작 (빌드 포함)
docker-compose up

# 백그라운드 실행
docker-compose up -d

# 빌드만
docker-compose build

# 강제 재빌드
docker-compose up --build

# 특정 서비스만
docker-compose up web

# 스케일링
docker-compose up -d --scale web=3

서비스 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 서비스 중지
docker-compose stop

# 특정 서비스 중지
docker-compose stop web

# 서비스 시작 (이미 생성된 컨테이너)
docker-compose start

# 서비스 재시작
docker-compose restart

# 서비스 일시 정지
docker-compose pause

# 재개
docker-compose unpause

정리

1
2
3
4
5
6
7
8
9
10
11
# 컨테이너 중지 및 삭제
docker-compose down

# 볼륨도 삭제
docker-compose down -v

# 이미지도 삭제
docker-compose down --rmi all

# 고아 컨테이너 제거
docker-compose down --remove-orphans

로그 및 모니터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 로그 확인
docker-compose logs

# 실시간 로그
docker-compose logs -f

# 특정 서비스
docker-compose logs -f web

# 마지막 N줄
docker-compose logs --tail=100

# 타임스탬프 포함
docker-compose logs -t

# 실행 중인 서비스 확인
docker-compose ps

# 프로세스 확인
docker-compose top

명령 실행

1
2
3
4
5
6
7
8
# 서비스에서 명령 실행
docker-compose exec web bash

# 새 컨테이너에서 일회성 명령
docker-compose run web python manage.py migrate

# 컨테이너 생성 없이 실행
docker-compose run --rm web python script.py

설정 검증

1
2
3
4
5
6
7
8
# 설정 파일 검증
docker-compose config

# 환경 변수 치환 후 출력
docker-compose config

# 특정 파일 지정
docker-compose -f docker-compose.prod.yml config

7.5 실전 예시

웹 애플리케이션 스택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
version: '3.8'

services:
  # Nginx 웹 서버
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static-files:/static
    depends_on:
      - web
    networks:
      - frontend

  # Django 웹 애플리케이션
  web:
    build: ./app
    command: gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./app:/app
      - static-files:/app/static
    environment:
      - DEBUG=False
      - DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    networks:
      - frontend
      - backend

  # PostgreSQL 데이터베이스
  db:
    image: postgres:15
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=secret
    networks:
      - backend

  # Redis 캐시
  redis:
    image: redis:alpine
    volumes:
      - redis-data:/data
    networks:
      - backend

  # Celery Worker
  celery:
    build: ./app
    command: celery -A myapp worker -l info
    volumes:
      - ./app:/app
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  postgres-data:
  redis-data:
  static-files:

개발 환경 vs 프로덕션

1
2
3
4
5
6
7
8
9
10
11
# docker-compose.yml (베이스)
version: '3.8'

services:
  web:
    build: .
    environment:
      - DATABASE_URL=postgresql://db/mydb

  db:
    image: postgres:15
1
2
3
4
5
6
7
8
9
10
11
# docker-compose.override.yml (개발 - 자동 로드)
version: '3.8'

services:
  web:
    volumes:
      - ./app:/app
    environment:
      - DEBUG=True
    ports:
      - "8000:8000"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# docker-compose.prod.yml (프로덕션)
version: '3.8'

services:
  web:
    image: myregistry.com/myapp:latest
    environment:
      - DEBUG=False
    restart: always

  db:
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: always

volumes:
  db-data:
1
2
3
4
5
# 개발
docker-compose up  # .yml + .override.yml 자동 병합

# 프로덕션
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

8. Docker 레지스트리

8.1 Docker Hub

Docker Hub 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 로그인
docker login
# Username:
# Password:

# 이미지 태그
docker tag myapp:latest username/myapp:latest
docker tag myapp:latest username/myapp:v1.0

# 푸시
docker push username/myapp:latest
docker push username/myapp:v1.0

# 풀
docker pull username/myapp:latest

# 로그아웃
docker logout

자동 빌드 (Automated Builds)

Docker Hub는 GitHub/Bitbucket과 연동하여 자동 빌드를 지원한다:

  1. Docker Hub에서 저장소 생성
  2. GitHub 저장소 연결
  3. 빌드 규칙 설정
  4. Git 푸시 시 자동 빌드

프라이빗 저장소

1
2
3
4
5
# 프라이빗 저장소 풀 (로그인 필요)
docker pull username/private-repo:latest

# 무료 플랜: 1개 프라이빗 저장소
# 유료 플랜: 무제한

8.2 프라이빗 레지스트리 구축

Registry 컨테이너 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
# 기본 레지스트리
docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  registry:2

# 이미지 푸시
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest

# 이미지 풀
docker pull localhost:5000/myapp:latest

TLS/SSL 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 인증서 준비
mkdir -p certs
# certs/domain.crt, certs/domain.key

# TLS로 레지스트리 실행
docker run -d \
  -p 443:443 \
  --name registry \
  -v registry-data:/var/lib/registry \
  -v $(pwd)/certs:/certs \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2

Basic 인증

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# htpasswd 파일 생성
mkdir auth
docker run --rm --entrypoint htpasswd \
  httpd:2 -Bbn username password > auth/htpasswd

# 인증 활성화
docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  -v $(pwd)/auth:/auth \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

# 로그인
docker login localhost:5000

8.3 Harbor

Harbor란?

Harbor는 엔터프라이즈급 컨테이너 레지스트리이다.

주요 기능

  • 웹 UI
  • RBAC (역할 기반 접근 제어)
  • 이미지 스캐닝 (Trivy, Clair)
  • 이미지 서명
  • 복제 (다중 레지스트리)
  • Helm 차트 저장소
  • 감사 로그

설치 (Docker Compose)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Harbor 다운로드
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
tar xzvf harbor-offline-installer-v2.9.0.tgz
cd harbor

# 설정
cp harbor.yml.tmpl harbor.yml
vi harbor.yml
# hostname 수정
# 인증서 경로 설정 (선택)

# 설치
sudo ./install.sh

# 접속
# https://your-harbor-domain
# admin / Harbor12345 (기본)

Harbor 사용

1
2
3
4
5
6
7
8
9
10
11
12
# 로그인
docker login harbor.example.com

# 프로젝트 생성 (UI에서)
# library, myapp 등

# 이미지 푸시
docker tag myapp:latest harbor.example.com/library/myapp:latest
docker push harbor.example.com/library/myapp:latest

# 이미지 풀
docker pull harbor.example.com/library/myapp:latest

9. Docker 보안

9.1 이미지 보안

취약점 스캔

1
2
3
4
5
6
7
8
# Trivy로 이미지 스캔
trivy image myapp:latest

# Docker Scout (Docker Desktop)
docker scout cves myapp:latest

# 취약점 레포트 생성
trivy image --format json myapp:latest > report.json

신뢰할 수 있는 이미지 사용

  • 공식 이미지 사용 (library/)
  • 검증된 게시자 확인
  • 최신 버전 유지
  • 정기적 업데이트

9.2 컨테이너 보안

최소 권한 원칙

1
2
3
4
5
6
7
8
# 나쁜 예: root로 실행
FROM nginx

# 좋은 예: 일반 사용자
FROM nginx
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup
USER appuser

Read-only 파일시스템

1
2
3
4
5
6
7
8
# 읽기 전용 루트 파일시스템
docker run --read-only nginx

# 쓰기 가능한 부분만 tmpfs 마운트
docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  nginx

환경 변수로 민감 정보 전달

1
2
3
4
5
6
7
8
# 좋은 예
docker run -e API_KEY=${API_KEY} myapp

# 또는 Docker Secrets 사용 (Swarm)
docker secret create api_key api_key.txt
docker service create \
  --secret api_key \
  myapp

9.3 런타임 보안

권한 드롭 (CAP DROP/ADD)

1
2
3
4
5
6
7
8
9
10
# 불필요한 권한 제거
docker run --cap-drop=ALL nginx

# 필요한 권한만 추가
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

# 일반적인 권한:
# - NET_BIND_SERVICE: 1024 미만 포트 바인딩
# - SYS_CHROOT: chroot 사용
# - SETUID/SETGID: UID/GID 변경

Seccomp 프로파일

1
2
3
4
5
# 기본 seccomp 프로파일 사용
docker run --security-opt seccomp=unconfined nginx

# 커스텀 seccomp 프로파일
docker run --security-opt seccomp=/path/to/profile.json nginx

AppArmor 프로필

1
2
3
4
5
# AppArmor 프로필 사용
docker run --security-opt apparmor=docker-default nginx

# 커스텀 프로필
docker run --security-opt apparmor=/path/to/profile nginx

9.4 네트워크 보안

Internal 네트워크

1
2
3
4
5
# 외부 통신 차단
docker network create --internal internal-net

# 내부 통신만 가능
docker run --network internal-net nginx

네트워크 정책 (Kubernetes)

1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: { }
  policyTypes:
    - Ingress
    - Egress

10. Docker 모니터링 및 로깅

10.1 컨테이너 로그

docker logs 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 로그 확인
docker logs <container>

# 실시간 로그 (tail -f)
docker logs -f <container>

# 마지막 N줄
docker logs --tail 100 <container>

# 타임스탐프 포함
docker logs -t <container>

# 특정 시간 이후
docker logs --since 1h <container>
docker logs --since 2024-01-01T10:00:00 <container>

로그 드라이버

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# json-file (기본)
docker run --log-driver json-file myapp

# syslog
docker run --log-driver syslog \
  --log-opt syslog-address=udp://127.0.0.1:514 \
  myapp

# journald
docker run --log-driver journald myapp

# Splunk
docker run --log-driver splunk \
  --log-opt splunk-token=<token> \
  --log-opt splunk-url=https://splunk.example.com:8088 \
  myapp

# AWS CloudWatch
docker run --log-driver awslogs \
  --log-opt awslogs-group=/docker/myapp \
  --log-opt awslogs-region=us-east-1 \
  myapp

로그 로테이션

1
2
3
4
5
6
# json-file 드라이버 로그 로테이션
docker run \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp

10.2 컨테이너 모니터링

docker stats

1
2
3
4
5
6
7
8
9
10
11
# 실시간 통계
docker stats

# 특정 컨테이너
docker stats myapp

# 스트리밍 없이 한 번만
docker stats --no-stream

# JSON 형식
docker stats --format='table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}'

cAdvisor (Google)

1
2
3
4
5
6
7
8
9
10
11
# cAdvisor 컨테이너 실행
docker run -d \
  --name=cadvisor \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --publish=8080:8080 \
  gcr.io/cadvisor/cadvisor:latest

# http://localhost:8080에서 접근

Prometheus + Grafana

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# docker-compose.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus

10.3 헬스 체크

Dockerfile에서 헬스체크

1
2
3
4
5
6
7
8
9
10
# 기본 헬스체크
HEALTHCHECK CMD curl -f http://localhost/ || exit 1

# 옵션 포함
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
  CMD curl -f http://localhost/ || exit 1

# 스크립트 사용
COPY healthcheck.sh /
HEALTHCHECK CMD /healthcheck.sh

실행 시 헬스체크

1
2
3
4
5
6
7
8
# 헬스체크 옵션
docker run \
  --health-cmd='curl -f http://localhost/' \
  --health-interval=30s \
  --health-timeout=10s \
  --health-retries=3 \
  --health-start-period=40s \
  myapp

헬스 상태 확인

1
2
3
4
5
# 헬스체크 상태 조회
docker inspect --format='{{.State.Health.Status}}' <container>

# 상세 정보
docker inspect --format='{{json .State.Health}}' <container> | jq

11. Docker 문제 해결

11.1 자주 발생하는 문제

컨테이너가 실행되지 않음

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 로그 확인
docker logs <container>

# 2. 컨테이너 상태 확인
docker ps -a
docker inspect <container>

# 3. 리소스 부족 확인
docker stats

# 4. 포트 충돌 확인
docker port <container>

포트 이미 사용 중

1
2
3
4
5
6
7
8
# Linux/Mac
lsof -i :8080

# Windows
netstat -ano | findstr :8080

# 포트를 사용 중인 프로세스 종료
kill -9 <pid>

디스크 공간 부족

1
2
3
4
5
6
7
# 디스크 사용량 확인
docker system df

# 정리
docker system prune
docker system prune -a
docker volume prune

11.2 디버깅 기법

컨테이너 접속

1
2
3
4
5
6
7
# 실행 중인 컨테이너
docker exec -it <container> bash

# 중지된 컨테이너 디버깅
docker run -it --rm \
  --volumes-from <stopped_container> \
  ubuntu bash

로그 분석

1
2
3
4
5
6
7
8
# 에러 로그만 필터링
docker logs <container> 2>&1 | grep ERROR

# 특정 시간대 로그
docker logs --since 1h <container>

# 로그 내보내기
docker logs <container> > container.log 2>&1

네트워크 디버깅

1
2
3
4
5
6
7
# nicolaka/netshoot 사용
docker run -it --rm \
  --network container:<container> \
  nicolaka/netshoot

# 패킷 캡처
docker exec <container> tcpdump -i eth0 -w /tmp/capture.pcap

11.3 성능 최적화

이미지 크기 최소화

1
2
3
4
5
6
7
8
9
# Multi-stage 빌드 사용
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

FROM node:18-alpine
COPY --from=builder /app/dist /app/dist
CMD ["node", "dist/index.js"]

레이어 캐싱 활용

1
2
3
4
# 변화 적은 것부터 복사
COPY package*.json ./
RUN npm install
COPY . .

리소스 제한

1
2
3
4
5
# 메모리 제한으로 안정성 향상
docker run -m 512m --memory-swap 1g myapp

# CPU 제한으로 이웃 노이즈 방지
docker run --cpus=1.5 --cpuset-cpus=0,1 myapp

마무리

Docker는 현대적인 애플리케이션 개발과 배포의 표준이 되었다. 이 가이드에서 다룬 기본 개념부터 고급 기술까지를 이해하고 활용하면, 더욱 효율적이고 안정적인 컨테이너 기반 애플리케이션을 구축할 수 있다.

추가 학습 자료

This post is licensed under CC BY 4.0 by the author.