Docker는 우리가 소프트웨어를 구축, 배포, 실행하는 방식을 바꿔놓았습니다. "내 컴퓨터에서는 작동해"가 아니라, Docker는 여러분의 애플리케이션이 어디서든 동일하게 실행되도록 보장합니다 - 노트북에서, 동료의 컴퓨터에서, CI/CD에서, 그리고 프로덕션에서. 이 가이드에서는 처음부터 실제 애플리케이션 배포까지 다루겠습니다.
Docker란?
Docker는 애플리케이션과 모든 종속성을 컨테이너라는 표준화된 단위로 패키징하는 플랫폼입니다. 컨테이너는 호스트 OS 커널을 공유하면서도 자체 파일시스템, 네트워크, 프로세스 공간을 가진 격리된 경량 프로세스입니다.
컨테이너 vs 가상 머신
| 항목 | 컨테이너 | 가상 머신 |
|---|---|---|
| 시작 | 초 단위 | 분 단위 |
| 크기 | MB 단위 | GB 단위 |
| OS | 호스트 커널 공유 | 전체 게스트 OS |
| 격리 | 프로세스 수준 | 하드웨어 수준 |
| 성능 | 거의 네이티브 | 하이퍼바이저 오버헤드 |
| 밀도 | 호스트당 수백 개 | 호스트당 수십 개 |
Docker 설치
# macOS brew install --cask docker # Ubuntu/Debian curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Verify installation docker --version docker run hello-world
핵심 개념
이미지
이미지는 컨테이너를 생성하기 위한 명령이 담긴 읽기 전용 템플릿입니다. 애플리케이션과 환경의 스냅샷이라고 생각하세요.
# Pull an image from Docker Hub docker pull node:20-alpine # List local images docker images # Remove an image docker rmi node:20-alpine
컨테이너
컨테이너는 이미지의 실행 중인 인스턴스입니다. 컨테이너를 생성, 시작, 중지, 삭제할 수 있습니다.
# Run a container docker run -d --name my-app -p 3000:3000 node:20-alpine # List running containers docker ps # List all containers (including stopped) docker ps -a # Stop a container docker stop my-app # Remove a container docker rm my-app # View logs docker logs my-app # Execute a command inside a running container docker exec -it my-app sh
Dockerfile 작성
Dockerfile은 이미지를 빌드하기 위한 명령이 담긴 텍스트 파일입니다. 각 명령은 하나의 레이어를 생성합니다.
Node.js 앱을 위한 기본 Dockerfile
# Use an official Node.js runtime as base image FROM node:20-alpine # Set working directory WORKDIR /app # Copy package files first (better caching) COPY package.json package-lock.json ./ # Install dependencies RUN npm ci --only=production # Copy application code COPY . . # Expose the port the app runs on EXPOSE 3000 # Command to run the application CMD ["node", "server.js"]
빌드 및 실행
# Build the image docker build -t my-node-app . # Run the container docker run -d -p 3000:3000 my-node-app # Visit http://localhost:3000
멀티 스테이지 빌드
멀티 스테이지 빌드는 빌드 환경을 런타임에서 분리하여 프로덕션 이미지를 작게 유지합니다.
# Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production FROM node:20-alpine AS runner WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"]
이렇게 하면 컴파일된 출력과 프로덕션 종속성만 포함된 이미지가 생성됩니다 - 소스 코드 없이, 개발 종속성 없이, 빌드 도구 없이.
Next.js 멀티 스테이지 예제
FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 CMD ["node", "server.js"]
볼륨: 영구 데이터
기본적으로 컨테이너 내부의 데이터는 컨테이너가 제거되면 사라집니다. 볼륨이 이 문제를 해결합니다.
# Create a named volume docker volume create my-data # Run with a volume docker run -d -v my-data:/app/data my-app # Bind mount (map host directory to container) docker run -d -v $(pwd)/data:/app/data my-app # List volumes docker volume ls
네트워킹
Docker는 컨테이너가 통신할 수 있도록 격리된 네트워크를 생성합니다.
# Create a custom network docker network create my-network # Run containers on the same network docker run -d --name api --network my-network my-api docker run -d --name db --network my-network postgres:16 # Containers can reach each other by name # From "api" container: postgres://db:5432
Docker Compose
Docker Compose를 사용하면 단일 YAML 파일로 멀티 컨테이너 애플리케이션을 정의하고 실행할 수 있습니다.
docker-compose.yml
services: api: build: ./api ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db db: image: postgres:16-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
명령어
# Start all services docker compose up -d # View logs docker compose logs -f # Stop all services docker compose down # Rebuild and restart docker compose up -d --build # Scale a service docker compose up -d --scale api=3
.dockerignore
.gitignore와 마찬가지로, 이 파일은 불필요한 파일이 이미지에 복사되는 것을 방지합니다.
node_modules .git .env *.md .next dist coverage
프로덕션 모범 사례
1. 작은 베이스 이미지 사용
# Bad: 1GB+ FROM node:20 # Good: ~180MB FROM node:20-alpine
2. Root로 실행하지 않기
FROM node:20-alpine RUN addgroup -S app && adduser -S app -G app USER app WORKDIR /home/app COPY . .
3. 특정 이미지 태그 사용
# Bad: can change unexpectedly FROM node:latest # Good: pinned version FROM node:20.11-alpine3.19
4. 빌드 캐시 활용
Dockerfile 명령을 변경 빈도가 낮은 것부터 높은 것 순으로 정렬하세요:
FROM node:20-alpine WORKDIR /app # These change rarely - cached COPY package.json package-lock.json ./ RUN npm ci --only=production # This changes often - not cached COPY . .
5. 헬스 체크
HEALTHCHECK \ CMD wget -qO- http://localhost:3000/health || exit 1
6. 환경 변수 사용
ENV NODE_ENV=production ENV PORT=3000
자주 사용하는 Docker 명령어 치트 시트
# Images docker build -t name:tag . # Build image docker images # List images docker rmi image_name # Remove image docker image prune # Remove unused images # Containers docker run -d -p 3000:3000 image # Run detached docker ps # List running docker stop container_name # Stop docker rm container_name # Remove docker logs -f container_name # Follow logs docker exec -it container sh # Shell into container # Compose docker compose up -d # Start services docker compose down # Stop services docker compose logs -f # Follow all logs docker compose ps # List services # Cleanup docker system prune -a # Remove everything unused
Docker에서 Kubernetes로
Docker는 개별 컨테이너를 관리합니다. 여러 서버에 걸쳐 수백 개의 컨테이너를 오케스트레이션해야 할 때 Kubernetes가 필요합니다. Docker와 Kubernetes는 상호 보완적입니다:
- Docker: 컨테이너를 빌드하고 실행
- Kubernetes: 대규모로 컨테이너를 오케스트레이션 (스케줄링, 스케일링, 자가 복구)
다음 단계에 관심이 있다면, Kubernetes 소개에 대한 제 글을 확인해보세요.
결론
Docker는 현대 개발자에게 필수적인 기술입니다. 환경 불일치를 제거하고, 배포를 단순화하며, Kubernetes를 통한 컨테이너 오케스트레이션의 기반입니다. 간단한 Dockerfile로 시작하여, 멀티 서비스 앱에는 Docker Compose로 이동하고, 성장하면서 멀티 스테이지 빌드와 보안 모범 사례를 도입하세요.
Docker를 배우는 가장 좋은 방법은 이미 작업 중인 프로젝트를 컨테이너화하는 것입니다. 오늘 시작하세요.