Docker 특징과 사용하는 이유

유정빈·2026년 5월 13일

1. Docker, 왜 실무 인프라의 표준이 되었나?

도커는 단순히 '가상화'를 편하게 해주는 툴이 아닙니다.
소프트웨어의 배포, 운영, 이식성(Portability)에 대한 패러다임을 바꾼 혁명입니다.
2023년 Stack Overflow 설문 기준, 개발자 툴 1위를 차지할 만큼 현대 개발 생태계에서 사실상 필수 도구가 되었습니다.

1-1) 치킨 프랜차이즈로 이해하는 Docker의 핵심

Docker를 처음 접하면 "리눅스 컨테이너 기반 OS 레벨 가상화 기술"이라는 정의부터 만나게 됩니다. 하지만 이 한 줄로는 아무것도 와닿지 않습니다. 치킨 프랜차이즈 비유로 시작해봅시다.

여러분이 치킨집 사장이라고 가정합니다.

상황치킨집 비유소프트웨어 세계
문제본점 셰프의 레시피가 맛있는데, 가맹점마다 주방 환경(가스레인지, 기름)이 달라 맛이 다름개발자 PC에선 잘 되는 코드가, 서버에 올리면 라이브러리/OS 차이로 오류 발생
해결책레시피 + 재료 + 양념 + 조리도구를 밀키트(Meal Kit)로 만들어 배송코드 + OS + 라이브러리 + 설정을 컨테이너 이미지로 패키징
결과전국 100개 가맹점에서 똑같은 맛개발/스테이징/운영 서버 어디서든 100% 동일하게 동작

Docker = 코드 밀키트 제조기입니다.
내 프로그램이 돌아가는 데 필요한 모든 것(코드, OS, 라이브러리, 환경변수)을 하나의 밀키트(=이미지)로 포장해서,
어떤 컴퓨터에서든 그 밀키트만 뜯으면(docker run) 똑같이 실행되게 만드는 프로그램입니다.

1-2) "내 PC에선 잘 되는데요?"의 종말

인프라 엔지니어를 괴롭히는 가장 큰 원인은 의존성 충돌(Dependency Hell)입니다.

ex)

AS-IS (Docker 이전):

개발자 PC: Python 3.11 + Ubuntu 22.04 + OpenSSL 3.0  -> 정상 작동
운영 서버: Python 3.8 + CentOS 7 + OpenSSL 1.0   -> 크래시
고객사 A:  Python 3.9 + RHEL 8 + 방화벽 정책 다름   -> 타임아웃

고객사마다 인프라와 플랫폼이 달랐기 때문에, 개발자가 만든 프로그램을 배포할 때마다 "이 서버에 Python 몇 버전이 깔려있지?" 하는 문제를 매번 겪어야 했습니다.

TO-BE (Docker 이후):

컨테이너 이미지 안에 Python 3.11 + 필요 라이브러리 + 설정 파일 전부 포함
-> 개발 서버든, AWS든, 고객사 서버든 docker run 한 줄이면 끝
-> "환경의 영향을 받지 않고 어디서든 동일하게 동작"

1-3) 소프트웨어 운영 플랫폼의 시대적 변천

컨테이너는 갑자기 등장한 것이 아닙니다. IT 인프라의 진화 과정에서 필연적으로 태어난 기술입니다.

[1세대] 베어메탈           ->  [2세대] 가상화(VM)      ->  [3세대] 컨테이너
+---------------+            +---------------+        +---------------+
|   App A/B/C   |            |  VM1  |  VM2  |        | C1 | C2 | C3  |
|---------------|            |  OS   |  OS   |        |   (100MB씩)   |
|    Host OS    |            |---------------|        |---------------|
|   Hardware    |            |  Hypervisor   |        | Container Eng.|
+---------------+            |   Hardware    |        |   Host OS     |
                             +---------------+        |   Hardware    |
                                                      +---------------+
  1. 베어메탈 시대: 물리 서버에 App을 직접 설치. 서버 한 대가 죽으면 서비스 전체가 중단.
  2. 가상화(VM) 시대: 하이퍼바이저 위에 Guest OS를 올려 여러 서버처럼 사용. 하지만 VM마다 무거운 OS(1GB+)를 통째로 띄워야 하므로 자원 낭비가 심함.
  3. 컨테이너 시대: Host OS의 커널을 공유하면서 프로세스만 격리. App에 필요한 환경(100MB 수준)만 담으므로 가볍고 빠르고 효율적.

확장성 비교: 일반 애플리케이션은 스케일 아웃 시 OS(1GB) + App(100MB)이 함께 복제되어 비효율적입니다. 컨테이너는 App(100MB)만 복제되므로, 동일 하드웨어에서 수십 배 더 많은 인스턴스를 띄울 수 있습니다.

1-4) 컨테이너 기술의 역사 - chroot에서 Docker까지

연도기술의미
1979chroot (Unix V7)프로세스의 루트 디렉터리를 변경하여 파일 시스템 격리. 컨테이너의 씨앗
2000FreeBSD Jails파일 시스템 + 네트워크 + 사용자 격리를 제공하는 최초의 완전한 샌드박스
2006cgroups (Google)프로세스 그룹의 CPU/메모리 사용량을 제한하는 커널 기능
2008LXC (LinuX Containers)Namespace + cgroups를 결합한 최초의 실용적 컨테이너. 하지만 복잡했음
2013Docker (Solomon Hykes)PaaS 기업 dotCloud의 내부 프로젝트에서 탄생. 누구나 쉽게 컨테이너를 쓸 수 있게 만든 혁신
2014libcontainerDocker가 LXC 의존성을 제거하고 자체 컨테이너 런타임 개발

Docker는 완전히 새로운 기술을 발명한 것이 아닙니다. 이미 존재하던 리눅스 커널 기능들을 조합하여 누구나 쓸 수 있게 포장한 것에 가치가 있습니다.

1-5) 마이크로서비스(MSA)와 DevOps - 컨테이너를 요구하는 시대

현대 IT 시대가 컨테이너를 필요로 하는 궁극적인 이유는 MSA(마이크로서비스 아키텍처)DevOps입니다.

  • MSA: 하나의 거대한 모놀리식 서비스를 수십~수백 개의 작은 서비스로 쪼개는 아키텍처. 각 서비스는 독립적으로 배포/확장/장애 복구가 가능해야 합니다.
  • DevOps: 개발(Dev)과 운영(Ops)의 경계를 허물어 고객 요구를 빠르게 처리하고 대응하는 문화.

컨테이너가 이 두 가지에 최적인 이유:
1. Push and Run: 개발자가 만든 프로그램을 어디서든 즉시 실행 가능
2. 초 단위 Scale Out/In: 트래픽 폭주 시 컨테이너를 수초 만에 수십 개 복제, 한가해지면 즉시 축소
3. 장애 격리: 컨테이너 하나가 죽어도 다른 컨테이너가 서비스 연속성 유지
4. IaC(Infrastructure as Code): 서버 구축 과정이 Dockerfile이라는 텍스트 파일로 관리되어 Git 버전 관리 가능


2. Docker를 움직이는 리눅스 커널 기술

2-1) 왜 굳이 리눅스인가?

컨테이너는 리눅스 커널의 기능을 직접 활용하여 생성됩니다. 이것이 컨테이너를 리눅스에서 운영하는 이유이자, Windows나 Mac에서 Docker Desktop을 설치하면 내부적으로 경량 리눅스 VM(하이퍼바이저)을 띄우는 이유입니다.

[리눅스 환경]                    [Windows/Mac 환경]
+-----------------+           +-----------------+
|   Container     |           |   Container     |
|   Docker Engine |           |   Docker Engine |
|   Linux Kernel  | <- 직접    |   Linux Kernel  | <- 에뮬레이션
|   Hardware      |           |   Hypervisor    |
+-----------------+           |   Host OS (Win) |
                              |   Hardware      |
                              +-----------------+

클라우드 서비스(AWS, GCP, Azure)는 모두 리눅스 환경에서 운영되므로 Docker를 네이티브로 사용할 수 있습니다. 현장에서는 라이선스 비용이 없는 리눅스 기반 운영을 선호합니다.

2-2) 일반 프로그램 vs 컨테이너 프로그램

수행하는 기능은 동일하지만 구조가 완전히 다릅니다. Node.js 웹 서버를 예로 들어보겠습니다.

일반 프로그램 방식:

1. 리눅스 서버에 Node.js를 직접 설치 (apt install nodejs)
2. 소스 코드(app.js)를 서버에 복사
3. node app.js로 직접 실행
-> 문제: 서버 OS에 종속. 다른 서버로 이동하면 환경 차이로 오류 가능

컨테이너 프로그램 방식:

1. Dockerfile에 "Node.js 18 + app.js + npm install" 명세 작성
2. docker build로 이미지 생성 (Node.js 환경 + app.js가 내부에 포함)
3. docker run으로 어디서든 실행
-> 핵심: 필요한 환경과 애플리케이션이 컨테이너 내부에 포함되어 독립적으로 동작
비교 항목일반 프로그램컨테이너 프로그램
환경 구성Host OS에 직접 설치이미지 내부에 포함
이식성OS/라이브러리 버전에 종속어디서든 동일 실행
격리같은 OS 자원 공유 (충돌 위험)프로세스 단위 완전 격리
배포서버마다 환경 세팅 필요이미지 하나로 즉시 배포
확장OS + App 통째로 복제 (무거움)App 환경만 복제 (가벼움)

2-3) VM(가상머신) vs Container

둘 다 "격리된 환경"을 제공하지만, 격리하는 방식과 수준이 근본적으로 다릅니다.

비교 항목VM (가상머신)Container (Docker)
가상화 수준하드웨어 레벨 (전체 OS 가상화)OS 레벨 (프로세스 격리)
핵심 엔진Hypervisor (VMware, KVM 등)Container Engine (Docker)
커널VM마다 독립된 Guest OS 커널Host OS 커널을 공유
부팅 속도수십 초~수 분 (OS 부팅 필요)수 초 이내 (프로세스 실행)
리소스무거움 (Guest OS 포함 수 GB)가벼움 (수십~수백 MB)
격리 강도강력 (완전 독립 OS)프로세스 수준 (커널 공유)
보안커널 분리로 높은 보안커널 공유로 취약점 공유 위험
적합 용도이기종 OS 실행, 레거시 앱MSA, 빠른 배포, 스케일링

2-4) 컨테이너를 컨테이너답게 만드는 3가지 커널 기술

1. chroot에서 Namespace로 (격리)

chroot는 프로세스의 루트 디렉터리를 변경하여 파일 시스템을 격리하는 최초의 기술이었지만, 파일 시스템만 격리할 뿐 네트워크/프로세스 등은 격리하지 못했습니다. 이를 발전시킨 것이 Namespace입니다.

Namespace격리 대상효과
PID프로세스 ID컨테이너 내부 프로세스는 자신이 1번(init)인 줄 앎
NET네트워크독립적인 IP 주소/포트/라우팅 테이블 할당
MNT파일 시스템독립적인 파일 시스템 트리 구성
UTS호스트명컨테이너마다 고유한 hostname 설정
IPC프로세스 간 통신세마포어/메시지 큐 격리
USER사용자/그룹컨테이너 내부 root != 호스트 root

한 줄 정리: Namespace는 프로세스가 "무엇을 볼 수 있는지" 범위를 제한합니다.

2. Cgroups (Control Groups - 자원 통제)

특정 컨테이너가 CPU나 메모리를 무한정 끌어다 쓰면 서버 전체가 뻗어버리는 Noisy Neighbor 문제가 발생합니다. Cgroups는 컨테이너별로 사용 가능한 하드웨어 자원의 상한선을 엄격하게 제한합니다.

# 예: 컨테이너에 CPU 50%, 메모리 512MB 제한
docker run --cpus="0.5" --memory="512m" myapp:latest

한 줄 정리: Cgroups는 프로세스가 "얼마나 많은 자원을 사용할 수 있는지" 양을 제한합니다.

3. UnionFS (Copy-on-Write - 스토리지 효율화)

Docker 이미지는 하나의 거대한 파일이 아니라, 여러 개의 읽기 전용(Read-Only) 레이어가 쌓인 구조입니다.

[컨테이너 실행 시 레이어 구조]

+---------------------+
|                     |  <- Upperdir (R/W) : 컨테이너 쓰기 레이어
|  파일 수정/생성/삭제  |     (컨테이너 종료 시 사라짐)
|---------------------|
|  COPY app.js /app   |  <- Layer 3 (R/O)
|---------------------|
|  RUN npm install    |  <- Layer 2 (R/O)
|---------------------|
|  FROM node:18-alpine|  <- Layer 1 (R/O) : Base Image
+---------------------+

Copy-on-Write(CoW): 컨테이너에서 파일을 수정할 때, 읽기 전용 레이어의 원본을 건드리지 않고 상위 쓰기 레이어에 복사한 뒤 수정합니다.

레이어 공유의 위력: node:18-alpine 베이스 이미지가 로컬에 이미 있다면, 다른 이미지를 빌드/pull할 때 공통 레이어는 재다운로드하지 않습니다.


3. Docker의 3대 핵심 요소 (Dockerfile, Image, Container)

Docker를 실무에서 다루려면 이 세 가지 요소의 관계를 명확히 이해하는 것이 모든 것의 출발점입니다. 코딩애플의 밀키트 비유를 이어가면 이렇습니다:

Docker 요소밀키트 비유프로그래밍 비유설명
Dockerfile밀키트 제조 레시피클래스 설계서어떤 재료(Base OS)를 쓰고, 어떤 양념(패키지)을 넣고, 소스(코드)는 어디에 담을지 적은 텍스트 명세서
Image완성된 밀키트 패키지클래스(Class)Dockerfile을 docker build하면 생성되는 읽기 전용 템플릿. 모든 재료가 동결(Immutable) 상태로 밀봉
Container밀키트를 뜯어서 조리 중인 상태인스턴스(Instance)이미지를 docker run하여 메모리에 올린 실행 상태. 읽기 전용 이미지 위에 쓰기 가능한 레이어를 얹어서 실제로 동작

3-1) Dockerfile — 인프라의 설계도 (IaC)

Dockerfile은 컨테이너를 어떻게 만들지 정의해 둔 텍스트 파일입니다.
이것이 바로 Infrastructure as Code(IaC)의 실현입니다.

# 예시: Node.js 웹서버 Dockerfile
FROM node:18-alpine          # 1. 베이스 이미지 지정 (어떤 OS + 런타임)
WORKDIR /app                 # 2. 작업 디렉터리 설정
COPY package.json .          # 3. 의존성 파일 먼저 복사 (캐시 최적화)
RUN npm install              # 4. 의존성 설치
COPY . .                     # 5. 소스 코드 복사
EXPOSE 3000                  # 6. 사용할 포트 선언
CMD ["node", "server.js"]    # 7. 컨테이너 실행 시 실행할 명령어

각 명령어의 의미:

명령어역할밀키트 비유
FROM베이스 이미지 선택밀키트에 쓸 기본 육수 선택
WORKDIR작업 디렉터리 설정조리대 위치 지정
COPY파일을 이미지 안으로 복사재료를 밀키트 상자에 담기
RUN빌드 시 실행할 명령 (레이어 생성)재료 손질 (밑간, 양념 배합)
EXPOSE컨테이너가 사용할 포트 선언완성된 요리를 서빙할 창구 지정
CMD컨테이너 실행 시 기본 명령손님이 밀키트를 뜯었을 때 첫 조리 동작

IaC의 가치: 서버 세팅 과정이 Git으로 버전 관리됩니다. 신입 사원이 와도, 서버가 한 대 더 늘어나도, Dockerfile 하나면 100% 동일한 서버가 구축됩니다.

3-2) Image — 불변의 템플릿

# Dockerfile을 이미지로 빌드
docker build -t myapp:v1 .

# 이미지 목록 확인
docker images

이미지의 핵심 특성:

  • 불변성(Immutable): 한번 빌드된 이미지는 수정할 수 없습니다. 변경이 필요하면 Dockerfile을 수정하고 새로 빌드합니다.
  • 레이어 구조: 각 Dockerfile 명령어가 하나의 레이어를 형성하며, 공통 레이어는 여러 이미지 간에 공유됩니다.
  • 빌드 캐시: Docker는 명령어 단위로 결과를 캐싱합니다. 소스 코드만 변경되었다면 npm install 레이어는 캐시를 재사용하여 빌드 속도를 대폭 향상시킵니다.

3-3) Container — 살아있는 실행 인스턴스

# 이미지를 컨테이너로 실행
docker run -d -p 8080:3000 --name my-web myapp:v1

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

# 컨테이너 로그 확인
docker logs my-web

# 컨테이너 중지 및 삭제
docker stop my-web && docker rm my-web

하나의 이미지에서 여러 개의 컨테이너를 동시에 실행할 수 있습니다. 마치 하나의 밀키트 레시피로 여러 주방에서 동시에 조리하는 것과 같습니다.

# 같은 이미지로 3개의 컨테이너 동시 실행
docker run -d -p 8081:3000 --name web-1 myapp:v1
docker run -d -p 8082:3000 --name web-2 myapp:v1
docker run -d -p 8083:3000 --name web-3 myapp:v1

3-4) 생명주기 전체 흐름 요약

[Dockerfile]  ──build──▶  [Image]  ──run──▶  [Container]
  (레시피)                (밀키트)            (조리 중)
                             │                   │
                             │ push/pull          │ stop/start
                             ▼                   │ rm
                        [Registry]               ▼
                       (Docker Hub)          [삭제됨]
                       (AWS ECR 등)    (쓰기 레이어만 소멸,
                                        이미지는 그대로 보존)

4. Docker 코어 아키텍처 컴포넌트

Docker는 클라이언트-서버(Client-Server) 아키텍처로 동작합니다. 개발자가 터미널에 명령어를 치면, 백그라운드에서 돌아가는 엔진이 모든 무거운 작업을 대신 처리합니다.

4-1) 아키텍처 전체 구조

┌─────────────────────────────────────────────────────────────┐
│                     Docker Client (CLI)                     │
│         docker build / run / pull / push / ...              │
└───────────────────────┬─────────────────────────────────────┘
                        │ REST API (Unix Socket)
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                   Docker Host (Daemon)                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │ Images   │  │Containers│  │ Networks │  │ Volumes  │       │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
│                     dockerd (백그라운드 프로세스)               │
└───────────────────────┬─────────────────────────────────────┘
                        │ HTTPS
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                   Docker Registry                            │
│           Docker Hub / AWS ECR / Harbor 등                   │
└─────────────────────────────────────────────────────────────┘

4-2) 컴포넌트별 상세 설명

Docker Client (CLI)

우리가 터미널에서 docker run, docker build 등의 명령어를 입력하는 사용자 인터페이스입니다.

  • 클라이언트는 사용자의 명령을 받아 내부적으로 REST API 형태로 변환하여 Docker Daemon에 전송합니다.
  • Daemon과 같은 호스트에 있을 수도 있고, 원격 호스트의 Daemon에 연결할 수도 있습니다.
# 클라이언트 → Daemon 통신 흐름 예시
docker run nginx
# 1. CLI가 "run nginx" 요청을 REST API로 변환
# 2. Unix Socket(/var/run/docker.sock)을 통해 dockerd로 전송
# 3. dockerd가 이미지 확인 → 없으면 Registry에서 pull → 컨테이너 생성·실행

Docker Host (Docker Daemon)

dockerd라는 이름의 백그라운드 프로세스(데몬)입니다. Docker의 실질적인 두뇌 역할을 합니다.

  • 클라이언트의 API 요청을 수신하여 이미지 다운로드, 컨테이너 실행/중지, 네트워크 세팅, 볼륨 관리 등 OS 커널과 소통하며 실제 무거운 작업을 수행합니다.
  • 내부적으로 containerdrunc라는 컴포넌트 체인을 통해 실제 컨테이너 프로세스를 생성합니다.
[Docker Daemon 내부 구조]
dockerd (API 수신 + 이미지/네트워크/볼륨 관리)
  └─▶ containerd (컨테이너 생명주기 관리)
        └─▶ runc (리눅스 커널 Namespace/Cgroups 호출하여 실제 컨테이너 프로세스 생성)

Docker Registry (원격 저장소)

완성된 이미지를 업로드(Push)하고 다운로드(Pull)할 수 있는 원격 이미지 저장소입니다.

Registry 종류설명사용 사례
Docker Hub전 세계 개발자가 공유하는 공개 저장소nginx, node, python 등 공식 이미지
AWS ECRAWS 관리형 Private Registry사내 서비스 이미지 관리
Harbor오픈소스 Private Registry온프레미스 환경 보안 요구
GitHub GHCRGitHub 연동 RegistryOSS 프로젝트 CI/CD 파이프라인
# Registry 활용 흐름
docker login                              # 1. Registry 인증
docker build -t myapp:v1 .                # 2. 이미지 빌드
docker tag myapp:v1 ryujungbin/myapp:v1   # 3. 이름표 달기 (Tag)
docker push ryujungbin/myapp:v1           # 4. Registry에 업로드
docker pull ryujungbin/myapp:v1           # 5. 다른 서버에서 다운로드

4-3) 전체 동작 흐름 — docker run nginx

docker run nginx 명령 하나를 쳤을 때 내부에서 일어나는 일을 순서대로 추적하면:

[1] 사용자 입력: docker run nginx
        │
[2] Docker Client가 REST API로 변환 → dockerd에 전송
        │
[3] dockerd가 로컬에 nginx 이미지가 있는지 확인
        │
        ├── 있음 → [5]로 이동
        └── 없음 → [4] Docker Hub에서 pull (레이어 단위 병렬 다운로드)
                          │
[5] dockerd → containerd → runc 체인으로 컨테이너 프로세스 생성
        │
[6] 리눅스 커널의 Namespace(격리) + Cgroups(자원 제한) 적용
        │
[7] UnionFS로 읽기 전용 이미지 레이어 위에 쓰기 레이어 생성
        │
[8] 컨테이너 프로세스 시작 → nginx가 80 포트에서 서비스 시작

0개의 댓글