Skip to content

Docker 컨테이너화

서론

"내 컴퓨터에서는 잘 돌아가"는 개발자의 가장 고전적인 변명이며, Docker는 이 변명을 완전히 없애버립니다. 컨테이너화 기술은 애플리케이션과 모든 의존성을 표준화된 단위로 패키징하여, 어떤 환경에서든 일관되게 실행되도록 보장합니다. 이것은 현대 소프트웨어 전달의 초석입니다.

이 글에서 무엇을 배우게 될까요?

이 장을 마치면 다음을 얻게 됩니다:

  • 핵심 개념: 이미지, 컨테이너, 레지스트리 3대 핵심 개념 이해
  • 아키텍처 비교: 컨테이너와 가상 머신의 본질적 차이 파악
  • 실무 능력: Dockerfile 작성 및 일반적인 명령어 숙지
  • 오케스트레이션 기초: Docker Compose로 다중 서비스 애플리케이션 관리
  • 모범 사례: 이미지 최적화, 보안 강화 등 프로덕션급 실무 이해
내용핵심 개념
제1장컨테이너가 필요한 이유환경 일관성, 리소스 효율, 표준화된 전달
제2장핵심 개념이미지, 컨테이너, 레지스트리, Dockerfile
제3장Docker 수명 주기작성, 빌드, 푸시, 실행, 관리
제4장Docker Compose다중 서비스 오케스트레이션, 네트워크, 데이터 볼륨
제5장모범 사례이미지 최적화, 보안, 멀티 스테이지 빌드

1. 컨테이너가 필요한 이유는 무엇인가?

컨테이너가 등장하기 전에는 애플리케이션을 배포하려면 서버에서 런타임을 수동으로 설치하고, 환경 변수를 설정하고, 의존성 충돌을 해결해야 했습니다. 서로 다른 환경(개발, 테스트, 프로덕션) 간의 차이는 버그의 온상이었습니다.

Virtual Machines vs Containers
Switch between both virtualization models to compare their architecture
App A / App B / App C
App A + Bins/Libs
App B + Bins/Libs
App C + Bins/Libs
Docker Engine
Host OS
Physical hardware
Startup speedSeconds
Resource usageShared host OS kernel (MB scale)
IsolationProcess-level isolation
DensityHundreds of containers per host
Image sizeMB scale

컨테이너가 해결하는 문제

문제전통적 방식컨테이너 방식
환경 불일치"내 로컬에서는 돌아가"모든 의존성을 패키징하여 어디서든 일관됨
의존성 충돌App A는 Node 14, App B는 Node 18 필요각 컨테이너가 독립된 환경
리소스 낭비각 VM마다 완전한 OS커널 공유, MB 단위 오버헤드
배포 속도수동 설치 및 설정docker run 명령 하나로 완료
확장 어려움새 VM 생성, 환경 설치, 배포초 단위로 새 컨테이너 시작

컨테이너의 본질

컨테이너는 경량 가상 머신이 아닙니다. 그 본질은 격리된 프로세스입니다. Linux 커널은 두 가지 메커니즘을 통해 컨테이너를 구현합니다:

  • Namespace: 프로세스의 시야를 격리(PID, 네트워크, 파일 시스템 등)
  • Cgroups: 프로세스의 리소스 사용을 제한(CPU, 메모리, IO)

컨테이너 안의 프로세스와 호스트의 일반 프로세스는 본질적으로 같지만, "바깥을 볼 수 없는 방"에 갇혀 있을 뿐입니다.


2. 핵심 개념

Docker의 세계는 이미지(Image), 컨테이너(Container), 레지스트리(Registry)라는 세 가지 핵심 개념을 중심으로 돌아갑니다.

개념비유설명
이미지(Image)클래스 / 템플릿읽기 전용 애플리케이션 템플릿으로 코드, 런타임, 라이브러리, 설정을 포함
컨테이너(Container)인스턴스 / 객체이미지의 실행 인스턴스로 읽기/쓰기가 가능하며 독립적인 수명 주기를 가짐
레지스트리(Registry)앱 스토어이미지를 저장하고 배포하는 서비스(Docker Hub, ACR, ECR)
Dockerfile레시피 / 청사진이미지를 구축하는 방법을 정의하는 텍스트 파일
데이터 볼륨(Volume)외장 하드데이터를 영속화하며 컨테이너가 삭제되어도 데이터가 유지됨

이미지의 계층 구조

Docker 이미지는 여러 개의 읽기 전용 계층(Layer)으로 겹겹이 쌓여 있으며, 각 Dockerfile 명령어가 하나의 계층을 만듭니다:

┌─────────────────────────┐
│  CMD ["node", "app.js"] │  ← 시작 명령 계층
├─────────────────────────┤
│  COPY . /app            │  ← 애플리케이션 코드 계층 (자주 변경)
├─────────────────────────┤
│  RUN npm install        │  ← 의존성 설치 계층 (가끔 변경)
├─────────────────────────┤
│  FROM node:18-alpine    │  ← 기본 이미지 계층 (거의 변경 안 됨)
└─────────────────────────┘

왜 계층화가 중요한가?

Docker는 각 계층을 캐싱합니다. 특정 계층에 변화가 없으면 빌드 시 캐시를 직접 재사용합니다. 따라서 Dockerfile에서 변경 빈도가 낮은 명령은 앞에(예: 의존성 설치), 변경 빈도가 높은 명령은 뒤에(예: 코드 복사) 배치해야 합니다. 이렇게 하면 대부분의 빌드에서 캐시를 적중하여 속도가 훨씬 빨라집니다.


3. Docker 수명 주기

Dockerfile 작성부터 컨테이너 실행까지, Docker의 작업 흐름은 명확한 파이프라인입니다.

Docker Lifecycle
Click each stage to inspect the details
📝
Write a Dockerfile
🔨
Build the image
☁️
Push to registry
▶️
Run the container
⚙️
Manage containers
Write a Dockerfile
A Dockerfile is the recipe for building an image. It starts from a base image and defines how to assemble the application environment step by step. Each instruction creates an image layer that Docker can cache for later builds.
Common commands
FROM node:18-alpineChoose the base image
WORKDIR /appSet the working directory
COPY package*.json ./Copy dependency files for cache reuse
RUN npm installInstall dependencies
COPY . .Copy application code
EXPOSE 3000Declare the port
CMD ["node", "server.js"]Start command

Dockerfile 일반 명령어 빠른 참조

명령어역할예시
FROM기본 이미지 지정FROM node:18-alpine
WORKDIR작업 디렉토리 설정WORKDIR /app
COPY파일을 이미지로 복사COPY package.json ./
RUN빌드 시 명령 실행RUN npm install
ENV환경 변수 설정ENV NODE_ENV=production
EXPOSE포트 선언(문서 목적으로만)EXPOSE 3000
CMD컨테이너 시작 명령CMD ["node", "app.js"]
ENTRYPOINT컨테이너 진입점(덮어쓰기 어려움)ENTRYPOINT ["nginx"]

4. Docker Compose: 다중 서비스 오케스트레이션

실제 프로젝트는 보통 컨테이너 하나가 아닙니다. 하나의 웹 애플리케이션에 애플리케이션 서버 + 데이터베이스 + Redis + Nginx가 필요할 수 있습니다. Docker Compose는 하나의 YAML 파일로 여러 컨테이너를 정의하고 관리합니다.

docker-compose.yml 예시

yaml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=secret

  redis:
    image: redis:7-alpine

volumes:
  db-data:

서비스 디스커버리

Docker Compose에서 서비스 이름이 호스트 이름입니다. app 컨테이너에서 db:5432로 데이터베이스에 직접 접속하고, redis:6379로 Redis에 접속할 수 있으며, IP 주소를 알 필요가 없습니다. 이것은 Docker에 내장된 DNS 덕분입니다.


5. 모범 사례

5.1 멀티 스테이지 빌드(Multi-stage Build)

멀티 스테이지 빌드는 이미지 크기를 최적화하는 강력한 도구입니다. 빌드 단계에서는 모든 도구와 의존성을 설치하고, 최종 단계에서는 런타임에 필요한 파일만 유지합니다.

dockerfile
# 빌드 단계
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 실행 단계
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

5.2 이미지 최적화 체크리스트

최적화 항목방법효과
작은 기본 이미지 선택ubuntu 대신 alpine 사용이미지 크기 ~200MB → ~50MB
RUN 명령어 병합여러 명령을 &&로 연결이미지 계층 수 감소
.dockerignore 사용node_modules, .git 등 제외빌드 속도 향상, 컨텍스트 크기 감소
멀티 스테이지 빌드빌드 환경과 실행 환경 분리최종 이미지에 빌드 도구 미포함
버전 번호 고정node:latest 대신 node:18.17-alpine빌드 재현성 보장

5.3 보안 실무

실무설명
root로 실행하지 않기USER node로 비 root 사용자 지정
취약점 스캔docker scout 또는 Trivy로 이미지 스캔
최소 권한필요한 패키지만 설치, 디버그 도구 미설치
비밀번호 하드코딩 금지환경 변수 또는 Docker Secrets 사용
기본 이미지 정기 업데이트보안 취약점 적시 수정

요약

Docker 컨테이너화는 현대 소프트웨어 전달의 인프라로, 모든 개발자에게 이해가 필수적입니다.

핵심 포인트 복습:

  1. 컨테이너 vs 가상 머신: 컨테이너는 호스트 커널을 공유하여 더 가볍고 빠르지만, 격리성은 VM보다 약간 낮음
  2. 핵심 3대 요소: 이미지(템플릿), 컨테이너(인스턴스), 레지스트리(배포)
  3. Dockerfile: 계층 빌드, 캐시 활용, 변경이 적은 명령을 앞에 배치
  4. Docker Compose: YAML로 다중 서비스 애플리케이션을 정의하고, 서비스 이름이 호스트 이름
  5. 프로덕션 실무: 멀티 스테이지 빌드로 이미지 축소, alpine 기본 이미지, 비 root 실행

더 읽어보기