
📌 들어가며
오늘의 학습 목표: 컨테이너 기술의 핵심, Docker와 Docker Compose를 완벽하게 이해하고 Spring Boot 프로젝트를 컨테이너화하기
안녕하세요! 오늘은 백엔드 개발자라면 반드시 알아야 하는 Docker를 공부했습니다.
사실 Docker를 배우게 된 계기가 있어요. 지난번에 AWS EC2에 Spring Boot 프로젝트를 배포했는데, 로컬에서는 분명히 잘 돌아갔거든요. 그런데 EC2에 올리니까...
"어? 왜 안 돼? 분명 내 컴퓨터에선 됐는데... 😭"
알고 보니 Java 버전이 달랐어요. 로컬은 Java 17인데 EC2는 Java 11이 깔려있었던 거죠. 이것 말고도 환경 변수, 라이브러리 버전 등등... 환경 차이 때문에 생기는 문제가 정말 많더라고요.
그래서 "어떻게 하면 어디서든 동일한 환경으로 실행할 수 있을까?" 찾아보다가 Docker를 발견했습니다!
Docker를 배우고 나니까 이제 "내 컴퓨터에선 되는데..."라는 말은 할 필요가 없어졌어요. 컨테이너 안에 모든 환경을 담아서 어디서든 똑같이 실행할 수 있거든요! 🐳
🎯 Today I Learned
✅ Docker란 무엇이고 왜 필요한가?
✅ Docker 설치 방법 (Windows, Mac, Linux)
✅ Docker vs 가상머신(VM) 차이점
✅ Docker의 핵심 개념 (이미지, 컨테이너, 레이어)
✅ Docker 아키텍처 이해
✅ Dockerfile 작성법
✅ Docker 핵심 명령어 실습
✅ Docker Compose로 멀티 컨테이너 관리
✅ Spring Boot + MySQL + Redis 컨테이너화
✅ Docker 볼륨과 데이터 영속성
✅ Docker 네트워크 심화
✅ Docker Hub에 이미지 푸시
✅ 실전 트러블슈팅
🤔 Docker, 왜 필요할까?
"내 컴퓨터에선 되는데..." 문제

개발자라면 한 번쯤 겪어봤을 악몽 같은 상황이에요:
개발자 A: "코드 다 짰어요! 로컬에서 완벽하게 돌아갑니다!"
배포 후...
개발자 A: "어... 왜 서버에서는 안 되죠? 😱"
원인들:
❌ Java 버전 차이 (로컬: 17, 서버: 11)
❌ OS 차이 (로컬: Windows, 서버: Linux)
❌ 라이브러리 버전 차이
❌ 환경 변수 누락
❌ 설정 파일 경로 차이
❌ 시스템 의존성 차이
❌ 네트워크 설정 차이
실제로 제가 겪었던 사례를 공유할게요:
🔥 실제 겪은 문제들:
1. 로컬(Windows)에서는 파일 경로가 C:\uploads\image.jpg
서버(Linux)에서는 /home/ubuntu/uploads/image.jpg
→ 파일 업로드 기능 전체가 터짐!
2. 로컬에서는 MySQL 8.0 사용
서버에는 MySQL 5.7이 설치되어 있었음
→ JSON 타입 관련 쿼리가 전부 에러!
3. 로컬에서는 타임존이 KST
서버에서는 UTC
→ 시간 관련 로직이 전부 꼬임!
Docker가 해결해주는 것

Docker 사용 전:
📱 내 맥북 (Java 17, macOS, MySQL 8.0, KST)
↓ 배포
🖥️ 서버 (Java 11, Ubuntu, MySQL 5.7, UTC)
→ 🔥 환경 차이로 에러 폭발!
Docker 사용 후:
📦 Docker 컨테이너 (Java 17, MySQL 8.0, 모든 의존성 + 설정 포함)
↓ 그대로 이동
🖥️ 어떤 서버든 (Docker만 설치되어 있으면)
→ ✅ 100% 동일하게 실행!
핵심 개념:
"Build once, Run anywhere" 한 번 만들면 어디서든 실행된다!
Docker를 써야 하는 이유 5가지
1️⃣ 환경 일관성
- 개발, 테스트, 운영 환경이 100% 동일
- "내 컴퓨터에선 되는데..." 문제 완전 해결
2️⃣ 빠른 배포
- 이미지만 pull 받으면 즉시 실행
- 서버 세팅 시간 대폭 단축
3️⃣ 격리성
- 각 컨테이너는 독립적으로 실행
- 한 서비스 문제가 다른 서비스에 영향 없음
4️⃣ 확장성
- 컨테이너 복제가 쉬움
- 트래픽 증가 시 빠르게 대응 가능
5️⃣ 버전 관리
- 이미지 태그로 버전 관리
- 롤백이 간단함
🐳 Docker란 무엇인가?
Docker의 정의

Docker는 애플리케이션을 컨테이너라는 격리된 환경에서 실행할 수 있게 해주는 오픈소스 컨테이너화 플랫폼이에요.
2013년 Docker, Inc.에서 처음 발표했고, 지금은 사실상 컨테이너 기술의 표준이 되었습니다.
쉽게 비유하면:
🚢 화물선 컨테이너
- 어떤 물건이든 컨테이너에 담으면
- 어떤 배로든, 어떤 항구로든 운송 가능
- 내용물은 안전하게 격리됨
- 컨테이너 규격이 표준화되어 있음
🐳 Docker 컨테이너
- 어떤 애플리케이션이든 컨테이너에 담으면
- 어떤 서버로든, 어떤 클라우드로든 배포 가능
- 환경은 완벽하게 격리됨
- 컨테이너 형식이 표준화되어 있음 (OCI 표준)
Docker 아키텍처
Docker 아키텍처:
┌─────────────────────────────────────────────────────────┐
│ Docker Client │
│ (docker build, docker pull, docker run) │
└───────────────────────────┬─────────────────────────────┘
│ REST API
▼
┌─────────────────────────────────────────────────────────┐
│ Docker Host │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Docker Daemon │ │ Containers │ │
│ │ (dockerd) │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ │ │ App │ │ DB │ │Redis│ │ │
│ │ │ │ └─────┘ └─────┘ └─────┘ │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Images │ │
│ │ [nginx] [mysql] [redis] [openjdk] [my-app] │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Docker Registry │
│ (Docker Hub, AWS ECR, etc.) │
└─────────────────────────────────────────────────────────┘
각 구성 요소 설명:
구성 요소 역할
| Docker Client | 사용자가 Docker와 상호작용하는 CLI |
| Docker Daemon | 컨테이너 생성, 실행, 관리를 담당하는 백그라운드 서비스 |
| Docker Image | 컨테이너 실행에 필요한 파일과 설정을 담은 템플릿 |
| Docker Container | 이미지를 실행한 인스턴스 |
| Docker Registry | 이미지를 저장하고 배포하는 저장소 |
Docker vs 가상머신(VM)

처음에는 "그냥 VM 쓰면 되는 거 아냐?"라고 생각했는데, 완전히 다르더라고요!
가상머신 (VM):
┌─────────────────────────────────────────┐
│ Application A │
│ Binaries/Libraries │
│ Guest OS (Ubuntu 4GB) │ ← OS 전체를 가상화
├─────────────────────────────────────────┤
│ Application B │
│ Binaries/Libraries │
│ Guest OS (CentOS 3GB) │ ← 또 다른 OS 전체
├─────────────────────────────────────────┤
│ Hypervisor │ ← 가상화 계층
├─────────────────────────────────────────┤
│ Host OS │
│ Hardware │
└─────────────────────────────────────────┘
총 용량: 앱 + 라이브러리 + OS(7GB+) = 엄청 무거움!
Docker 컨테이너:
┌─────────────────────────────────────────┐
│ App A │ App B │ App C │
│ Bins │ Bins │ Bins │
│ (100MB) │ (50MB) │ (80MB) │
├─────────────────────────────────────────┤
│ Docker Engine │ ← 커널을 공유
├─────────────────────────────────────────┤
│ Host OS │
│ Hardware │
└─────────────────────────────────────────┘
총 용량: 앱 + 라이브러리만 = 가볍다!
상세 비교표:
비교 항목 가상머신 (VM) Docker 컨테이너
| 크기 | GB 단위 (OS 포함) | MB 단위 (앱만) |
| 시작 시간 | 분 단위 (OS 부팅) | 초 단위 (프로세스 시작) |
| 메모리 사용 | 많음 (OS마다 할당) | 적음 (공유) |
| 격리 수준 | 완전 격리 (하드웨어 레벨) | 프로세스 격리 |
| 성능 | 오버헤드 10-20% | 거의 네이티브 (1-2%) |
| 이식성 | 낮음 (이미지가 큼) | 매우 높음 |
| 밀도 | 서버당 수십 개 | 서버당 수백~수천 개 |
언제 뭘 써야 할까?
VM을 써야 할 때:
✅ 완전히 다른 OS가 필요할 때 (Windows에서 Linux 전체 필요)
✅ 강력한 격리가 필요할 때 (보안상 커널도 분리)
✅ 레거시 시스템 운영
Docker를 써야 할 때:
✅ 마이크로서비스 아키텍처
✅ CI/CD 파이프라인
✅ 개발 환경 표준화
✅ 빠른 확장이 필요할 때
✅ 리소스 효율성이 중요할 때
🔧 Docker 설치하기
Windows에 Docker 설치

사전 요구사항:
- Windows 10 64-bit: Pro, Enterprise, Education (Build 19041 이상)
- Windows 11 64-bit
- WSL 2 활성화
설치 순서:
1️⃣ WSL 2 설치 (필수!)
PowerShell을 관리자 권한으로 실행:
# WSL 활성화
wsl --install
# 재부팅 후 WSL 버전 확인
wsl --version
# WSL 2를 기본값으로 설정
wsl --set-default-version 2

2️⃣ Docker Desktop 다운로드
공식 사이트: https://www.docker.com/products/docker-desktop/
"Download for Windows" 클릭
→ Docker Desktop Installer.exe 다운로드
3️⃣ Docker Desktop 설치
다운로드한 설치 파일 실행
→ "Use WSL 2 instead of Hyper-V" 체크 ✅
→ "Add shortcut to desktop" 체크 ✅
→ Install 클릭
→ 설치 완료 후 재부팅
4️⃣ 설치 확인
# PowerShell 또는 CMD에서
docker --version
# 출력: Docker version 24.0.7, build afdd53b
docker run hello-world
# Hello from Docker! 메시지가 나오면 성공! 🎉

Windows에서 자주 발생하는 문제:
❌ 문제: "WSL 2 installation is incomplete"
해결:
1. https://aka.ms/wsl2kernel 에서 WSL2 Linux 커널 업데이트 패키지 다운로드
2. 설치 후 Docker Desktop 재시작
❌ 문제: "Hardware assisted virtualization and data execution protection must be enabled"
해결:
1. BIOS 진입 (재부팅 시 F2 또는 DEL)
2. Intel VT-x 또는 AMD-V 활성화
3. 저장 후 재부팅
Mac에 Docker 설치
설치 순서:
1️⃣ Docker Desktop 다운로드
공식 사이트: https://www.docker.com/products/docker-desktop/
Mac with Apple chip → Apple Silicon 버전
Mac with Intel chip → Intel 버전
해당하는 버전 다운로드
2️⃣ 설치
Docker.dmg 파일 열기
→ Docker 아이콘을 Applications 폴더로 드래그
→ Applications에서 Docker 실행
→ 권한 허용 팝업에서 "OK" 클릭
3️⃣ 설치 확인
# 터미널에서
docker --version
docker run hello-world
Mac에서 팁:
# Homebrew로 설치하는 방법 (대안)
brew install --cask docker
# Docker Desktop 없이 CLI만 설치 (고급)
brew install docker
brew install docker-compose
Linux (Ubuntu)에 Docker 설치

Ubuntu 22.04 / 24.04 기준:
# 1️⃣ 기존 Docker 제거 (혹시 있다면)
sudo apt-get remove docker docker-engine docker.io containerd runc
# 2️⃣ 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release
# 3️⃣ Docker 공식 GPG 키 추가
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 4️⃣ Docker 저장소 추가
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 5️⃣ Docker 설치
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 6️⃣ 설치 확인
sudo docker --version
sudo docker run hello-world
sudo 없이 Docker 사용하기 (권장):
# docker 그룹에 현재 사용자 추가
sudo usermod -aG docker $USER
# 변경사항 적용 (재로그인 또는)
newgrp docker
# 이제 sudo 없이 실행 가능
docker ps
Docker Compose 설치 확인:
# Docker Compose v2는 Docker와 함께 설치됨
docker compose version
# 출력: Docker Compose version v2.21.0
설치 후 필수 설정
1. 리소스 할당 (Docker Desktop):

Settings → Resources → Advanced
Memory: 4GB 이상 권장 (Spring Boot + MySQL + Redis 동시 실행 시)
CPUs: 2개 이상 권장
Disk image size: 60GB 이상 권장
적용 후 "Apply & Restart"
2. WSL 통합 (Windows):

Settings → Resources → WSL Integration
"Enable integration with my default WSL distro" 체크 ✅
사용할 Linux 배포판 활성화
3. 실험적 기능 (선택사항):
Settings → Features in development
"Use containerd for pulling and storing images" - 최신 기능
"Enable Docker Debug" - 디버깅 시 유용
🏗️ Docker의 핵심 개념
1️⃣ 이미지 (Image)

이미지는 컨테이너를 만들기 위한 읽기 전용 템플릿이에요.
비유:
🥟 붕어빵 틀 = Docker 이미지
🐟 붕어빵 = Docker 컨테이너
하나의 틀(이미지)로 여러 개의 붕어빵(컨테이너)을 만들 수 있어요!
틀은 변하지 않고(읽기 전용), 붕어빵은 먹으면 사라짐(삭제 가능)
이미지 구성 요소:
Docker 이미지 = 애플리케이션 + 실행에 필요한 모든 것
포함되는 것들:
📦 애플리케이션 코드 (JAR, WAR 등)
📦 런타임 (JDK, Node.js, Python 등)
📦 라이브러리 (의존성들)
📦 환경 변수
📦 설정 파일
📦 시스템 도구
이미지 명명 규칙:
[레지스트리/]저장소:태그
예시:
nginx # 공식 이미지 (태그 생략 = latest)
nginx:1.24 # 특정 버전
nginx:alpine # Alpine Linux 기반 경량 버전
mysql:8.0 # MySQL 8.0 버전
username/my-app:1.0 # 사용자 이미지
gcr.io/project/app:v2 # Google Container Registry
123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-app:latest # AWS ECR
# 이미지 다운로드 (Docker Hub에서)
docker pull openjdk:17-jdk-slim
# 이미지 목록 확인
docker images
# 결과:
REPOSITORY TAG IMAGE ID CREATED SIZE
openjdk 17-jdk-slim a3b5c6d7e8f9 2 weeks ago 407MB
mysql 8.0 b1c2d3e4f5g6 3 weeks ago 544MB
redis alpine c2d3e4f5g6h7 1 week ago 28MB
nginx latest d3e4f5g6h7i8 4 days ago 142MB
# 이미지 상세 정보
docker inspect openjdk:17-jdk-slim
# 이미지 히스토리 (레이어 확인)
docker history openjdk:17-jdk-slim
2️⃣ 컨테이너 (Container)

컨테이너는 이미지를 실행한 인스턴스예요.
이미지 → 실행 → 컨테이너
프로그래밍 비유:
클래스(Class) → new → 객체(Object)
이미지(Image) → run → 컨테이너(Container)
컨테이너 라이프사이클:
docker create
┌─────────────────────────────────┐
│ ▼
[이미지] ──────────────────────► [Created]
│
│ docker start
▼
[Running] ◄─────┐
│ │ │ │
docker │ │ │ │ docker
pause │ │ │ │ unpause
▼ │ │ │
[Paused] ─┘ │ │
│ │
docker stop │ │ docker restart
▼ │
[Stopped] ┘
│
│ docker rm
▼
[Deleted]
# 컨테이너 생성 + 실행 (가장 많이 사용)
docker run -d --name my-nginx -p 80:80 nginx
# 옵션 상세 설명:
# -d, --detach : 백그라운드 실행
# --name : 컨테이너 이름 지정
# -p, --publish : 포트 매핑 (호스트:컨테이너)
# -e, --env : 환경 변수 설정
# -v, --volume : 볼륨 마운트
# --network : 네트워크 연결
# --restart : 재시작 정책
# --memory : 메모리 제한
# --cpus : CPU 제한
# 실행 중인 컨테이너 확인
docker ps
# 결과:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" Up 2 minutes 0.0.0.0:80->80/tcp my-nginx
# 모든 컨테이너 확인 (중지된 것 포함)
docker ps -a
# 컨테이너 상세 정보
docker inspect my-nginx
# 컨테이너 리소스 사용량 실시간 모니터링
docker stats
3️⃣ Dockerfile
Dockerfile은 이미지를 만들기 위한 **레시피(설계도)**예요.
기본 Dockerfile 구조:
# 1. 베이스 이미지 선택 (필수)
FROM openjdk:17-jdk-slim
# 2. 메타데이터 추가 (선택)
LABEL maintainer="coding-quokka@example.com"
LABEL version="1.0"
LABEL description="My Spring Boot Application"
# 3. 환경 변수 설정
ENV SPRING_PROFILES_ACTIVE=prod
ENV TZ=Asia/Seoul
# 4. 작업 디렉토리 설정
WORKDIR /app
# 5. 파일 복사
COPY build/libs/*.jar app.jar
# 6. 포트 노출 (문서화 목적)
EXPOSE 8080
# 7. 실행 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile 주요 명령어:
명령어 설명 예시
| FROM | 베이스 이미지 지정 | FROM openjdk:17-jdk-slim |
| LABEL | 메타데이터 추가 | LABEL version="1.0" |
| ENV | 환경 변수 설정 | ENV JAVA_OPTS="-Xmx512m" |
| WORKDIR | 작업 디렉토리 설정 | WORKDIR /app |
| COPY | 파일/디렉토리 복사 | COPY src/ /app/src/ |
| ADD | 파일 복사 (URL, tar 지원) | ADD app.tar.gz /app/ |
| RUN | 빌드 시 명령 실행 | RUN apt-get update |
| CMD | 컨테이너 시작 시 실행 (덮어쓰기 가능) | CMD ["nginx", "-g", "daemon off;"] |
| ENTRYPOINT | 컨테이너 시작 시 실행 (고정) | ENTRYPOINT ["java", "-jar", "app.jar"] |
| EXPOSE | 포트 문서화 | EXPOSE 8080 |
| VOLUME | 볼륨 마운트 포인트 | VOLUME /data |
| USER | 실행 사용자 변경 | USER appuser |
| ARG | 빌드 시 변수 | ARG JAR_FILE=app.jar |
| HEALTHCHECK | 컨테이너 상태 체크 | HEALTHCHECK CMD curl -f http://localhost/ |
CMD vs ENTRYPOINT 차이:
# CMD - 기본 명령어, 실행 시 덮어쓰기 가능
FROM ubuntu
CMD ["echo", "Hello World"]
# 실행
docker run my-image # 출력: Hello World
docker run my-image echo "Hi" # 출력: Hi (CMD 덮어쓰기)
# ENTRYPOINT - 고정 명령어, 인자만 추가 가능
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["Hello World"]
# 실행
docker run my-image # 출력: Hello World
docker run my-image "Hi" # 출력: Hi (CMD 부분만 교체)
docker run my-image ls # 출력: ls (ENTRYPOINT는 유지)
4️⃣ 레이어 시스템

Docker 이미지는 레이어로 구성되어 있어요. 이게 Docker가 빠르고 효율적인 비결!
이미지 레이어 구조:
┌─────────────────────────────────────┐
│ Layer 5: ENTRYPOINT ["java"...] │ ← 실행 명령 (작음)
├─────────────────────────────────────┤
│ Layer 4: COPY app.jar │ ← 내 애플리케이션 (변경 잦음)
├─────────────────────────────────────┤
│ Layer 3: RUN apt-get install -y... │ ← 패키지 설치 (캐시됨)
├─────────────────────────────────────┤
│ Layer 2: WORKDIR /app │ ← 작업 디렉토리 (캐시됨)
├─────────────────────────────────────┤
│ Layer 1: FROM openjdk:17-jdk-slim │ ← 베이스 이미지 (공유/재사용)
└─────────────────────────────────────┘
특징:
✅ 각 레이어는 읽기 전용 (Immutable)
✅ 변경된 레이어만 다시 빌드
✅ 공통 레이어는 캐시해서 재사용
✅ 여러 이미지가 같은 베이스 레이어 공유
레이어 캐싱 활용법:
# ❌ 비효율적 (소스 변경 시 의존성도 다시 다운로드)
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY . .
RUN ./gradlew build
# ✅ 효율적 (의존성 레이어 캐싱)
FROM openjdk:17-jdk-slim
WORKDIR /app
# 의존성 파일 먼저 복사 (변경 적음)
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
RUN ./gradlew dependencies --no-daemon
# 소스 코드 복사 (변경 잦음)
COPY src ./src
RUN ./gradlew build --no-daemon -x test
캐싱 효과:
첫 번째 빌드: 5분
소스만 변경 후 두 번째 빌드: 30초 ✨
→ Layer 1~3 캐시 사용, Layer 4~5만 재빌드!
5️⃣ 볼륨 (Volume)
컨테이너는 기본적으로 **일시적(ephemeral)**이에요. 컨테이너가 삭제되면 데이터도 사라져요!
볼륨은 데이터를 컨테이너 외부에 저장해서 영속성을 보장합니다.
볼륨의 3가지 타입:
1️⃣ Named Volume (명명된 볼륨) - 권장!
- Docker가 관리하는 볼륨
- 이름으로 참조
- 여러 컨테이너에서 공유 가능
2️⃣ Bind Mount (바인드 마운트)
- 호스트의 특정 경로를 마운트
- 개발 시 소스 코드 실시간 반영에 유용
3️⃣ tmpfs Mount
- 메모리에만 저장
- 컨테이너 종료 시 사라짐
- 민감한 임시 데이터용
# 1️⃣ Named Volume
docker volume create my-data
docker run -v my-data:/app/data my-image
# 2️⃣ Bind Mount
docker run -v $(pwd)/data:/app/data my-image
# Windows: docker run -v ${PWD}/data:/app/data my-image
# 3️⃣ tmpfs Mount
docker run --tmpfs /app/temp my-image
# 볼륨 목록 확인
docker volume ls
# 볼륨 상세 정보
docker volume inspect my-data
# 볼륨 삭제
docker volume rm my-data
# 사용하지 않는 볼륨 정리
docker volume prune
MySQL 데이터 영속성 예시:
# ❌ 볼륨 없이 실행 - 컨테이너 삭제 시 데이터 손실!
docker run -d --name mysql-temp mysql:8.0
# ✅ 볼륨과 함께 실행 - 데이터 안전!
docker run -d \
--name mysql-persistent \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root1234 \
mysql:8.0
# 컨테이너 삭제 후 재생성해도 데이터 유지!
docker rm -f mysql-persistent
docker run -d \
--name mysql-new \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root1234 \
mysql:8.0
# → 기존 데이터 그대로 존재! ✨
💻 Docker 핵심 명령어 실습
이미지 관련 명령어


# 이미지 검색 (Docker Hub에서)
docker search mysql
docker search --filter=stars=100 mysql # 별 100개 이상만
# 이미지 다운로드
docker pull mysql:8.0
docker pull mysql:8.0 --platform linux/amd64 # 특정 플랫폼
# 이미지 목록 확인
docker images
docker images -a # 중간 레이어 포함
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 이미지 상세 정보
docker inspect mysql:8.0
docker inspect --format='{{.Config.Env}}' mysql:8.0 # 환경변수만
# 이미지 히스토리 (레이어 확인)
docker history mysql:8.0
docker history --no-trunc mysql:8.0 # 전체 명령어 표시
# 이미지 삭제
docker rmi mysql:8.0
docker rmi -f mysql:8.0 # 강제 삭제
docker image prune # 사용하지 않는 이미지 정리
docker image prune -a # 모든 미사용 이미지 정리
# Dockerfile로 이미지 빌드
docker build -t my-app:1.0 .
docker build -t my-app:1.0 -f Dockerfile.prod . # 다른 Dockerfile
docker build --no-cache -t my-app:1.0 . # 캐시 무시
docker build --build-arg JAR_FILE=app.jar -t my-app:1.0 . # 빌드 인자
# 이미지 태그 추가
docker tag my-app:1.0 my-app:latest
docker tag my-app:1.0 username/my-app:1.0
# 이미지 저장/로드 (파일로)
docker save -o my-app.tar my-app:1.0
docker load -i my-app.tar
컨테이너 관련 명령어
# 컨테이너 실행 (다양한 옵션)
docker run -d --name my-app \
-p 8080:8080 \ # 포트 매핑
-e SPRING_PROFILES_ACTIVE=prod \ # 환경 변수
-v app-data:/app/data \ # 볼륨 마운트
--network my-network \ # 네트워크 연결
--restart unless-stopped \ # 재시작 정책
--memory 512m \ # 메모리 제한
--cpus 1.0 \ # CPU 제한
my-app:1.0
# 재시작 정책 옵션:
# no : 재시작 안 함 (기본값)
# on-failure : 에러로 종료 시 재시작
# always : 항상 재시작
# unless-stopped: 수동 중지 전까지 항상 재시작
# 실행 중인 컨테이너 확인
docker ps
docker ps -a # 모든 컨테이너
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker ps -q # ID만 출력
# 컨테이너 제어
docker stop my-app # 정상 종료 (SIGTERM)
docker stop -t 30 my-app # 30초 대기 후 강제 종료
docker kill my-app # 강제 종료 (SIGKILL)
docker start my-app # 시작
docker restart my-app # 재시작
docker pause my-app # 일시정지
docker unpause my-app # 일시정지 해제
# 컨테이너 삭제
docker rm my-app
docker rm -f my-app # 실행 중이어도 강제 삭제
docker rm $(docker ps -aq) # 모든 컨테이너 삭제
docker container prune # 중지된 컨테이너 정리
# 컨테이너 로그
docker logs my-app
docker logs -f my-app # 실시간 로그 (follow)
docker logs --tail 100 my-app # 마지막 100줄
docker logs --since 1h my-app # 최근 1시간
docker logs -t my-app # 타임스탬프 포함
# 컨테이너 내부 접속
docker exec -it my-app /bin/bash
docker exec -it my-app sh # bash 없는 경우
docker exec my-app ls /app # 단일 명령 실행
docker exec -u root my-app whoami # 특정 사용자로 실행
# 컨테이너 파일 복사
docker cp my-app:/app/logs ./logs # 컨테이너 → 호스트
docker cp ./config.yml my-app:/app/config/ # 호스트 → 컨테이너
# 컨테이너 리소스 모니터링
docker stats # 모든 컨테이너
docker stats my-app # 특정 컨테이너
docker top my-app # 컨테이너 내 프로세스 목록
# 컨테이너 변경사항 확인
docker diff my-app # 파일시스템 변경사항
# 컨테이너를 이미지로 저장
docker commit my-app my-app-snapshot:v1
실습: Spring Boot 컨테이너 띄우기

1단계: Spring Boot 프로젝트 준비
# 프로젝트 빌드
./gradlew clean build -x test
# JAR 파일 확인
ls build/libs/
# my-app-0.0.1-SNAPSHOT.jar
2단계: Dockerfile 작성
# Dockerfile
FROM openjdk:17-jdk-slim
# 타임존 설정
ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
# JAR 파일 복사
COPY build/libs/*.jar app.jar
# 포트 노출
EXPOSE 8080
# 실행
ENTRYPOINT ["java", "-jar", "app.jar"]
3단계: 이미지 빌드 및 실행
# 이미지 빌드
docker build -t my-spring-app:1.0 .
# 빌드 과정:
# [+] Building 23.5s (8/8) FINISHED
# => [1/4] FROM openjdk:17-jdk-slim
# => [2/4] WORKDIR /app
# => [3/4] COPY build/libs/*.jar app.jar
# => [4/4] EXPOSE 8080
# => exporting to image
# 컨테이너 실행
docker run -d \
--name spring-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
my-spring-app:1.0
# 로그 확인
docker logs -f spring-app
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot :: (v3.2.0)
4단계: 테스트
# API 호출 테스트
curl http://localhost:8080/api/health
# {"status":"UP"}
# 컨테이너 상태 확인
docker ps
# CONTAINER ID IMAGE STATUS PORTS
# abc123def456 my-spring-app:1.0 Up 2 minutes 0.0.0.0:8080->8080/tcp
실습: MySQL 컨테이너 띄우기
# MySQL 컨테이너 실행 (상세 설정)
docker run -d \
--name mysql-db \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root1234 \
-e MYSQL_DATABASE=mydb \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=apppass \
-v mysql-data:/var/lib/mysql \
-v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
mysql:8.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci
# 환경 변수 설명:
# MYSQL_ROOT_PASSWORD : root 계정 비밀번호 (필수!)
# MYSQL_DATABASE : 자동 생성할 데이터베이스
# MYSQL_USER : 추가 생성할 사용자
# MYSQL_PASSWORD : 추가 사용자 비밀번호
# MySQL 접속 테스트
docker exec -it mysql-db mysql -u root -p
# Enter password: root1234
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
| mysql |
| performance_schema |
| sys |
+--------------------+
mysql> USE mydb;
mysql> SHOW TABLES;
📝 Dockerfile 작성 심화
Spring Boot용 최적화된 Dockerfile
# ===== 1단계: 빌드 스테이지 =====
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
# Gradle 캐싱을 위해 의존성 파일 먼저 복사
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
# 의존성 다운로드 (캐시됨)
RUN gradle dependencies --no-daemon || return 0
# 소스 코드 복사
COPY src ./src
# 빌드 실행 (테스트 제외)
RUN gradle build --no-daemon -x test
# ===== 2단계: 실행 스테이지 =====
FROM openjdk:17-jdk-slim
# 메타데이터
LABEL maintainer="coding-quokka@example.com"
LABEL version="1.0"
# 타임존 설정
ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 보안: non-root 사용자 생성
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
# 빌드된 JAR 파일만 복사
COPY --from=builder /app/build/libs/*.jar app.jar
# 소유권 변경
RUN chown -R appuser:appgroup /app
# non-root 사용자로 전환
USER appuser
# 포트 노출
EXPOSE 8080
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# JVM 최적화 옵션과 함께 실행
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]
멀티 스테이지 빌드의 장점:
빌드 스테이지 이미지:
- Gradle + JDK + 소스코드 + 의존성
- 크기: ~1.5GB 😱
최종 실행 이미지:
- JDK-slim + JAR 파일만
- 크기: ~400MB ✅
→ 70% 이상 크기 절감!
→ 보안 향상 (빌드 도구 미포함)
.dockerignore 파일
# .dockerignore
# Git
.git
.gitignore
# Gradle
.gradle
build/
!build/libs/*.jar
# IDE
.idea
*.iml
.vscode
*.swp
# 로그
*.log
logs/
# 테스트
src/test/
# 문서
*.md
docs/
# OS 파일
.DS_Store
Thumbs.db
# 환경 파일
.env
.env.local
Dockerfile Best Practices
# ✅ 1. 구체적인 태그 사용
FROM openjdk:17-jdk-slim
# ❌ FROM openjdk:latest
# ✅ 2. 멀티스테이지 빌드로 이미지 크기 최소화
FROM gradle:8.5-jdk17 AS builder
...
FROM openjdk:17-jdk-slim
COPY --from=builder ...
# ✅ 3. 레이어 캐싱 최적화 (자주 변경되는 것을 나중에)
COPY build.gradle settings.gradle ./
RUN gradle dependencies
COPY src ./src
RUN gradle build
# ✅ 4. RUN 명령 합치기 (레이어 수 줄이기)
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# ❌ 나쁜 예
RUN apt-get update
RUN apt-get install -y curl
# ✅ 5. non-root 사용자 사용
RUN useradd -r appuser
USER appuser
# ✅ 6. COPY vs ADD
COPY app.jar /app/ # 단순 복사는 COPY
ADD https://example.com/file /app/ # URL, tar 압축해제는 ADD
# ✅ 7. HEALTHCHECK 추가
HEALTHCHECK --interval=30s CMD curl -f http://localhost:8080/health
# ✅ 8. ARG로 빌드 시 변수 받기
ARG JAR_FILE=app.jar
COPY ${JAR_FILE} /app/app.jar
🐙 Docker Compose - 멀티 컨테이너 관리
Docker Compose란?


실제 서비스는 여러 컨테이너가 함께 돌아가죠:
- Spring Boot 애플리케이션
- MySQL 데이터베이스
- Redis 캐시
- Nginx 웹서버
이걸 하나하나 docker run으로 실행하면 너무 번거로워요:
# 😱 매번 이렇게 입력해야 함...
docker network create my-network
docker run -d --name mysql-db \
--network my-network \
-e MYSQL_ROOT_PASSWORD=root1234 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
docker run -d --name redis-cache \
--network my-network \
redis:alpine
docker run -d --name spring-app \
--network my-network \
-p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:mysql://mysql-db:3306/mydb \
my-spring-app:1.0
Docker Compose는 여러 컨테이너를 YAML 파일 하나로 정의하고 한 번에 실행할 수 있게 해줍니다!
# ✨ 이것만으로 전체 인프라 실행!
docker compose up -d
docker-compose.yml 기본 구조
version: '3.8' # Compose 파일 버전
services: # 컨테이너 정의
app: # 서비스 이름 (컨테이너 이름이 됨)
...
db:
...
cache:
...
networks: # 네트워크 정의
app-network:
...
volumes: # 볼륨 정의
db-data:
...
완전한 docker-compose.yml 예시

version: '3.8'
services:
# ===== Spring Boot 애플리케이션 =====
app:
build:
context: .
dockerfile: Dockerfile
container_name: spring-app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=root1234
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
# ===== MySQL 데이터베이스 =====
mysql:
image: mysql:8.0
container_name: mysql-db
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root1234
- MYSQL_DATABASE=mydb
- MYSQL_CHARACTER_SET_SERVER=utf8mb4
- MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci
- TZ=Asia/Seoul
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot1234"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
# ===== Redis 캐시 =====
redis:
image: redis:alpine
container_name: redis-cache
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
command: redis-server --appendonly yes
# ===== Nginx 리버스 프록시 (선택사항) =====
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- app-network
restart: unless-stopped
# ===== 네트워크 정의 =====
networks:
app-network:
driver: bridge
# ===== 볼륨 정의 =====
volumes:
mysql-data:
driver: local
redis-data:
driver: local
Docker Compose 주요 옵션 설명
services:
app:
# 이미지 빌드 설정
build:
context: . # Dockerfile 위치
dockerfile: Dockerfile # Dockerfile 이름
args: # 빌드 인자
JAR_FILE: app.jar
# 또는 기존 이미지 사용
image: my-app:1.0
# 컨테이너 이름
container_name: my-app
# 포트 매핑
ports:
- "8080:8080" # 호스트:컨테이너
- "8443:8443"
# 환경 변수
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql
# 또는 파일에서 로드
env_file:
- .env
- .env.local
# 볼륨 마운트
volumes:
- app-data:/app/data # Named volume
- ./logs:/app/logs # Bind mount
- ./config.yml:/app/config.yml:ro # 읽기 전용
# 의존성 (시작 순서)
depends_on:
mysql:
condition: service_healthy # 헬스체크 통과 후
redis:
condition: service_started # 시작만 하면 OK
# 네트워크
networks:
- app-network
- monitoring-network
# 재시작 정책
restart: unless-stopped
# no | always | on-failure | unless-stopped
# 리소스 제한
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
# 헬스체크
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# 로깅 설정
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Docker Compose 명령어
# ===== 기본 명령어 =====
# 모든 서비스 시작 (백그라운드)
docker compose up -d
# 로그 확인 (모든 서비스)
docker compose logs -f
# 특정 서비스 로그만
docker compose logs -f app
# 모든 서비스 중지
docker compose down
# 볼륨까지 삭제 (데이터 초기화)
docker compose down -v
# 서비스 재빌드 후 시작
docker compose up -d --build
# 특정 서비스만 재빌드
docker compose up -d --build app
# 실행 중인 서비스 확인
docker compose ps
# 서비스 스케일링 (여러 인스턴스)
docker compose up -d --scale app=3
# ===== 서비스 제어 =====
# 특정 서비스만 시작
docker compose up -d mysql redis
# 특정 서비스 중지
docker compose stop app
# 특정 서비스 재시작
docker compose restart app
# 서비스 내부 접속
docker compose exec app /bin/bash
docker compose exec mysql mysql -u root -p
# ===== 설정 확인 =====
# docker-compose.yml 문법 검사
docker compose config
# 서비스 설정 확인
docker compose config --services
# 이미지 목록
docker compose images
# ===== 정리 =====
# 중지된 컨테이너 삭제
docker compose rm
# 전체 정리 (컨테이너, 네트워크, 볼륨)
docker compose down -v --rmi all --remove-orphans
실행 결과
$ docker compose up -d
[+] Running 5/5
✔ Network myapp_app-network Created 0.1s
✔ Volume "myapp_mysql-data" Created 0.0s
✔ Volume "myapp_redis-data" Created 0.0s
✔ Container mysql-db Healthy 31.2s
✔ Container redis-cache Started 0.9s
✔ Container spring-app Started 32.1s
$ docker compose ps
NAME IMAGE STATUS PORTS
mysql-db mysql:8.0 Up 2 minutes (healthy) 0.0.0.0:3306->3306/tcp
redis-cache redis:alpine Up 2 minutes 0.0.0.0:6379->6379/tcp
spring-app myapp-app Up 2 minutes (healthy) 0.0.0.0:8080->8080/tcp
명령어 한 줄로 전체 인프라가 뜨는 마법! ✨
🔗 Docker 네트워크 심화
Docker 네트워크 타입

1️⃣ bridge (기본값)
- 단일 호스트 내 컨테이너 간 통신
- Docker Compose 기본 네트워크
- 가장 많이 사용
2️⃣ host
- 호스트 네트워크 직접 사용
- 포트 매핑 불필요
- 성능 최적화 시 사용
3️⃣ none
- 네트워크 비활성화
- 완전 격리 필요 시
4️⃣ overlay
- 여러 Docker 호스트 간 통신
- Docker Swarm에서 사용
컨테이너 간 통신 원리

# docker-compose.yml에서
services:
app:
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
# ↑ 컨테이너 이름으로 접근!
Docker Compose 네트워크 (bridge):
┌─────────────────────────────────────────────────────┐
│ app-network │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ spring │ │ mysql │ │ redis │ │
│ │ app │→ │ db │ │ cache │ │
│ │ :8080 │ │ :3306 │ │ :6379 │ │
│ │ 172.18.0.3 │ │ 172.18.0.2 │ │ 172.18.0.4 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Docker 내장 DNS 서버 (127.0.0.11) │
│ mysql → 172.18.0.2 자동 해석 │
│ redis → 172.18.0.4 자동 해석 │
└─────────────────────────────────────────────────────┘
│
│ 포트 매핑
▼
┌─────────────────────────────────────────────────────┐
│ Host Machine │
│ localhost:8080 → spring-app:8080 │
│ localhost:3306 → mysql:3306 │
│ localhost:6379 → redis:6379 │
└─────────────────────────────────────────────────────┘
같은 네트워크 안에서는:
✅ 컨테이너 이름 = 호스트명 (DNS 자동 해석)
✅ 내부 통신은 포트 매핑 불필요
✅ 외부 접근만 포트 매핑 필요
네트워크 명령어
# 네트워크 목록
docker network ls
# 네트워크 상세 정보
docker network inspect app-network
# 네트워크 생성
docker network create my-network
docker network create --driver bridge my-bridge
# 컨테이너를 네트워크에 연결
docker network connect my-network my-container
# 네트워크에서 연결 해제
docker network disconnect my-network my-container
# 네트워크 삭제
docker network rm my-network
# 사용하지 않는 네트워크 정리
docker network prune
📦 Docker Hub에 이미지 푸시


Docker Hub 계정 생성 및 로그인
# 1️⃣ Docker Hub 가입
# https://hub.docker.com/ 에서 회원가입
# 2️⃣ 터미널에서 로그인
docker login
# Username: your-username
# Password: your-password
# Login Succeeded
# 또는 토큰 사용 (보안 권장)
docker login -u your-username --password-stdin < token.txt
이미지 푸시 과정
# 1️⃣ 이미지 빌드
docker build -t my-spring-app:1.0 .
# 2️⃣ Docker Hub 형식으로 태그 지정
# 형식: docker-hub-username/repository:tag
docker tag my-spring-app:1.0 codingquokka/my-spring-app:1.0
docker tag my-spring-app:1.0 codingquokka/my-spring-app:latest
# 3️⃣ 이미지 푸시
docker push codingquokka/my-spring-app:1.0
docker push codingquokka/my-spring-app:latest
# 푸시 진행 상황:
# The push refers to repository [docker.io/codingquokka/my-spring-app]
# 5f70bf18a086: Pushed
# 3c816b4ead84: Pushed
# 1.0: digest: sha256:abc123... size: 1234
# 4️⃣ 다른 곳에서 이미지 받기
docker pull codingquokka/my-spring-app:1.0
이미지 버전 관리 전략
# Semantic Versioning 사용
docker tag my-app:latest codingquokka/my-app:1.0.0
docker tag my-app:latest codingquokka/my-app:1.0
docker tag my-app:latest codingquokka/my-app:1
docker tag my-app:latest codingquokka/my-app:latest
# 모두 푸시
docker push codingquokka/my-app:1.0.0
docker push codingquokka/my-app:1.0
docker push codingquokka/my-app:1
docker push codingquokka/my-app:latest
# 사용자는 원하는 정밀도로 버전 선택 가능:
# codingquokka/my-app:1.0.0 ← 정확한 버전
# codingquokka/my-app:1.0 ← 1.0.x 중 최신
# codingquokka/my-app:1 ← 1.x.x 중 최신
# codingquokka/my-app:latest ← 가장 최신
🛠️ 실전 트러블슈팅
문제 1: 포트 충돌
Error response from daemon: driver failed programming external connectivity:
Bind for 0.0.0.0:3306 failed: port is already allocated
원인: 호스트에서 이미 해당 포트를 사용 중
해결:
# 1️⃣ 사용 중인 포트 확인
# Linux/Mac
lsof -i :3306
netstat -tlnp | grep 3306
# Windows
netstat -ano | findstr :3306
# 2️⃣ 해당 프로세스 종료하거나
# 3️⃣ 다른 포트로 매핑
docker run -p 3307:3306 mysql:8.0
# docker-compose.yml에서
ports:
- "3307:3306" # 호스트 포트 변경
문제 2: 컨테이너가 바로 종료됨
$ docker ps
# 아무것도 안 나옴...
$ docker ps -a
CONTAINER ID IMAGE STATUS
a1b2c3d4 my-app Exited (1) 2 seconds ago
원인: 애플리케이션 에러로 종료
해결:
# 1️⃣ 로그 확인 (가장 먼저!)
docker logs a1b2c3d4
docker logs --tail 100 a1b2c3d4
# 2️⃣ 대화형 모드로 실행해서 디버깅
docker run -it my-app /bin/bash
# 3️⃣ 내부에서 직접 실행해보기
java -jar app.jar
# 4️⃣ 환경 변수 확인
docker run -it my-app env
문제 3: MySQL 연결 실패
com.mysql.cj.jdbc.exceptions.CommunicationsException:
Communications link failure
원인: MySQL이 아직 준비 안 됨 (Spring이 먼저 시작)
해결:
# docker-compose.yml에서 healthcheck + depends_on 조합
services:
app:
depends_on:
mysql:
condition: service_healthy # 헬스체크 통과 후 시작!
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
또는 애플리케이션 레벨에서 재시도:
# application.yml
spring:
datasource:
hikari:
connection-timeout: 30000
initialization-fail-timeout: 60000
maximum-pool-size: 10
문제 4: 볼륨 데이터가 안 보임
$ docker compose down
$ docker compose up -d
# MySQL 데이터가 사라짐!
원인: 볼륨 설정 누락 또는 잘못된 경로
해결:
# docker-compose.yml
services:
mysql:
volumes:
- mysql-data:/var/lib/mysql # 명명된 볼륨 사용!
volumes:
mysql-data: # 볼륨 정의 필수!
driver: local
# 볼륨 확인
docker volume ls
docker volume inspect mysql-data
# down 시 볼륨 유지 확인
docker compose down # 볼륨 유지됨 ✅
docker compose down -v # 볼륨 삭제됨 ❌
문제 5: 이미지 빌드 실패 - 캐시 문제
Step 4/8 : RUN apt-get update
---> Using cache
...
E: Unable to fetch some archives
원인: 오래된 캐시 사용
해결:
# 캐시 없이 빌드
docker build --no-cache -t my-app:1.0 .
# 또는 빌더 캐시 정리
docker builder prune
# 모든 캐시 정리
docker system prune -a
문제 6: 디스크 공간 부족
Error: no space left on device
해결:
# Docker가 사용하는 공간 확인
docker system df
# 상세 정보
docker system df -v
# 미사용 리소스 정리 (조심!)
docker system prune # 기본 정리
docker system prune -a # 모든 미사용 이미지 포함
docker system prune -a --volumes # 볼륨까지 포함 (데이터 손실 주의!)
# 개별 정리
docker container prune # 중지된 컨테이너
docker image prune -a # 미사용 이미지
docker volume prune # 미사용 볼륨
docker network prune # 미사용 네트워크
💡 오늘 나는 무엇을 배웠는가
1. Docker는 "환경"을 패키징한다
예전 배포:
1. 서버에 Java 설치
2. 서버에 MySQL 설치
3. 환경 변수 설정
4. 포트 설정
5. 타임존 설정
6. JAR 파일 복사
7. 실행... 에러! 😭
8. 환경 맞추기... 또 에러!
9. 3시간 삽질...
Docker 배포:
1. docker compose up -d
2. 끝! 🎉 (2분 컷)
2. 컨테이너는 일회용이다
전통적 사고:
"서버는 소중히 관리해야 해. 설정 건드리면 큰일나!"
"서버 세팅에 3일 걸렸는데, 절대 지우면 안 돼!"
컨테이너 사고 (Cattle, not Pets):
"문제 생기면? 그냥 지우고 새로 띄우면 돼!"
"3일 세팅? Dockerfile에 다 정의되어 있으니 1분이면 복구!"
docker compose down
docker compose up -d
→ 깨끗한 환경으로 즉시 복구!
3. 인프라도 코드로 관리한다 (IaC)
# docker-compose.yml = 인프라 설계도
# Git으로 버전 관리 가능
# 코드 리뷰 가능
# 누가 봐도 동일한 환경 구축 가능
# 변경 이력 추적 가능
version: '3.8'
services:
app:
...
mysql:
...
redis:
...
4. 레이어 캐싱이 빌드 속도의 핵심
# 변경 빈도가 낮은 것 → 먼저 (캐시 재사용)
# 변경 빈도가 높은 것 → 나중에
COPY build.gradle settings.gradle ./ # 거의 안 변함
RUN gradle dependencies # 캐시됨!
COPY src ./src # 자주 변함
RUN gradle build # 여기만 재빌드
😊 좋았던 점 & 😅 아쉬웠던 점
좋았던 점
👍 환경 구축 시간 대폭 단축
예전에는 프로젝트 시작할 때 MySQL 설치, Redis 설치, 환경 설정... 반나절은 걸렸는데, 이제는 docker compose up -d 한 줄이면 5분 안에 끝!
👍 팀 프로젝트에서 환경 통일
"내 컴퓨터에선 되는데..." 이제 이 말 안 해도 돼요. docker-compose.yml 하나 공유하면 팀원 모두 동일한 환경!
👍 실험하기 편함
새로운 기술 테스트할 때 컨테이너로 띄워보고, 마음에 안 들면 바로 삭제. 내 컴퓨터는 깨끗하게 유지!
👍 롤백이 쉬움
# 새 버전에 버그 있으면?
docker compose down
docker compose up -d --image my-app:1.0 # 이전 버전으로!
아쉬웠던 점
😢 Kubernetes까지는 못 배움
Docker 다음은 Kubernetes라고 하는데, 이번에는 Docker까지만 다뤘어요. 실제 프로덕션 환경에서는 K8s가 필수라고 하니 다음에 꼭 공부해야겠어요.
😢 Docker 보안 설정
일단 동작하게 만드는 데 집중했는데, 보안 관련 설정(이미지 취약점 스캔, 네트워크 격리, seccomp 프로파일 등)은 더 공부가 필요해요.
😢 Windows에서의 제약
Windows에서 Docker Desktop을 쓰니까 WSL2 관련 이슈가 가끔 있더라고요. 특히 파일 시스템 성능이 Linux보다 느려요.
😢 컨테이너 모니터링
docker stats로 기본적인 리소스만 확인했는데, Prometheus + Grafana로 제대로 된 모니터링을 구축하고 싶어요.
🤔 어려웠던 점과 해결
문제 1: Dockerfile 레이어 캐싱 이해
처음에는 왜 COPY 순서가 중요한지 몰랐어요.
# ❌ 소스 조금만 바꿔도 전체 빌드
COPY . .
RUN gradle build
# ✅ 소스만 바뀌면 의존성은 캐시 사용
COPY build.gradle .
RUN gradle dependencies
COPY src .
RUN gradle build
깨달음: 자주 변경되는 파일은 Dockerfile 아래쪽에!
문제 2: 컨테이너 간 통신
localhost로 접근하려다 계속 실패했어요.
# ❌ 안 됨 - 각 컨테이너는 자기만의 localhost가 있음
url: jdbc:mysql://localhost:3306/mydb
# ✅ 됨 - Docker 내부 DNS가 해석
url: jdbc:mysql://mysql:3306/mydb
깨달음: 같은 네트워크 안에서는 컨테이너 이름 = 호스트명!
문제 3: 권한 문제
non-root 사용자로 실행하니 파일 쓰기가 안 됐어요.
# 권한 문제 발생
USER appuser
# /app/logs에 쓰기 실패!
해결:
RUN mkdir -p /app/logs && chown -R appuser:appgroup /app
USER appuser
🎓 나만의 학습 팁
1. Docker Desktop의 GUI 활용
CLI도 좋지만, 처음에는 Docker Desktop의 GUI로 컨테이너 상태를 보면서 학습하면 이해가 빨라요!
2. 작은 것부터 시작

# 1단계: 공식 이미지 실행해보기
docker run nginx
docker run mysql
# 2단계: 포트 매핑 이해하기
docker run -p 80:80 nginx
# 3단계: 환경 변수 사용하기
docker run -e MYSQL_ROOT_PASSWORD=1234 mysql
# 4단계: 볼륨 마운트
docker run -v my-data:/var/lib/mysql mysql
# 5단계: 직접 Dockerfile 작성
# 6단계: Docker Compose 사용
3. .dockerignore 꼭 사용하기
# .dockerignore
.git
node_modules
build
*.log
.env
빌드 컨텍스트 크기가 줄어들어 빌드 속도가 빨라져요!
4. 유용한 별칭 설정
# ~/.bashrc 또는 ~/.zshrc에 추가
alias dc='docker compose'
alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias dlogs='docker logs -f'
alias dexec='docker exec -it'
alias dclean='docker system prune -af'
# 사용 예
dc up -d
dps
dlogs spring-app
dexec spring-app /bin/bash
5. 자주 쓰는 명령어 스크립트화
#!/bin/bash
# scripts/docker-rebuild.sh
echo "🔄 Rebuilding containers..."
docker compose down
docker compose build --no-cache
docker compose up -d
echo "✅ Done! Checking status..."
docker compose ps
docker compose logs -f
🚀 다음 학습 목표
📌 단기 목표 (이번 주)
✓ 기존 프로젝트 전체 Docker화 완료
✓ Docker Hub에 이미지 배포
✓ EC2에서 Docker Compose로 운영
📌 중기 목표 (이번 달)
✓ CI/CD 파이프라인에 Docker 통합 (GitHub Actions)
✓ Docker 이미지 최적화 (멀티스테이지 빌드 심화)
✓ 컨테이너 모니터링 (Prometheus + Grafana)
📌 장기 목표 (부트캠프 기간)
✓ Kubernetes 입문
✓ AWS ECS 또는 EKS 배포 경험
✓ 포트폴리오 프로젝트 전체 컨테이너화
🎬 마치며
Docker를 배우기 전에는 "컨테이너? 그거 DevOps 엔지니어나 하는 거 아니야?"라고 생각했어요.
하지만 직접 써보니까, 백엔드 개발자도 반드시 알아야 하는 필수 기술이더라고요!
특히 이런 순간에 Docker의 가치를 느꼈어요:
"로컬에서 작업한 거, 팀원한테 공유할 때" → docker-compose.yml 하나면 끝!
"새 프로젝트 시작할 때 DB 세팅" → docker run mysql 한 줄이면 끝!
"서버 배포할 때" → 이미지 푸시하고 pull 받으면 끝!
"개발 환경 초기화하고 싶을 때" → docker compose down -v && docker compose up -d
이제 "내 컴퓨터에선 되는데..."라는 변명은 더 이상 할 수 없게 됐어요. 환경이 다르면 그냥 컨테이너로 통일하면 되니까요! 🐳
다음에는 GitHub Actions와 연동해서 push만 하면 자동으로 Docker 빌드 → 배포까지 되는 CI/CD 파이프라인을 구축해보고 싶어요!
여러분도 Docker, 꼭 배워보세요. 개발 생산성이 확 올라갑니다! 💪
📚 참고 자료
공식 문서
추천 블로그
추천 도서
- "도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문" - 야마다 아키노리
- "시작하세요! 도커/쿠버네티스" - 용찬호
유용한 도구
- Docker Desktop
- Portainer - Docker GUI 관리 도구
- Dive - Docker 이미지 레이어 분석 도구