이 글에서 얻는 것
- Docker가 무엇이고, 왜 필요한지 이해합니다.
- Dockerfile로 이미지를 빌드하고, 컨테이너를 실행할 수 있습니다.
- 볼륨과 네트워크로 데이터 영속성과 컨테이너 간 통신을 관리합니다.
- Docker Compose로 멀티 컨테이너 애플리케이션을 구성합니다.
0) Docker는 “애플리케이션을 격리된 환경에서 실행"한다
Docker란?
Docker = 컨테이너 기반 가상화 플랫폼
목적:
- 애플리케이션을 독립된 환경에서 실행
- "내 컴퓨터에서는 되는데..."를 해결
- 빠른 배포와 확장
컨테이너 vs 가상 머신 (VM)
┌─────────────────────┐ ┌─────────────────────┐
│ Virtual Machine │ │ Container │
├─────────────────────┤ ├─────────────────────┤
│ App A │ App B │ │ App A │ App B │
│ ───── │ ───── │ │ ───── │ ───── │
│ Bins │ Bins │ │ Bins │ Bins │
│ Libs │ Libs │ │ Libs │ Libs │
├─────────┼───────────┤ └─────────┴───────────┘
│ Guest OS│ Guest OS │ Docker Engine
├─────────┴───────────┤ ─────────────────────
│ Hypervisor │ Host OS (Linux/Mac/Win)
├─────────────────────┤ ─────────────────────
│ Host OS │ Infrastructure
├─────────────────────┤
│ Infrastructure │
└─────────────────────┘
VM:
- 각 VM이 전체 OS 포함
- 무겁고 느림 (GB 단위)
- 부팅 시간 오래 걸림
Container:
- OS 커널 공유
- 가볍고 빠름 (MB 단위)
- 초 단위로 시작
1) Docker 핵심 개념
1-1) 이미지 (Image)
이미지 = 애플리케이션 실행에 필요한 모든 것
포함 내용:
- 애플리케이션 코드
- 런타임 (Java, Python 등)
- 라이브러리/의존성
- 환경 변수
- 설정 파일
특징:
- 읽기 전용 (Immutable)
- 레이어 구조 (효율적 저장)
- Docker Hub에서 공유
1-2) 컨테이너 (Container)
컨테이너 = 이미지의 실행 인스턴스
특징:
- 이미지로부터 생성
- 격리된 프로세스
- 읽기/쓰기 가능
- 여러 컨테이너가 같은 이미지 사용 가능
비유:
이미지 = 클래스
컨테이너 = 인스턴스
class Image { }
Container c1 = new Image(); // 컨테이너 1
Container c2 = new Image(); // 컨테이너 2
2) Docker 기본 명령어
2-1) 이미지 관리
# 이미지 검색 (Docker Hub)
docker search nginx
# 이미지 다운로드
docker pull nginx:latest
# 이미지 목록
docker images
# 결과:
# REPOSITORY TAG IMAGE ID SIZE
# nginx latest 605c77e624dd 141MB
# 이미지 삭제
docker rmi nginx:latest
# 사용하지 않는 이미지 정리
docker image prune
2-2) 컨테이너 실행
# 컨테이너 실행 (기본)
docker run nginx
# 백그라운드 실행 (-d: detached)
docker run -d nginx
# 포트 매핑 (-p: publish)
docker run -d -p 8080:80 nginx
# 호스트 8080 → 컨테이너 80
# 이름 지정 (--name)
docker run -d -p 8080:80 --name my-nginx nginx
# 환경 변수 (-e)
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql
# 대화형 모드 (-it: interactive + tty)
docker run -it ubuntu bash
# 컨테이너 안에서 bash 실행
2-3) 컨테이너 관리
# 실행 중인 컨테이너 목록
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
2-4) 컨테이너 접근
# 로그 확인
docker logs my-nginx
# 실시간 로그 (tail -f)
docker logs -f my-nginx
# 컨테이너 내부 접속
docker exec -it my-nginx bash
# 단일 명령 실행
docker exec my-nginx ls /usr/share/nginx/html
# 컨테이너 정보 확인
docker inspect my-nginx
# 리소스 사용량
docker stats my-nginx
3) Dockerfile: 이미지 만들기
3-1) Dockerfile 기본
# 베이스 이미지
FROM openjdk:17-jdk-slim
# 작업 디렉토리 설정
WORKDIR /app
# 파일 복사
COPY build/libs/myapp.jar app.jar
# 환경 변수
ENV SPRING_PROFILES_ACTIVE=prod
# 포트 노출 (문서화 목적)
EXPOSE 8080
# 컨테이너 시작 시 실행할 명령
ENTRYPOINT ["java", "-jar", "app.jar"]
빌드:
# 이미지 빌드
docker build -t myapp:1.0 .
# 실행
docker run -d -p 8080:8080 myapp:1.0
3-2) 멀티 스테이지 빌드 (권장)
# Stage 1: Build
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY . .
# Gradle 빌드
RUN gradle clean build -x test
# Stage 2: Runtime
FROM openjdk:17-jdk-slim
WORKDIR /app
# 빌드 스테이지에서 JAR 파일만 복사
COPY --from=builder /app/build/libs/myapp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
장점:
- 최종 이미지에 빌드 도구 불포함 → 이미지 크기 감소
- 보안 향상 (불필요한 도구 제거)
3-3) .dockerignore
# .dockerignore (이미지에 포함하지 않을 파일)
.git
.gradle
build
node_modules
*.md
.env
4) 볼륨: 데이터 영속성
4-1) 볼륨이 필요한 이유
문제:
- 컨테이너 삭제 시 데이터도 삭제됨
해결:
- 볼륨으로 데이터를 호스트에 저장
4-2) 볼륨 사용
# 볼륨 생성
docker volume create mysql-data
# 볼륨 마운트
docker run -d \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql
# 호스트 디렉토리 마운트 (바인드 마운트)
docker run -d \
-v /home/user/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql
# 볼륨 목록
docker volume ls
# 볼륨 삭제
docker volume rm mysql-data
4-3) Spring Boot 예시
# 로그 디렉토리 마운트
docker run -d \
-p 8080:8080 \
-v /var/log/myapp:/app/logs \
myapp:1.0
# 설정 파일 마운트
docker run -d \
-p 8080:8080 \
-v /etc/myapp/application.yml:/app/config/application.yml \
myapp:1.0
5) 네트워크: 컨테이너 간 통신
5-1) 네트워크 생성
# 네트워크 생성
docker network create myapp-network
# 네트워크 목록
docker network ls
# 네트워크 상세 정보
docker network inspect myapp-network
5-2) 컨테이너 연결
# MySQL 컨테이너 (네트워크에 연결)
docker run -d \
--name mysql \
--network myapp-network \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=mydb \
mysql
# Spring Boot 컨테이너 (같은 네트워크)
docker run -d \
--name myapp \
--network myapp-network \
-p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb \
-e SPRING_DATASOURCE_USERNAME=root \
-e SPRING_DATASOURCE_PASSWORD=secret \
myapp:1.0
# 같은 네트워크에서는 컨테이너 이름으로 통신 가능!
# "mysql"이 호스트명으로 동작
6) Docker Compose: 멀티 컨테이너 관리
6-1) docker-compose.yml
version: '3.8'
services:
# MySQL
mysql:
image: mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: mydb
volumes:
- mysql-data:/var/lib/mysql
networks:
- myapp-network
# Redis
redis:
image: redis:7
container_name: redis
networks:
- myapp-network
# Spring Boot App
app:
build:
context: .
dockerfile: Dockerfile
container_name: myapp
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mydb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: secret
SPRING_REDIS_HOST: redis
depends_on:
- mysql
- redis
networks:
- myapp-network
volumes:
mysql-data:
networks:
myapp-network:
6-2) Compose 명령어
# 모든 서비스 시작 (백그라운드)
docker-compose up -d
# 로그 확인
docker-compose logs -f
# 특정 서비스 로그
docker-compose logs -f app
# 서비스 중지
docker-compose stop
# 서비스 삭제 (볼륨 유지)
docker-compose down
# 서비스 + 볼륨 삭제
docker-compose down -v
# 특정 서비스만 재시작
docker-compose restart app
# 스케일링 (여러 인스턴스)
docker-compose up -d --scale app=3
7) 실전 예제
7-1) Spring Boot Dockerfile
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle clean build -x test
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
# 비루트 사용자 생성 (보안)
RUN useradd -m appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
7-2) 전체 스택 docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mydb
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: password
SPRING_REDIS_HOST: redis
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
postgres-data:
8) 베스트 프랙티스
✅ 1. 경량 베이스 이미지 사용
# ❌ 나쁜 예 (무거움)
FROM openjdk:17
# ✅ 좋은 예 (가벼움)
FROM openjdk:17-jdk-slim
# 또는
FROM eclipse-temurin:17-jre-alpine
✅ 2. 멀티 스테이지 빌드
# 빌드 도구는 builder 스테이지에만
FROM gradle:8.5 AS builder
# ...
# 최종 이미지는 런타임만
FROM openjdk:17-jdk-slim
COPY --from=builder /app/build/libs/*.jar app.jar
✅ 3. .dockerignore 사용
# 불필요한 파일 제외
.git
.gradle
build
node_modules
✅ 4. 레이어 캐싱 활용
# ❌ 나쁜 예 (코드 변경마다 전체 재빌드)
COPY . .
RUN gradle build
# ✅ 좋은 예 (의존성은 캐싱)
COPY build.gradle settings.gradle ./
RUN gradle dependencies
COPY src ./src
RUN gradle build
✅ 5. 환경 변수로 설정 분리
# 하드코딩 대신 환경 변수
docker run -d \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST=mysql \
myapp:1.0
9) 자주 하는 실수
❌ 실수 1: 루트 사용자로 실행
# ✅ 비루트 사용자 생성
RUN useradd -m appuser
USER appuser
❌ 실수 2: 볼륨 없이 데이터베이스 실행
# ❌ 컨테이너 삭제 시 데이터 손실
docker run -d mysql
# ✅ 볼륨 사용
docker run -d -v mysql-data:/var/lib/mysql mysql
❌ 실수 3: 모든 포트 노출
# ❌ 불필요한 포트까지 노출
EXPOSE 8080 3306 6379
# ✅ 필요한 포트만
EXPOSE 8080
연습 (추천)
Dockerfile 작성
- Spring Boot 프로젝트를 Docker 이미지로 빌드
- 멀티 스테이지 빌드 적용
Docker Compose
- MySQL + Redis + Spring Boot 구성
- 각 서비스 연결 확인
볼륨 실습
- 컨테이너 재생성 후 데이터 유지 확인
요약: 스스로 점검할 것
- Docker와 VM의 차이를 설명할 수 있다
- Dockerfile로 이미지를 빌드할 수 있다
- 컨테이너 실행, 중지, 삭제를 할 수 있다
- 볼륨으로 데이터를 영속화할 수 있다
- Docker Compose로 멀티 컨테이너를 관리할 수 있다
다음 단계
- Kubernetes 기초:
/learning/deep-dive/deep-dive-kubernetes-basics/ - CI/CD 파이프라인:
/learning/deep-dive/deep-dive-cicd-pipeline/ - 컨테이너 모니터링:
/learning/deep-dive/deep-dive-container-monitoring/
💬 댓글