Docker 완벽 가이드: 컨테이너 기반 개발 환경 구축
이 글의 핵심
Docker는 애플리케이션을 컨테이너로 패키징하여 어디서나 동일하게 실행할 수 있게 하는 플랫폼입니다. VM보다 가볍고 빠르며, 이미지 레이어 시스템으로 효율적인 스토리지 관리가 가능합니다. Docker Compose로 멀티 컨테이너 애플리케이션을 쉽게 관리하고, 개발/프로덕션 환경을 일관되게 유지할 수 있습니다.
Docker란?
Docker는 애플리케이션을 컨테이너로 패키징하고 실행하는 오픈소스 플랫폼입니다. “Build once, Run anywhere” 철학으로 개발, 배포, 실행을 일관되게 만듭니다.
핵심 개념
-
컨테이너
- 격리된 실행 환경
- 애플리케이션 + 의존성
- 이식 가능
-
이미지
- 컨테이너 템플릿
- Read-only 레이어
- Docker Hub에서 공유
-
레지스트리
- 이미지 저장소
- Docker Hub (공개)
- Private Registry
-
레이어
- 이미지 구성 단위
- 캐싱 및 재사용
- 효율적인 저장
VM vs Container 비교
| 항목 | 가상 머신 (VM) | 컨테이너 (Docker) |
|---|---|---|
| OS | 각 VM마다 전체 OS | 호스트 OS 커널 공유 |
| 크기 | GB 단위 | MB 단위 |
| 시작 시간 | 분 단위 | 초 단위 |
| 리소스 | 많이 사용 | 적게 사용 |
| 격리 | 완전 격리 | 프로세스 수준 격리 |
| 이식성 | 제한적 | 매우 높음 |
| 용도 | 전체 시스템 가상화 | 애플리케이션 격리 |
┌─────────────────────┐ ┌─────────────────────┐
│ 가상 머신 (VM) │ │ 컨테이너 (Docker) │
├─────────────────────┤ ├─────────────────────┤
│ App A │ App B │ │ App A │ App B │
│ ─────────────── │ │ ───────────────── │
│ Bins/Libs │ │ Bins/Libs │
│ ─────────────── │ │ ───────────────── │
│ Guest OS │ │ Docker Engine │
│ ─────────────── │ │ ───────────────── │
│ Hypervisor │ │ Host OS │
│ ─────────────── │ │ ───────────────── │
│ Host OS │ │ Infrastructure │
└─────────────────────┘ └─────────────────────┘
설치
Windows (WSL 2)
# Docker Desktop 다운로드 및 설치
# https://www.docker.com/products/docker-desktop
# WSL 2 활성화
wsl --install
# 버전 확인
docker --version
docker compose version
macOS
# Docker Desktop 다운로드 및 설치
# 또는 Homebrew
brew install --cask docker
# 버전 확인
docker --version
Linux (Ubuntu)
# 이전 버전 제거
sudo apt-get remove docker docker-engine docker.io containerd runc
# 저장소 설정
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
# Docker GPG 키 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 저장소 추가
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Docker 설치
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
# sudo 없이 실행
sudo usermod -aG docker $USER
newgrp docker
# 버전 확인
docker --version
기본 명령어
컨테이너 실행
# 이미지 다운로드 및 실행
docker run hello-world
# 백그라운드 실행
docker run -d nginx
# 포트 매핑
docker run -d -p 8080:80 nginx
# 이름 지정
docker run -d --name my-nginx -p 8080:80 nginx
# 환경 변수 전달
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql
# 볼륨 마운트
docker run -d -v /host/path:/container/path nginx
# 대화형 쉘
docker run -it ubuntu bash
# 컨테이너 제거 후 종료
docker run --rm -it ubuntu bash
컨테이너 관리
# 실행 중인 컨테이너 목록
docker ps
# 모든 컨테이너 목록
docker ps -a
# 컨테이너 중지
docker stop my-nginx
# 컨테이너 시작
docker start my-nginx
# 컨테이너 재시작
docker restart my-nginx
# 컨테이너 제거
docker rm my-nginx
# 강제 제거
docker rm -f my-nginx
# 모든 중지된 컨테이너 제거
docker container prune
# 컨테이너 로그
docker logs my-nginx
docker logs -f my-nginx # 실시간
# 컨테이너 접속
docker exec -it my-nginx bash
# 컨테이너 정보
docker inspect my-nginx
# 리소스 사용량
docker stats
이미지 관리
# 이미지 목록
docker images
# 이미지 다운로드
docker pull ubuntu:22.04
# 이미지 검색
docker search nginx
# 이미지 빌드
docker build -t my-app:1.0 .
# 이미지 태그
docker tag my-app:1.0 my-app:latest
# 이미지 제거
docker rmi my-app:1.0
# 사용하지 않는 이미지 제거
docker image prune
# 이미지 저장
docker save -o my-app.tar my-app:1.0
# 이미지 로드
docker load -i my-app.tar
# 이미지 히스토리
docker history my-app:1.0
Dockerfile 작성
기본 구조
# 베이스 이미지
FROM node:18-alpine
# 메타데이터
LABEL maintainer="[email protected]"
LABEL version="1.0"
# 작업 디렉터리
WORKDIR /app
# 파일 복사
COPY package*.json ./
# 명령 실행
RUN npm ci --only=production
# 소스 코드 복사
COPY . .
# 환경 변수
ENV NODE_ENV=production
ENV PORT=3000
# 포트 노출
EXPOSE 3000
# 사용자 변경 (보안)
USER node
# 실행 명령
CMD ["node", "server.js"]
Node.js 애플리케이션
# 멀티 스테이지 빌드
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
# 비루트 사용자
USER node
EXPOSE 3000
CMD ["node", "dist/index.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 . .
# 비루트 사용자
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]
Go 애플리케이션
# 빌드 스테이지
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 의존성
COPY go.mod go.sum ./
RUN go mod download
# 빌드
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# 프로덕션 스테이지 (매우 작은 이미지)
FROM scratch
COPY --from=builder /app/main /main
EXPOSE 8080
CMD ["/main"]
.dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
.DS_Store
*.md
README.md
docker-compose*.yml
Dockerfile
.dockerignore
dist
coverage
.vscode
.idea
*.log
Docker Compose
docker-compose.yml 기본
version: '3.8'
services:
# Node.js 애플리케이션
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=db
depends_on:
- db
- redis
volumes:
- ./logs:/app/logs
restart: unless-stopped
# PostgreSQL 데이터베이스
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
# Redis 캐시
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
실전 예제: MERN 스택
version: '3.8'
services:
# MongoDB
mongo:
image: mongo:7
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
volumes:
- mongo_data:/data/db
ports:
- "27017:27017"
networks:
- mern-network
# Express API
api:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "5000:5000"
environment:
- NODE_ENV=production
- MONGO_URI=mongodb://admin:secret@mongo:27017/myapp?authSource=admin
- JWT_SECRET=your-secret-key
depends_on:
- mongo
networks:
- mern-network
volumes:
- ./backend:/app
- /app/node_modules
# React Frontend
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
depends_on:
- api
networks:
- mern-network
volumes:
mongo_data:
networks:
mern-network:
driver: bridge
Docker Compose 명령어
# 시작
docker compose up
# 백그라운드 시작
docker compose up -d
# 빌드 포함 시작
docker compose up --build
# 중지
docker compose down
# 볼륨 포함 제거
docker compose down -v
# 로그 보기
docker compose logs
docker compose logs -f api
# 특정 서비스만 실행
docker compose up api
# 스케일링
docker compose up -d --scale api=3
# 실행 중인 서비스
docker compose ps
# 서비스 재시작
docker compose restart api
네트워킹
네트워크 종류
# Bridge (기본) - 같은 호스트
docker network create my-bridge
# Host - 호스트 네트워크 사용
docker run --network host nginx
# None - 네트워크 없음
docker run --network none alpine
# 사용자 정의 네트워크
docker network create --driver bridge my-network
docker run -d --name app1 --network my-network nginx
docker run -d --name app2 --network my-network nginx
# app1에서 app2로 접근
docker exec app1 ping app2
네트워크 관리
# 네트워크 목록
docker network ls
# 네트워크 상세 정보
docker network inspect my-network
# 컨테이너를 네트워크에 연결
docker network connect my-network my-container
# 컨테이너를 네트워크에서 분리
docker network disconnect my-network my-container
# 네트워크 제거
docker network rm my-network
# 사용하지 않는 네트워크 제거
docker network prune
볼륨 관리
볼륨 종류
# Named Volume
docker volume create my-volume
docker run -d -v my-volume:/app/data nginx
# Bind Mount (호스트 디렉터리)
docker run -d -v /host/path:/container/path nginx
docker run -d -v $(pwd):/app nginx
# Tmpfs Mount (메모리)
docker run -d --tmpfs /tmp nginx
볼륨 명령어
# 볼륨 목록
docker volume ls
# 볼륨 상세 정보
docker volume inspect my-volume
# 볼륨 제거
docker volume rm my-volume
# 사용하지 않는 볼륨 제거
docker volume prune
# 볼륨 백업
docker run --rm -v my-volume:/source -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /source .
# 볼륨 복원
docker run --rm -v my-volume:/target -v $(pwd):/backup alpine tar xzf /backup/backup.tar.gz -C /target
이미지 최적화
멀티 스테이지 빌드
# React 앱 예제
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Alpine 베이스 사용
# Node.js
FROM node:18-alpine # 대신 node:18 (500MB → 170MB)
# Python
FROM python:3.11-alpine # 대신 python:3.11 (900MB → 50MB)
# Go
FROM golang:1.21-alpine # 빌드용
FROM scratch # 실행용 (0MB)
레이어 캐싱 활용
# ❌ 나쁨: 소스 변경 시 의존성 재설치
COPY . .
RUN npm install
# ✅ 좋음: 의존성 먼저 설치 (캐싱)
COPY package*.json ./
RUN npm ci
COPY . .
RUN 명령 최적화
# ❌ 나쁨: 여러 레이어
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*
# ✅ 좋음: 단일 레이어 + 정리
RUN apt-get update && \
apt-get install -y curl vim && \
rm -rf /var/lib/apt/lists/*
보안 베스트 프랙티스
1. 비루트 사용자
# 사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 권한 설정
RUN chown -R appuser:appgroup /app
# 사용자 전환
USER appuser
2. 최소 권한 이미지
# scratch (아무것도 없음)
FROM scratch
# distroless (런타임만)
FROM gcr.io/distroless/nodejs18
3. 비밀 정보 관리
# ❌ Dockerfile에 하드코딩
ENV DB_PASSWORD=secret
# ✅ 환경 변수로 전달
docker run -e DB_PASSWORD=$DB_PASSWORD my-app
# ✅ Docker Secrets (Swarm)
echo "secret" | docker secret create db_password -
docker service create --secret db_password my-app
# ✅ .env 파일 (Docker Compose)
docker compose --env-file .env up
4. 이미지 스캔
# Docker Scout
docker scout cves my-app:latest
# Trivy
trivy image my-app:latest
# Snyk
snyk container test my-app:latest
헬스체크
# Dockerfile에 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Docker Compose
services:
app:
image: my-app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
CI/CD 통합
GitHub Actions
name: Docker Build and Push
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: username/app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
GitLab CI
build:
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
트러블슈팅
컨테이너가 즉시 종료될 때
# 로그 확인
docker logs container-name
# 실행 후 바로 쉘 진입
docker run -it my-app sh
# 오버라이드 명령
docker run -it my-app sh
포트가 이미 사용 중일 때
# 사용 중인 포트 확인 (Linux/Mac)
lsof -i :8080
# Windows
netstat -ano | findstr :8080
# 다른 포트로 매핑
docker run -p 8081:80 nginx
디스크 공간 정리
# 모든 정리 (위험!)
docker system prune -a
# 볼륨 포함 정리
docker system prune -a --volumes
# 빌드 캐시만
docker builder prune
Docker는 현대적인 애플리케이션 개발과 배포의 핵심 도구입니다. 컨테이너 기술로 일관된 환경을 유지하고 효율적인 리소스 관리가 가능합니다.