Docker로 개발환경 통일하기: 컨테이너 기반 개발의 모든 것

Docker로 개발환경 통일하기: 컨테이너 기반 개발의 모든 것
Photo by Ian Taylor / Unsplash

🐳 Docker로 개발환경 통일하기

"내 컴퓨터에서는 잘 되는데요?" 이 말, 개발자라면 한 번쯤은 들어봤을 겁니다. 환경 차이로 인한 문제는 개발팀의 영원한 골칫거리죠. 하지만 Docker를 사용하면 이런 문제를 깔끔하게 해결할 수 있습니다!

이 가이드에서는 Docker를 활용해 일관된 개발환경을 구축하는 모든 방법을 알아보겠습니다. 초보자도 따라할 수 있도록 단계별로 설명드릴게요! 🚀

🔍 Docker란 무엇인가?

Docker는 컨테이너 기반 가상화 플랫폼으로, 애플리케이션과 그 실행 환경을 하나의 패키지로 묶어 관리할 수 있게 해줍니다.

기존 개발환경의 문제점

  • 🖥️ 환경 차이: 개발자마다 다른 OS, 라이브러리 버전
  • 설정 시간: 새 팀원 온보딩에 하루 이상 소요
  • 🐛 버그 재현 어려움: "내 환경에서는 안 되는데요"
  • 📦 의존성 충돌: 프로젝트별 다른 버전 요구사항

Docker의 해결책

  • 🎯 환경 일관성: 어디서나 동일한 실행 환경
  • 빠른 설정: 몇 분 내에 개발환경 구축
  • 🔄 쉬운 공유: 환경 설정을 코드로 관리
  • 🏠 격리: 프로젝트간 독립적인 환경

📦 Docker 설치하기

Windows/macOS: Docker Desktop

# 1. Docker Desktop 다운로드
https://www.docker.com/products/docker-desktop

# 2. 설치 후 Docker 실행 확인
docker --version
docker run hello-world

Ubuntu/Linux

# Docker 설치
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 사용자를 docker 그룹에 추가
sudo usermod -aG docker $USER

# 재로그인 후 확인
docker --version
docker run hello-world

필수 확인사항

# Docker 서비스 상태 확인
docker info

# Docker Compose 설치 확인
docker-compose --version

# 시스템 리소스 확인
docker system df

🏗️ Dockerfile 작성 마스터하기

Dockerfile 기본 구조

# Node.js 애플리케이션 예시
FROM node:16-alpine

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

# 의존성 파일 복사 (캐시 최적화)
COPY package*.json ./

# 의존성 설치
RUN npm ci --only=production

# 애플리케이션 소스 복사
COPY . .

# 애플리케이션 사용자 생성 (보안)
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# 포트 노출
EXPOSE 3000

# 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 실행 명령
CMD ["npm", "start"]

Python Django 애플리케이션

FROM python:3.9-slim

# 시스템 패키지 업데이트
RUN apt-get update && apt-get install -y \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

# 작업 디렉터리
WORKDIR /app

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

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

# 정적 파일 수집
RUN python manage.py collectstatic --noinput

# 포트 설정
EXPOSE 8000

# 실행 명령
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]

멀티스테이지 빌드 (최적화)

# 빌드 스테이지
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 프로덕션 스테이지
FROM node:16-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["npm", "start"]

🎼 Docker Compose로 멀티 컨테이너 관리

기본 docker-compose.yml

version: '3.8'

services:
  # 웹 애플리케이션
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DB_HOST=db
      - REDIS_HOST=redis
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      - db
      - redis
    restart: unless-stopped

  # 데이터베이스
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: developer
      POSTGRES_PASSWORD: secretpassword
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    restart: unless-stopped

  # Redis 캐시
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  # Nginx 프록시
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

개발환경별 설정

# docker-compose.dev.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    environment:
      - NODE_ENV=development
      - HOT_RELOAD=true
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

  db:
    ports:
      - "5432:5432"  # 로컬 접근 허용
# docker-compose.prod.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.prod
    environment:
      - NODE_ENV=production
    restart: always
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

🛠️ 개발환경별 활용 사례

1. 프론트엔드 개발환경 (React/Vue)

# Dockerfile.dev
FROM node:16-alpine

WORKDIR /app

# 의존성 설치
COPY package*.json ./
RUN npm install

# 포트 노출
EXPOSE 3000

# 개발 서버 실행
CMD ["npm", "run", "dev"]
# docker-compose.yml
version: '3.8'

services:
  frontend:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true  # Hot reload 설정

2. 백엔드 API 개발환경

# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DEBUG=1
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    volumes:
      - .:/app
    depends_on:
      - db
      - redis

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:6-alpine
    
  # 개발도구들
  adminer:
    image: adminer
    ports:
      - "8080:8080"

volumes:
  postgres_data:

3. 마이크로서비스 개발환경

# docker-compose.yml
version: '3.8'

services:
  # API Gateway
  gateway:
    build: ./gateway
    ports:
      - "80:80"
    depends_on:
      - user-service
      - product-service

  # 사용자 서비스
  user-service:
    build: ./services/user
    environment:
      - DB_HOST=user-db
    depends_on:
      - user-db

  user-db:
    image: postgres:13
    environment:
      POSTGRES_DB: users

  # 상품 서비스
  product-service:
    build: ./services/product
    environment:
      - DB_HOST=product-db
    depends_on:
      - product-db

  product-db:
    image: mongodb:4.4

  # 메시지 큐
  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "15672:15672"

  # 모니터링
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"

⚡ 성능 최적화 전략

이미지 크기 최적화

# 1. Alpine 리눅스 사용
FROM node:16-alpine  # ubuntu 대신 alpine

# 2. 불필요한 파일 제거
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 3. 멀티스테이지 빌드
FROM node:16-alpine AS build
# ... 빌드 과정
FROM node:16-alpine AS production
COPY --from=build /app/dist ./dist

# 4. .dockerignore 활용
# .dockerignore 파일
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
coverage
.nyc_output

빌드 캐시 최적화

# 의존성 파일을 먼저 복사 (캐시 활용)
COPY package*.json ./
RUN npm ci

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

# 레이어 합치기
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

실행 시간 최적화

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    # 헬스체크로 의존성 관리
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    
    # 리소스 제한
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'

🔒 보안 베스트 프랙티스

1. 사용자 권한 관리

# root 사용자 피하기
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# 또는 기존 사용자 활용
USER node

2. 민감 정보 관리

# .env 파일 사용
# .env
DATABASE_PASSWORD=secretpassword
API_KEY=your-secret-key

# docker-compose.yml
services:
  web:
    env_file:
      - .env
    
# 또는 Docker secrets 사용 (Swarm mode)
secrets:
  db_password:
    file: ./db_password.txt

services:
  web:
    secrets:
      - db_password

3. 이미지 스캔

# Docker Hub에서 공식 이미지 사용
FROM node:16-alpine  # 공식 이미지

# 취약점 스캔
docker scan myapp:latest

# 베이스 이미지 정기 업데이트
FROM node:16-alpine@sha256:abc123...  # 특정 digest 사용

🎯 실전 워크플로우

일일 개발 루틴

#!/bin/bash
# start-dev.sh

echo "🚀 개발 환경 시작..."

# 최신 이미지 확인
docker-compose pull

# 개발 환경 실행
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

# 로그 확인
docker-compose logs -f web

팀 협업을 위한 Makefile

# Makefile
.PHONY: build up down logs shell test clean

# 개발 환경 시작
up:
	docker-compose up -d

# 개발 환경 중지
down:
	docker-compose down

# 이미지 빌드
build:
	docker-compose build

# 로그 확인
logs:
	docker-compose logs -f

# 컨테이너 접속
shell:
	docker-compose exec web sh

# 테스트 실행
test:
	docker-compose exec web npm test

# 환경 정리
clean:
	docker system prune -a

CI/CD 파이프라인 통합

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

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Build test image
      run: docker build -f Dockerfile.test -t myapp:test .
    
    - name: Run tests
      run: docker run --rm myapp:test npm test
    
    - name: Build production image
      run: docker build -t myapp:latest .
    
    - name: Login to DockerHub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_TOKEN }}
    
    - name: Push to DockerHub
      run: docker push myapp:latest

🐛 문제 해결과 디버깅

일반적인 문제들

# 1. 포트 충돌
Error: Port 3000 is already in use
# 해결: 다른 포트 사용 또는 기존 프로세스 종료
docker-compose down
lsof -ti:3000 | xargs kill

# 2. 볼륨 권한 문제
Permission denied
# 해결: 적절한 사용자 ID 설정
USER 1000:1000

# 3. 메모리 부족
OOMKilled
# 해결: 메모리 한계 증가
deploy:
  resources:
    limits:
      memory: 1G

디버깅 팁

# 컨테이너 내부 확인
docker-compose exec web sh

# 로그 실시간 확인
docker-compose logs -f --tail=100 web

# 리소스 사용량 모니터링
docker stats

# 네트워크 문제 디버깅
docker network ls
docker network inspect myapp_default

# 볼륨 상태 확인
docker volume ls
docker volume inspect myapp_postgres_data

📊 모니터링과 로깅

로깅 설정

# docker-compose.yml
services:
  web:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        
  # 중앙집중식 로깅
  elasticsearch:
    image: elasticsearch:7.14.0
    
  logstash:
    image: logstash:7.14.0
    
  kibana:
    image: kibana:7.14.0
    ports:
      - "5601:5601"

헬스체크와 모니터링

# 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:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

🎓 마무리

Docker를 활용한 개발환경 통일은 단순히 "내 컴퓨터에서는 되는데"라는 말을 없애는 것 이상의 가치가 있습니다:

  • 🚀 개발 속도 향상: 환경 설정 시간 단축
  • 🤝 협업 효율성: 일관된 개발 환경
  • 🔄 배포 일관성: 개발과 운영 환경의 일치
  • 🛡️ 격리와 보안: 안전한 개발 환경
  • 📈 확장성: 마이크로서비스 아키텍처 대응

처음에는 복잡해 보일 수 있지만, 한 번 구축해두면 팀 전체의 생산성이 크게 향상됩니다. 특히 새로운 팀원이 합류했을 때 몇 분 만에 개발환경을 구축할 수 있다는 점은 정말 큰 장점이죠.

오늘부터 Docker를 활용해서 더 효율적이고 안정적인 개발 워크플로우를 만들어보세요! 🐳

Featured image by Growtika on Unsplash