← 목록으로

Docker란?

Docker 완전 정복: 컨테이너화로 배포와 개발을 혁신하다

서론

"내 컴퓨터에서는 잘 되는데요?"라는 말은 개발자들 사이에서 유명한 농담이지만, 실제로는 심각한 문제입니다. 개발 환경과 프로덕션 환경의 차이로 인해 발생하는 배포 문제를 해결하기 위해 Docker가 등장했습니다. Docker는 애플리케이션을 컨테이너로 패키징하여 어디서든 동일하게 실행할 수 있게 해주는 플랫폼입니다. 이 글에서는 Docker의 기본 개념부터 실전 활용까지 포괄적으로 다루겠습니다.

1. Docker란 무엇인가?

1.1 Docker의 정의

Docker는 컨테이너 기반 가상화 플랫폼으로, 애플리케이션과 그 의존성을 컨테이너라는 경량화된 패키지로 묶어 배포할 수 있게 해줍니다.

컨테이너와 가상 머신의 차이

가상 머신 (VM): ┌─────────────────────────────────┐ │ 애플리케이션 A │ │ 애플리케이션 B │ ├─────────────────────────────────┤ │ 게스트 OS (Ubuntu) │ ├─────────────────────────────────┤ │ 하이퍼바이저 (Hypervisor) │ ├─────────────────────────────────┤ │ 호스트 OS │ │ 하드웨어 │ └─────────────────────────────────┘ Docker 컨테이너: ┌─────────────────────────────────┐ │ 애플리케이션 A │ │ 애플리케이션 B │ ├─────────────────────────────────┤ │ Docker Engine │ ├─────────────────────────────────┤ │ 호스트 OS │ │ 하드웨어 │ └─────────────────────────────────┘

주요 차이점:

  • 가상 머신: 각 VM마다 독립적인 OS가 필요하여 리소스 사용량이 큼
  • 컨테이너: 호스트 OS를 공유하여 경량화되고 빠르게 시작됨

1.2 Docker의 핵심 개념

이미지 (Image)

컨테이너를 생성하기 위한 읽기 전용 템플릿입니다. 애플리케이션과 실행 환경이 포함되어 있습니다.

컨테이너 (Container)

이미지를 실행한 인스턴스로, 독립적인 실행 환경을 제공합니다.

Dockerfile

이미지를 빌드하기 위한 명령어들이 담긴 텍스트 파일입니다.

레지스트리 (Registry)

Docker 이미지를 저장하고 공유하는 저장소입니다. Docker Hub가 대표적입니다.

2. Docker 설치와 기본 사용법

2.1 Docker 설치

Linux (Ubuntu/Debian)

# Docker 저장소 추가
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

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

# Docker 서비스 시작
sudo systemctl start docker
sudo systemctl enable docker

macOS / Windows

Docker Desktop을 다운로드하여 설치합니다:

2.2 기본 Docker 명령어

# Docker 버전 확인
docker --version

# 실행 중인 컨테이너 확인
docker ps

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

# 이미지 목록 확인
docker images

# 컨테이너 실행
docker run hello-world

# 백그라운드에서 컨테이너 실행
docker run -d nginx

# 포트 매핑
docker run -d -p 8080:80 nginx

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

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

# 이미지 삭제
docker rmi <image_name>

# 컨테이너 로그 확인
docker logs <container_id>

# 실행 중인 컨테이너에 접속
docker exec -it <container_id> /bin/bash

3. Dockerfile 작성하기

3.1 기본 Dockerfile 구조

# 베이스 이미지 지정
FROM node:18-alpine

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

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

# 의존성 설치
RUN npm install

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

# 포트 노출
EXPOSE 3000

# 애플리케이션 실행
CMD ["node", "server.js"]

3.2 Dockerfile 명령어 설명

FROM

베이스 이미지를 지정합니다. 공식 이미지를 사용하는 것이 좋습니다.

FROM ubuntu:22.04
FROM node:18-alpine
FROM python:3.11-slim
FROM nginx:latest

WORKDIR

작업 디렉토리를 설정합니다. 이후 명령어는 이 디렉토리에서 실행됩니다.

WORKDIR /app

COPY / ADD

파일을 컨테이너로 복사합니다. ADD는 URL이나 압축 파일 해제 기능이 있지만, 일반적으로 COPY를 사용합니다.

# 단일 파일 복사
COPY package.json ./

# 디렉토리 복사
COPY src/ ./src/

# 여러 파일 복사
COPY package*.json ./
COPY . .

RUN

이미지 빌드 시점에 실행되는 명령어입니다.

RUN apt-get update && apt-get install -y curl
RUN npm install
RUN pip install -r requirements.txt

CMD / ENTRYPOINT

컨테이너가 시작될 때 실행되는 명령어입니다.

# CMD: 기본 명령어 (덮어쓸 수 있음)
CMD ["node", "server.js"]

# ENTRYPOINT: 항상 실행되는 명령어
ENTRYPOINT ["node"]
CMD ["server.js"]

EXPOSE

컨테이너가 사용하는 포트를 문서화합니다. 실제로 포트를 열지는 않습니다.

EXPOSE 3000
EXPOSE 80 443

ENV

환경 변수를 설정합니다.

ENV NODE_ENV=production
ENV APP_PORT=3000

3.3 실전 Dockerfile 예제

Node.js 애플리케이션

# 멀티 스테이지 빌드로 이미지 크기 최적화
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine AS runner

WORKDIR /app

# 프로덕션 의존성만 복사
COPY --from=builder /app/node_modules ./node_modules
COPY . .

# 비root 사용자로 실행 (보안)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

EXPOSE 3000

CMD ["node", "server.js"]

Python 애플리케이션

FROM python:3.11-slim

WORKDIR /app

# 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

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

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Nginx 정적 사이트

FROM nginx:alpine

# 커스텀 설정 파일 복사
COPY nginx.conf /etc/nginx/nginx.conf

# 정적 파일 복사
COPY dist/ /usr/share/nginx/html/

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

4. 이미지 빌드와 실행

4.1 이미지 빌드

# 기본 빌드
docker build -t my-app:latest .

# 태그 지정 빌드
docker build -t my-app:v1.0.0 .

# 빌드 캐시 없이 빌드
docker build --no-cache -t my-app:latest .

# 특정 Dockerfile 사용
docker build -f Dockerfile.prod -t my-app:prod .

4.2 컨테이너 실행

# 기본 실행
docker run my-app:latest

# 포트 매핑
docker run -p 3000:3000 my-app:latest

# 환경 변수 설정
docker run -e NODE_ENV=production my-app:latest

# 볼륨 마운트
docker run -v /host/path:/container/path my-app:latest

# 백그라운드 실행
docker run -d my-app:latest

# 컨테이너 이름 지정
docker run --name my-container my-app:latest

# 모든 옵션 조합
docker run -d \
  --name my-app \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -v $(pwd)/data:/app/data \
  my-app:latest

5. Docker Compose

5.1 Docker Compose란?

여러 컨테이너를 하나의 애플리케이션으로 정의하고 관리하는 도구입니다.

5.2 docker-compose.yml 작성

version: '3.8'

services:
  # 웹 애플리케이션
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    volumes:
      - ./src:/app/src
    depends_on:
      - db
      - redis

  # 데이터베이스
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  # Redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  # Nginx 리버스 프록시
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

volumes:
  postgres_data:
  redis_data:

5.3 Docker Compose 명령어

# 서비스 시작
docker-compose up

# 백그라운드에서 시작
docker-compose up -d

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

# 서비스 중지
docker-compose stop

# 서비스 중지 및 컨테이너 삭제
docker-compose down

# 볼륨까지 삭제
docker-compose down -v

# 로그 확인
docker-compose logs

# 특정 서비스 로그 확인
docker-compose logs web

# 서비스 재시작
docker-compose restart

# 이미지 재빌드
docker-compose build

# 이미지 재빌드 후 시작
docker-compose up --build

6. 볼륨과 데이터 관리

6.1 볼륨 (Volume)

컨테이너의 데이터를 영구적으로 저장하기 위한 메커니즘입니다.

# 볼륨 생성
docker volume create my-volume

# 볼륨 목록 확인
docker volume ls

# 볼륨 정보 확인
docker volume inspect my-volume

# 볼륨 삭제
docker volume rm my-volume

# 사용하지 않는 볼륨 삭제
docker volume prune

6.2 볼륨 마운트

# 네임드 볼륨 마운트
docker run -v my-volume:/data my-app

# 바인드 마운트 (호스트 디렉토리)
docker run -v /host/path:/container/path my-app

# Docker Compose에서 볼륨 사용
volumes:
  - my-volume:/app/data
  - ./config:/app/config

6.3 데이터 영속성 예제

# docker-compose.yml
services:
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  postgres_data:

7. 네트워킹

7.1 Docker 네트워크

# 네트워크 목록 확인
docker network ls

# 네트워크 생성
docker network create my-network

# 네트워크에 컨테이너 연결
docker run --network my-network my-app

# 네트워크 정보 확인
docker network inspect my-network

# 네트워크 삭제
docker network rm my-network

7.2 Docker Compose 네트워킹

services:
  web:
    build: .
    networks:
      - frontend
      - backend

  api:
    build: ./api
    networks:
      - backend

networks:
  frontend:
  backend:

8. 멀티 스테이지 빌드

이미지 크기를 최적화하기 위해 여러 단계로 빌드하는 방법입니다.

# 빌드 스테이지
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# 프로덕션 스테이지
FROM node:18-alpine AS production

WORKDIR /app

# 프로덕션 의존성만 설치
COPY package*.json ./
RUN npm ci --only=production

# 빌드된 파일만 복사
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public

EXPOSE 3000

CMD ["node", "dist/server.js"]

9. 보안 모범 사례

9.1 비root 사용자

FROM node:18-alpine

# 사용자 생성
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 사용자 전환
USER nextjs

WORKDIR /app
COPY --chown=nextjs:nodejs . .

9.2 최소 권한 원칙

# 필요한 패키지만 설치
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

9.3 시크릿 관리

# docker-compose.yml
services:
  app:
    build: .
    secrets:
      - db_password
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

10. 실전 예제: 풀스택 애플리케이션

10.1 프로젝트 구조

my-app/ ├── frontend/ │ ├── Dockerfile │ └── src/ ├── backend/ │ ├── Dockerfile │ └── src/ ├── docker-compose.yml └── nginx.conf

10.2 Frontend Dockerfile

# frontend/Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

10.3 Backend Dockerfile

# backend/Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

10.4 docker-compose.yml

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

11. Docker 이미지 최적화

11.1 이미지 크기 최적화

# ❌ 나쁜 예: 불필요한 파일 포함
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nodejs npm
COPY . .
RUN npm install
CMD ["node", "server.js"]

# ✅ 좋은 예: Alpine 기반, 멀티 스테이지 빌드
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 package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/server.js"]

11.2 빌드 캐시 활용

# 의존성 파일을 먼저 복사하여 캐시 활용
COPY package*.json ./
RUN npm install

# 소스 코드는 나중에 복사
COPY . .

11.3 .dockerignore 파일

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.vscode
.idea
dist
build

12. CI/CD 통합

12.1 GitHub Actions 예제

# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: myusername/my-app:latest

13. 모니터링과 로깅

13.1 로그 관리

# 로그 확인
docker logs <container_id>

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

# 마지막 N줄만 확인
docker logs --tail 100 <container_id>

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

13.2 헬스체크

# Dockerfile에 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
# docker-compose.yml
services:
  web:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

14. 트러블슈팅

14.1 일반적인 문제 해결

# 컨테이너가 시작되지 않을 때
docker logs <container_id>

# 디스크 공간 확인
docker system df

# 사용하지 않는 리소스 정리
docker system prune -a

# 컨테이너 내부 디버깅
docker exec -it <container_id> /bin/sh

# 네트워크 문제 확인
docker network inspect bridge

14.2 성능 문제 해결

# 컨테이너 리소스 사용량 확인
docker stats

# 특정 컨테이너 리소스 제한
docker run --memory="512m" --cpus="1.0" my-app

15. 결론

Docker는 현대 소프트웨어 개발과 배포에 혁명을 가져왔습니다. 컨테이너화를 통해 개발 환경과 프로덕션 환경의 일관성을 보장하고, 배포 프로세스를 간소화하며, 리소스 사용을 최적화할 수 있습니다.

Docker를 효과적으로 활용하기 위해서는:

  • 명확한 Dockerfile 작성: 멀티 스테이지 빌드와 캐시 최적화
  • Docker Compose 활용: 복잡한 애플리케이션의 오케스트레이션
  • 보안 고려: 비root 사용자, 최소 권한 원칙
  • 모니터링: 로그와 헬스체크를 통한 상태 관리

Docker는 단순한 도구가 아니라 개발 워크플로우를 근본적으로 변화시키는 플랫폼입니다. 마이크로서비스 아키텍처, 클라우드 네이티브 개발, DevOps 실무에서 Docker는 필수적인 기술이 되었습니다. 올바르게 이해하고 활용한다면, 더 빠르고 안정적인 소프트웨어 개발과 배포가 가능해집니다.