☁️ 클라우드

도커

Docker

컨테이너 기반 가상화 플랫폼. 애플리케이션을 이미지로 패키징하여 어디서든 동일하게 실행. 개발 환경 표준화의 사실상 표준입니다.

📖 상세 설명

Docker는 2013년 Docker Inc.(구 dotCloud)에서 출시한 컨테이너 플랫폼으로, "Build, Ship, Run" 철학 아래 개발 환경의 일관성 문제("내 컴퓨터에서는 되는데...")를 해결했습니다. Linux 컨테이너(LXC) 기술을 사용자 친화적으로 만들어 대중화시켰으며, 현재 전 세계 1,500만 명 이상의 개발자가 사용합니다.

Docker 에코시스템:

Docker Engine

컨테이너 런타임. dockerd 데몬, containerd, runc로 구성

Docker Desktop

Mac/Windows용 GUI 앱. VM 위에서 Linux 컨테이너 실행

Docker Hub

공개 이미지 레지스트리. Official 이미지, 커뮤니티 이미지 제공

Docker Compose

멀티 컨테이너 정의. YAML로 서비스, 네트워크, 볼륨 선언

핵심 개념: Docker는 이미지(Image)와 컨테이너(Container)의 관계로 동작합니다. 이미지는 읽기 전용 템플릿(클래스)이고, 컨테이너는 이미지의 실행 인스턴스(객체)입니다. Dockerfile로 이미지를 정의하고, docker build로 빌드하고, docker run으로 컨테이너를 실행합니다.

라이선스 변경: 2021년부터 Docker Desktop은 대기업(직원 250명 이상 또는 연매출 $10M 이상)에서 유료입니다. 오픈소스 대안으로 Podman(Red Hat), Rancher Desktop, Colima(macOS) 등이 있으며, OCI 표준 호환으로 Docker 명령어와 거의 동일하게 사용할 수 있습니다.

💻 코드 예제

# === Docker 기본 명령어 ===

# 이미지 관리
docker pull node:20-alpine          # 이미지 다운로드
docker images                       # 로컬 이미지 목록
docker rmi node:20-alpine           # 이미지 삭제
docker image prune -a               # 미사용 이미지 전체 삭제

# 컨테이너 실행
docker run -d --name myapp -p 3000:3000 myapp:v1
# -d: 백그라운드 실행 (detached)
# --name: 컨테이너 이름 지정
# -p: 포트 매핑 (호스트:컨테이너)

docker run -it --rm node:20-alpine /bin/sh
# -it: 대화형 터미널
# --rm: 종료 시 자동 삭제

# 환경 변수 & 볼륨
docker run -d \
    -e NODE_ENV=production \
    -e DATABASE_URL=postgres://... \
    -v $(pwd)/data:/app/data \
    -v /app/node_modules \
    myapp:v1

# 컨테이너 관리
docker ps                           # 실행 중인 컨테이너
docker ps -a                        # 모든 컨테이너 (정지 포함)
docker logs -f myapp                # 로그 실시간 확인
docker exec -it myapp /bin/sh       # 실행 중 컨테이너 접속
docker stop myapp                   # 컨테이너 정지
docker rm myapp                     # 컨테이너 삭제
docker container prune              # 정지된 컨테이너 전체 삭제

# 이미지 빌드 & 푸시
docker build -t username/myapp:v1 .
docker login                        # Docker Hub 로그인
docker push username/myapp:v1       # 이미지 업로드

# 시스템 정리
docker system df                    # 디스크 사용량 확인
docker system prune -a --volumes    # 미사용 리소스 전체 정리 (주의!)

# 네트워크
docker network create app-network   # 사용자 정의 네트워크 생성
docker network ls                   # 네트워크 목록
docker run -d --network app-network --name db postgres:16
# === Dockerfile 베스트 프랙티스 ===

# 1. 멀티스테이지 빌드 (이미지 경량화)
# ===== Stage 1: 빌드 =====
FROM node:20-alpine AS builder
WORKDIR /app

# 의존성 파일만 먼저 복사 (캐시 최적화)
COPY package*.json ./
RUN npm ci

# 소스 코드 복사 & 빌드
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# 프로덕션 의존성만 재설치
RUN npm ci --only=production && npm cache clean --force

# ===== Stage 2: 런타임 =====
FROM node:20-alpine AS runner

# 보안: 비루트 사용자 설정
RUN addgroup -g 1001 -S nodejs \
    && adduser -S nodejs -u 1001

WORKDIR /app

# 빌드 결과물만 복사 (소스코드, devDeps 제외)
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

# 비루트 사용자로 전환
USER nodejs

# 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000

# 포트 노출 (문서화 목적)
EXPOSE 3000

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

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

# === 결과 ===
# 빌드 스테이지: ~1GB (node_modules, TypeScript, 소스코드)
# 런타임 스테이지: ~150MB (Alpine + dist + production deps만)
# === docker-compose.yml (로컬 개발 환경) ===
version: "3.9"

services:
  # Node.js 애플리케이션
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: myapp
    ports:
      - "3000:3000"
      - "9229:9229"  # 디버깅 포트
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://user:password@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - .:/app                    # 소스코드 바인드 마운트 (핫 리로드)
      - /app/node_modules         # node_modules는 컨테이너 것 사용
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network
    command: npm run dev          # 개발 서버 실행

  # PostgreSQL 데이터베이스
  db:
    image: postgres:16-alpine
    container_name: myapp-db
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # Redis 캐시
  redis:
    image: redis:7-alpine
    container_name: myapp-redis
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - app-network

# Named Volumes (데이터 영속화)
volumes:
  postgres_data:
  redis_data:

# 네트워크
networks:
  app-network:
    driver: bridge

# === 명령어 ===
# docker compose up -d          # 백그라운드 실행
# docker compose up --build     # 이미지 재빌드 후 실행
# docker compose logs -f app    # app 서비스 로그 확인
# docker compose exec app sh    # app 컨테이너 접속
# docker compose down           # 정지 및 삭제
# docker compose down -v        # 볼륨까지 삭제

🗣️ 실무에서 이렇게 말하세요

💬 온보딩에서
"README의 docker compose up 한 줄이면 개발 환경 전체가 올라와요. DB, Redis, 앱까지요. Node 버전 맞추고 PostgreSQL 설치하고 할 필요 없어요. 첫 날부터 코드 작성할 수 있습니다."
💬 코드 리뷰에서
"Dockerfile에서 npm install 전에 package.json만 먼저 COPY하는 이유는 캐시 최적화예요. 소스 코드만 바뀌면 의존성 설치 레이어는 캐시에서 가져오니까 빌드 시간이 3분에서 30초로 줄어요."
💬 면접에서
"Docker 이미지 크기 최적화 경험이 있습니다. Alpine 베이스로 바꾸고, 멀티스테이지 빌드로 빌드 도구 제외하고, .dockerignore로 불필요한 파일 제외해서 1.2GB에서 120MB로 90% 줄였어요. CI/CD 시간과 레지스트리 비용 모두 개선됐습니다."
💬 보안 이슈에서
"Docker Scout이나 Trivy로 이미지 취약점 스캔하고 있어요. base 이미지 CVE가 발견되면 Renovate Bot이 자동으로 PR 올려주고, CI에서 스캔 통과해야 배포됩니다. latest 태그 대신 버전 고정하는 것도 중요해요."

⚠️ 흔한 실수 & 주의사항

root 사용자로 컨테이너 실행

기본값이 root입니다. 컨테이너 탈출 취약점 발생 시 호스트 전체가 위험해집니다. Dockerfile에 USER 지시자로 비루트 사용자를 설정하세요. --privileged 플래그는 절대 사용하지 마세요.

latest 태그 사용

FROM node:latest는 언제든 버전이 바뀔 수 있어 빌드 재현성이 없습니다. 반드시 버전을 명시하세요: node:20.11.1-alpine. CI에서 갑자기 빌드가 깨지는 원인이 됩니다.

Docker Desktop 라이선스

대기업(250명+ 또는 $10M+ 매출)에서는 Docker Desktop이 유료입니다. Podman, Colima, Rancher Desktop 같은 무료 대안을 검토하세요. CLI는 여전히 무료입니다.

.dockerignore 필수

node_modules, .git, .env, 테스트 파일 등을 .dockerignore에 추가하세요. 빌드 컨텍스트가 작아져 빌드 속도가 빨라지고, 민감 정보가 이미지에 포함되는 것을 방지합니다.

🔗 관련 용어

📚 더 배우기