도커
Docker
컨테이너 기반 가상화 플랫폼. 애플리케이션을 이미지로 패키징하여 어디서든 동일하게 실행. 개발 환경 표준화의 사실상 표준입니다.
Docker
컨테이너 기반 가상화 플랫폼. 애플리케이션을 이미지로 패키징하여 어디서든 동일하게 실행. 개발 환경 표준화의 사실상 표준입니다.
Docker는 2013년 Docker Inc.(구 dotCloud)에서 출시한 컨테이너 플랫폼으로, "Build, Ship, Run" 철학 아래 개발 환경의 일관성 문제("내 컴퓨터에서는 되는데...")를 해결했습니다. Linux 컨테이너(LXC) 기술을 사용자 친화적으로 만들어 대중화시켰으며, 현재 전 세계 1,500만 명 이상의 개발자가 사용합니다.
Docker 에코시스템:
컨테이너 런타임. dockerd 데몬, containerd, runc로 구성
Mac/Windows용 GUI 앱. VM 위에서 Linux 컨테이너 실행
공개 이미지 레지스트리. Official 이미지, 커뮤니티 이미지 제공
멀티 컨테이너 정의. YAML로 서비스, 네트워크, 볼륨 선언
핵심 개념: Docker는 이미지(Image)와 컨테이너(Container)의 관계로 동작합니다. 이미지는 읽기 전용 템플릿(클래스)이고, 컨테이너는 이미지의 실행 인스턴스(객체)입니다. Dockerfile로 이미지를 정의하고, docker build로 빌드하고, docker run으로 컨테이너를 실행합니다.
라이선스 변경: 2021년부터 Docker Desktop은 대기업(직원 250명 이상 또는 연매출 $10M 이상)에서 유료입니다. 오픈소스 대안으로 Podman(Red Hat), Rancher Desktop, Colima(macOS) 등이 있으며, OCI 표준 호환으로 Docker 명령어와 거의 동일하게 사용할 수 있습니다.
# === Docker 기본 명령어 ===
# 이미지 관리
docker pull node:20-alpine # 이미지 다운로드
docker images # 로컬 이미지 목록
docker rmi node:20-alpine # 이미지 삭제
docker image prune -a # 미사용 이미지 전체 삭제
# 컨테이너 실행
docker run -d --name myapp -p 3000:3000 myapp:v1
# -d: 백그라운드 실행 (detached)
# --name: 컨테이너 이름 지정
# -p: 포트 매핑 (호스트:컨테이너)
docker run -it --rm node:20-alpine /bin/sh
# -it: 대화형 터미널
# --rm: 종료 시 자동 삭제
# 환경 변수 & 볼륨
docker run -d \
-e NODE_ENV=production \
-e DATABASE_URL=postgres://... \
-v $(pwd)/data:/app/data \
-v /app/node_modules \
myapp:v1
# 컨테이너 관리
docker ps # 실행 중인 컨테이너
docker ps -a # 모든 컨테이너 (정지 포함)
docker logs -f myapp # 로그 실시간 확인
docker exec -it myapp /bin/sh # 실행 중 컨테이너 접속
docker stop myapp # 컨테이너 정지
docker rm myapp # 컨테이너 삭제
docker container prune # 정지된 컨테이너 전체 삭제
# 이미지 빌드 & 푸시
docker build -t username/myapp:v1 .
docker login # Docker Hub 로그인
docker push username/myapp:v1 # 이미지 업로드
# 시스템 정리
docker system df # 디스크 사용량 확인
docker system prune -a --volumes # 미사용 리소스 전체 정리 (주의!)
# 네트워크
docker network create app-network # 사용자 정의 네트워크 생성
docker network ls # 네트워크 목록
docker run -d --network app-network --name db postgres:16
# === Dockerfile 베스트 프랙티스 ===
# 1. 멀티스테이지 빌드 (이미지 경량화)
# ===== Stage 1: 빌드 =====
FROM node:20-alpine AS builder
WORKDIR /app
# 의존성 파일만 먼저 복사 (캐시 최적화)
COPY package*.json ./
RUN npm ci
# 소스 코드 복사 & 빌드
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# 프로덕션 의존성만 재설치
RUN npm ci --only=production && npm cache clean --force
# ===== Stage 2: 런타임 =====
FROM node:20-alpine AS runner
# 보안: 비루트 사용자 설정
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nodejs -u 1001
WORKDIR /app
# 빌드 결과물만 복사 (소스코드, devDeps 제외)
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
# 비루트 사용자로 전환
USER nodejs
# 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000
# 포트 노출 (문서화 목적)
EXPOSE 3000
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# 실행 명령
CMD ["node", "dist/server.js"]
# === 결과 ===
# 빌드 스테이지: ~1GB (node_modules, TypeScript, 소스코드)
# 런타임 스테이지: ~150MB (Alpine + dist + production deps만)
# === docker-compose.yml (로컬 개발 환경) ===
version: "3.9"
services:
# Node.js 애플리케이션
app:
build:
context: .
dockerfile: Dockerfile.dev
container_name: myapp
ports:
- "3000:3000"
- "9229:9229" # 디버깅 포트
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
volumes:
- .:/app # 소스코드 바인드 마운트 (핫 리로드)
- /app/node_modules # node_modules는 컨테이너 것 사용
depends_on:
db:
condition: service_healthy
networks:
- app-network
command: npm run dev # 개발 서버 실행
# PostgreSQL 데이터베이스
db:
image: postgres:16-alpine
container_name: myapp-db
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network
# Redis 캐시
redis:
image: redis:7-alpine
container_name: myapp-redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- app-network
# Named Volumes (데이터 영속화)
volumes:
postgres_data:
redis_data:
# 네트워크
networks:
app-network:
driver: bridge
# === 명령어 ===
# docker compose up -d # 백그라운드 실행
# docker compose up --build # 이미지 재빌드 후 실행
# docker compose logs -f app # app 서비스 로그 확인
# docker compose exec app sh # app 컨테이너 접속
# docker compose down # 정지 및 삭제
# docker compose down -v # 볼륨까지 삭제
"README의 docker compose up 한 줄이면 개발 환경 전체가 올라와요. DB, Redis, 앱까지요. Node 버전 맞추고 PostgreSQL 설치하고 할 필요 없어요. 첫 날부터 코드 작성할 수 있습니다."
"Dockerfile에서 npm install 전에 package.json만 먼저 COPY하는 이유는 캐시 최적화예요. 소스 코드만 바뀌면 의존성 설치 레이어는 캐시에서 가져오니까 빌드 시간이 3분에서 30초로 줄어요."
"Docker 이미지 크기 최적화 경험이 있습니다. Alpine 베이스로 바꾸고, 멀티스테이지 빌드로 빌드 도구 제외하고, .dockerignore로 불필요한 파일 제외해서 1.2GB에서 120MB로 90% 줄였어요. CI/CD 시간과 레지스트리 비용 모두 개선됐습니다."
"Docker Scout이나 Trivy로 이미지 취약점 스캔하고 있어요. base 이미지 CVE가 발견되면 Renovate Bot이 자동으로 PR 올려주고, CI에서 스캔 통과해야 배포됩니다. latest 태그 대신 버전 고정하는 것도 중요해요."
기본값이 root입니다. 컨테이너 탈출 취약점 발생 시 호스트 전체가 위험해집니다. Dockerfile에 USER 지시자로 비루트 사용자를 설정하세요. --privileged 플래그는 절대 사용하지 마세요.
FROM node:latest는 언제든 버전이 바뀔 수 있어 빌드 재현성이 없습니다. 반드시 버전을 명시하세요: node:20.11.1-alpine. CI에서 갑자기 빌드가 깨지는 원인이 됩니다.
대기업(250명+ 또는 $10M+ 매출)에서는 Docker Desktop이 유료입니다. Podman, Colima, Rancher Desktop 같은 무료 대안을 검토하세요. CLI는 여전히 무료입니다.
node_modules, .git, .env, 테스트 파일 등을 .dockerignore에 추가하세요. 빌드 컨텍스트가 작아져 빌드 속도가 빨라지고, 민감 정보가 이미지에 포함되는 것을 방지합니다.