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을 다운로드하여 설치합니다:
- macOS: https://www.docker.com/products/docker-desktop/
- Windows: https://www.docker.com/products/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는 필수적인 기술이 되었습니다. 올바르게 이해하고 활용한다면, 더 빠르고 안정적인 소프트웨어 개발과 배포가 가능해집니다.