Docker 개요
컨테이너 기술의 탄생
DotCloud라는 스타트업이 2010년에 PaaS(Platform as a Service) 형태로 서비스를 시작하면서 등장했다. 2013년 오픈소스로 공개된 후 기업명도 Docker Inc.로 변경되면서 지금의 모습을 갖추게 되었다.
Linux Container(LXC)라는 커널 수준의 경량 가상화 기술을 기반으로 하며, Go 언어로 개발되었다. 핵심 철학은 "소프트웨어 운영 환경을 컨테이너라는 단위로 패키징하여 어디서든 동일하게 실행"하는 것이다.
가상 머신과의 차이
기존 가상화는 하이퍼바이저 위에 게스트 OS를 전부 올리는 방식이었다면, 컨테이너는 호스트 OS의 커널을 공유하면서 프로세스와 자원만 격리한다. 이로 인해 시작 속도가 훨씬 빠르고, 오버헤드가 극도로 적다.
핵심 구성 요소
- Registry: 이미지를 저장하고 배포하는 중앙 저장소 (Docker Hub 등)
- Image: 컨테이너 실행에 필요한 파일과 설정의 불변 스냅샷
- Container: 이미지를 기반으로 실제 실행 중인 프로세스 단위
설치 및 환경 구성
설치 방법별 비교
스크립트 자동 설치
# 시스템 업데이트 후 Docker 공식 스크립트 실행
sudo yum update -y
curl -fsSL https://get.docker.com | sh
# 서비스 활성화 및 부팅 시 자동 시작
sudo systemctl enable --now docker
# 정상 설치 확인
sudo docker run hello-world
YUM 저장소 방식
sudo tee /etc/yum.repos.d/docker-ce.repo <<'EOF'
[docker-ce-stable]
name=Docker CE Stable
baseurl=https://download.docker.com/linux/centos/7/x86_64/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg
EOF
sudo yum install -y docker-ce
sudo systemctl enable --now docker
국내 이미지 가속 설정
Docker Hub 직접 접속 시 속도가 느릴 수 있어, 국내 미러 서버를 지정한다.
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"registry-mirrors": ["https://mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
컨테이너 운영 명령어
기본 실행 패턴
컨테이너 실행의 기본 문법은 docker [명령] [옵션] [대상] 형태이다.
# 백그라운드 실행 및 포트 매핑
docker run -d -p 호스트포트:컨테이너포트 --name 컨테이너명 이미지명
# 대화형 터미널 접속
docker run -it ubuntu:22.04 /bin/bash
# 환경변수와 볼륨 함께 지정
docker run -d \
-e DB_PASSWORD=secret \
-v /host/data:/app/data \
-p 8080:80 \
--network backend \
myapp:latest
주요 옵션 정리
| 옵션 | 의미 |
|---|---|
-d | 백그라운드(detached) 모드 |
-i | 표준입력(STDIN) 유지 |
-t | 가짜 터미널(TTY) 할당 |
-p 호스트:컨테이너 | 포트 포워딩 |
-P | 컨테이너의 노출 포트를 호스트 임의 포트에 매핑 |
--name | 컨테이너에 고유 이름 부여 |
--link | 다른 컨테이와 네트워크 연결(레거시) |
-v 호스트:컨테이너 | 볼륨 마운트 |
--network | 사용할 네트워크 라이버 지정 |
-m | 메모리 사용 한도 |
--cpus | CPU 사용 한도 |
라이프사이클 관리
# 상태 전환
docker start|stop|restart|pause|unpause 컨테이너명
# 강제 종료 (SIGKILL)
docker kill --signal=SIGTERM 웹서버
# 완전 삭제 (실행 중인 것도 강제)
docker rm -f $(docker ps -aq)
# 실행 중인 컨테이너 내부 명령 실행
docker exec -it 데이터베이스 /bin/sh -c "mysql -u root -p"
# 로그 실시간 추적
docker logs -f --tail=100 애플리케이션
정보 조회
# 실행 중인 프로세스 목록
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 상세 메타데이터 (JSON)
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 웹서버
# 파일 시스템 변경 사항 확인
docker diff 컨테이너ID
# 리소스 사용량 통계
docker stats --no-stream
이미지 관리 심화
이미지 계층 구조 이해
Docker 이미지는 UnionFS(Union File System)를 활용한 계층(layer) 구조로 구성된다. 각 명령어(INSTRUCTION)마다 새로운 레이어가 생성되며, 이 레이어들은 읽기 전용(read-only)으로 쌓인다.
- bootfs: 부트로더와 커널 포함. 컨테이너 시작 후 메모리에서 해제됨
- rootfs: 실제 파일 시스템. 기본적으로 읽기 전용으로 마운트
- Container Layer: 컨테이너 실행 시 쓰기 가능한 레이어가 위에 추가됨(Copy-on-Write)
컨테이너 → 이미지 변환
# 실행 중인 컨테이너의 현재 상태를 새 이미지로 저장
docker commit -a "작성자" -m "설명 메시지" 원본컨테이너 새이미지명:태그
# 실제 사용 예시
docker commit --pause=false nginx컨테이너 내nginx:v1.1
이미지 이전:存(save/load)
# 이미지를 tar 아카이브로 추출 (레이어 포함)
docker save -o /backup/webapp-$(date +%Y%m%d).tar myapp:1.0
# 다른 환경에서 복원
docker load -i webapp-20240115.tar
# 태그 변경 후 배포
docker tag 소스이미지:태그 목적지저장소/프로젝트/이미지:태그
Dockerfile 작성 전략
핵심 지침과 예시
Dockerfile은 이미지 빌드 자동화를 위한 선언형 스크립트이다. 각 지시어(INSTRUCTION)는 대문자로 작성하며, 캐시 효율을 고려한 순서 배치가 중요하다.
# ---------------------------
# Java Spring Boot 애플리케이션 빌드 예시
# ---------------------------
# 1단계: 빌드 환경
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# 2단계: 실행 환경 (멀티스테이지)
FROM eclipse-temurin:17-jre-alpine
LABEL maintainer="devops@example.com"
# 비루트 사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /application
COPY --from=builder --chown=appuser:appgroup /workspace/target/*.jar app.jar
# JVM 튜닝 옵션
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENV SERVER_PORT=8080
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
지시어 상세 비교
| 지시어 | 용도 | 특징 |
|---|---|---|
FROM | 기반 이미지 지정 | 반드시 첫 줄, 멀티스테이지 시 여러 번 사용 |
RUN | 빌드 시 명령 실행 | 레이어 생성, &&로 연결해 캐시 최적화 |
CMD | 컨테이너 기본 실행 | docker run 인자로 오버라이드 가능 |
ENTRYPOINT | 고정 실행 명령 | exec 형태 권장, CMD와 조합 사용 |
COPY | 호스트 → 이미지 파일 복사 | 명시적, 권한/소유자 지정 가능 |
ADD | 파일 복사 + 자동 추출 | tar 자동 해제, 원격 URL 지원 |
ENV | 환경변수 설정 | 빌드 및 실행 시 모두 사용 |
ARG | 빌드 인자 | 빌드 시 --build-arg로 전달 |
VOLUME | 영속 데이터 경로 | 익명 볼륨 생성, 호스트 경로 미지정 |
WORKDIR | 작업 디렉터리 | 없으면 생성, 여러 번 사용 가능 |
EXPOSE | 노출 포트 문서화 | 실제 포트 개방은 -p 필요 |
ONBUILD | 자식 이미지 빌드 시 실행 | 베이스 이미지용, 상위 계층에서 정의 |
ENTRYPOINT와 CMD의 조합 패턴
# 패턴 1: 고정 실행 + 기본 인자
ENTRYPOINT ["java", "-jar", "/app.jar"]
CMD ["--spring.profiles.active=prod"]
# 패턴 2: 쉘 스크립트 래퍼
ENTRYPOINT ["/entrypoint.sh"]
# entrypoint.sh 내에서 exec "$@" 로 CMD 전달
# 패턴 3: 환경변수 기반 동적 실행
ENTRYPOINT ["sh", "-c", "java $JVM_OPTS -jar app.jar $0 $@"]
docker-compose 다중 컨테이너 관리
설치 및 기본 사용
# Docker Compose 플러그인 설치 (현재 권장 방식)
docker compose version
# 또는 독립 바이너리
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
실전 구성 예시
version: "3.9"
services:
postgres:
image: postgres:15-alpine
container_name: order-db
restart: unless-stopped
environment:
POSTGRES_DB: order_system
POSTGRES_USER: app_admin
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD required}
volumes:
- pg_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_admin -d order_system"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
redis:
image: redis:7-alpine
container_name: cache-server
volumes:
- redis_data:/data
networks:
- backend
api-gateway:
build:
context: ./gateway
dockerfile: Dockerfile
args:
BUILD_ENV: production
container_name: gateway-svc
ports:
- "80:8080"
- "443:8443"
environment:
- REDIS_HOST=cache-server
- DB_HOST=order-db
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- backend
- frontend
prometheus:
image: prom/prometheus:v2.47
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "9090:9090"
networks:
- monitoring
volumes:
pg_data:
redis_data:
networks:
backend:
driver: bridge
frontend:
driver: bridge
monitoring:
driver: bridge
주요 명령어
# 전체 스택 기동 (백그라운드)
docker compose up -d --build
# 특정 서비스만 재빌드
docker compose up -d --no-deps --build api-gateway
# 설정 검증
docker compose config
# 로그 집중 확인
docker compose logs -f --tail=50 api-gateway
# 리소스 정리
docker compose down -v # 볼륨까지 제거
프라이빗 레지스트리 구축
Registry 서버 배포
# 공식 Registry 이미지 실행
docker run -d \
--name private-registry \
-p 5000:5000 \
-v /opt/registry-data:/var/lib/registry \
-e REGISTRY_STORAGE_DELETE_ENABLED=true \
--restart always \
registry:2
# 클라이언트 신뢰 설정 (HTTP 허용)
sudo tee /etc/docker/daemon.json <<'EOF'
{
"insecure-registries": ["registry.internal.example.com:5000"]
}
EOF
이미지 푸시/풀 흐름
# 1. 로컬 이미지에 레지스트리 경로 태그 부여
docker tag myapp:2.1.0 registry.internal.example.com:5000/team-alpha/myapp:2.1.0
# 2. 인증
docker login registry.internal.example.com:5000 -u deployer -p $TOKEN
# 3. 업로드
docker push registry.internal.example.com:5000/team-alpha/myapp:2.1.0
# 4. 다른 환경에서 다운로드
docker pull registry.internal.example.com:5000/team-alpha/myapp:2.1.0
# 5. API로 저장소 목록 확인
curl -s http://registry.internal.example.com:5000/v2/_catalog | jq .
이미지 빌드 최적화 전략
레이어 캐시 활용
# ❌ 비효율적: 소스 변경 시 dependency도 재설치
COPY . /app
RUN npm install
# ✅ 효율적: package 파일 변경 시에만 npm install 실행
COPY package*.json /app/
RUN npm ci --only=production
COPY src /app/src
RUN npm run build
멀티스테이지 빌드로 이미지 경량화
| 스테이지 | 목적 | 결과 크기 |
|---|---|---|
| 단일 이미지 (빌드+실행) | 개발 편의성 | 1.2 GB |
| 멀티스테이지 분리 | 빌드 도구 제외 | 180 MB |
| Distroless/Alpine 기반 | 최소 런타임만 포함 | 45 MB |
보안 고려사항
- 루트 사용자 대신
USER지시어로 권한 하향 - 민감 정보는 빌드 인수(
ARG)가 아닌 런타인 환경변수(ENV) 또는 외부 시크릿 관리 도구 사용 .dockerignore로 불필요한 파일 제외- 취약점 스캔:
docker scan또는 Trivy, Snyk 연동