컨테이너가 VM과 근본적으로 무엇이 다른지부터, 이미지 레이어 구조, Dockerfile·멀티 스테이지 빌드, BuildKit/buildx 멀티 플랫폼 빌드, 네트워크, 볼륨, Docker Compose, Docker Swarm 오케스트레이션, 컨테이너 보안·취약점 스캔까지 — Kubernetes로 넘어가기 전 반드시 다져야 할 Docker 실무 지식을 정리합니다.
컨테이너가 VM과 근본적으로 무엇이 다른지부터, 이미지 레이어 구조, Dockerfile·멀티 스테이지 빌드, BuildKit/buildx 멀티 플랫폼 빌드, 네트워크, 볼륨, Docker Compose, Docker Swarm 오케스트레이션, 컨테이너 보안·취약점 스캔까지 — Kubernetes로 넘어가기 전 반드시 다져야 할 Docker 실무 지식을 정리합니다. 이 가이드는 개념을 나열하기보다, 실제 프로젝트에서 판단해야 하는 순서대로 내용을 따라갈 수 있게 구성했습니다.
핵심 관점
인프라 / 운영
설치 명령을 외우기보다 트래픽, 런타임, 관측, 장애 대응이 어떤 순서로 이어지는지 파악합니다.
개발 환경 표준화CI/CD 파이프라인 & 멀티 플랫폼 빌드Docker Swarm 오케스트레이션마이크로서비스 패키징
구조 다이어그램
글로 읽은 내용을 머릿속에 오래 남기려면 먼저 흐름을 그림으로 잡는 편이 좋습니다. 아래 두 그림은 Docker를 학습할 때 계속 되돌아볼 수 있는 기준 지도입니다.
학습 흐름
다이어그램 렌더링 중…
아키텍처 관점
다이어그램 렌더링 중…
컨테이너란 무엇인가
Docker를 처음 펼칠 때는 세부 명령보다 큰 그림이 먼저입니다. 이 섹션에서는 앞으로 배울 개념들이 어떤 문제를 풀기 위해 등장했는지부터 잡아봅니다.
컨테이너는 애플리케이션과 그 실행에 필요한 라이브러리·런타임·설정을 하나의 격리된 단위로 묶는 기술입니다. 가상머신(VM)이 하드웨어 자체를 가상화해 게스트 OS 전체를 통째로 띄우는 것과 달리, 컨테이너는 호스트 OS의 커널을 그대로 공유하면서 프로세스 수준에서만 격리됩니다. 그래서 VM은 부팅에 수십 초~수 분이 걸리지만, 컨테이너는 이미 떠 있는 커널 위에서 프로세스 하나를 띄우는 것과 비슷해 1초 안팎으로 시작합니다.
다이어그램 렌더링 중…
비교 항목
VM
Container
격리 단위
OS 전체 (게스트 OS 포함)
프로세스 (커널 공유)
시작 속도
수십 초 ~ 수 분
1초 안팎
이미지 크기
보통 GB 단위
보통 MB 단위
밀도
서버당 수~수십 개
서버당 수십~수백 개
설치
여기서는 설치을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
macOS/Windows에서는 Docker Desktop이 Docker Engine, CLI, Compose를 한 번에 설치해줍니다. Linux 서버에는 Docker Desktop 없이 Docker Engine만 직접 설치하는 경우가 많습니다.
BASH
# Docker Desktop (macOS/Windows): https://docker.com/get-started# Linux — Docker Engine 설치curl -fsSL https://get.docker.com | shsudo usermod -aG docker $USER # sudo 없이 docker 명령 실행 (재로그인 필요)docker --versiondocker info # 데몬 연결 상태, 스토리지 드라이버 등 확인
기본 명령어와 컨테이너 생명주기
여기서는 기본 명령어와 컨테이너 생명주기을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
이미지(Image)는 컨테이너를 만들기 위한 읽기 전용 템플릿이고, 컨테이너(Container)는 그 이미지를 실행한 인스턴스입니다. 같은 이미지로 컨테이너를 몇 개든 띄울 수 있고, 컨테이너를 지워도 원본 이미지는 그대로 남습니다.
BASH
docker pull nginx # 이미지 다운로드docker run -d -p 80:80 --name web nginx # 백그라운드 실행 + 이름 지정docker ps # 실행 중인 컨테이너 목록docker ps -a # 종료된 컨테이너까지 전체 목록docker logs -f web # 로그 실시간 확인docker exec -it web bash # 실행 중인 컨테이너 내부 접속docker stop web # 정상 종료 (SIGTERM)docker rm web # 컨테이너 삭제 (정지 상태여야 함)docker rmi nginx # 이미지 삭제
명령어
역할
docker pull
레지스트리(Docker Hub 등)에서 이미지를 내려받음
docker run
이미지로부터 컨테이너를 생성하고 시작
docker stop / start
실행 중인 컨테이너를 정지 / 정지된 컨테이너를 재시작
docker rm / rmi
컨테이너 삭제 / 이미지 삭제
이미지와 레이어 구조
여기서는 이미지와 레이어 구조을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Docker 이미지는 Dockerfile의 각 명령어가 하나씩 쌓인 읽기 전용 레이어들의 집합입니다. 같은 레이어가 이미 캐시되어 있으면 Docker는 그 레이어를 다시 만들지 않고 재사용하기 때문에, Dockerfile에서 자주 바뀌는 내용을 뒤쪽에 둘수록 빌드가 빨라집니다.
다이어그램 렌더링 중…
Dockerfile 작성 & 멀티 스테이지 빌드
여기서는 Dockerfile 작성 & 멀티 스테이지 빌드을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
멀티 스테이지 빌드는 빌드에만 필요한 도구(컴파일러, devDependencies)와 실제 실행에 필요한 결과물을 서로 다른 스테이지로 분리합니다. 최종 이미지는 마지막 스테이지만 남기 때문에, 빌드 도구가 전혀 포함되지 않은 훨씬 가벼운 이미지를 만들 수 있습니다.
DockerfileDOCKERFILE
# Stage 1: 빌드 전용 — devDependencies와 소스 전체 포함FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build# Stage 2: 실행 전용 — 빌드 결과물과 production 의존성만 복사FROM node:20-alpine AS runnerWORKDIR /appENV NODE_ENV=productionCOPY package*.json ./RUN npm ci --omit=devCOPY --from=builder /app/dist ./distUSER nodeEXPOSE 3000CMD ["node", "dist/server.js"]
명령어
역할
FROM
베이스 이미지 지정 (AS로 스테이지 이름 부여 가능)
COPY --from=builder
이전 스테이지의 결과물만 선택적으로 복사
RUN
이미지 빌드 시점에 한 번 실행 (레이어 생성)
CMD vs ENTRYPOINT
CMD는 실행 시 통째로 덮어쓰기 쉬움, ENTRYPOINT는 고정 실행파일 + CMD로 기본 인자만 전달할 때 사용
BuildKit & buildx — 멀티 플랫폼 빌드
여기서는 BuildKit & buildx — 멀티 플랫폼 빌드을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
BuildKit은 Docker 18.09부터 기본으로 내장된 차세대 빌드 엔진으로, Dockerfile의 각 단계를 의존성 그래프로 분석해 관련 없는 단계를 병렬로 실행하고, 캐시를 훨씬 세밀하게(레이어 단위가 아니라 마운트 단위로) 재사용합니다. buildx는 이 BuildKit을 CLI에서 확장해 쓰는 플러그인으로, Apple Silicon Mac에서 만든 이미지를 x86 서버에도 그대로 돌아가게 하는 멀티 플랫폼 빌드가 대표 기능입니다.
다이어그램 렌더링 중…
Dockerfile — 캐시 마운트로 의존성 재설치 방지DOCKERFILE
# syntax=docker/dockerfile:1FROM node:20-alpineWORKDIR /appCOPY package*.json ./# npm 캐시를 레이어가 아니라 별도 캐시 마운트에 보관 — 이미지 레이어에는 남지 않으면서# 빌드마다 재사용되어, package.json이 바뀌어도 이미 받은 패키지는 다시 안 받음RUN --mount=type=cache,target=/root/.npm \ npm ciCOPY . .CMD ["node", "server.js"]
BASH
# buildx 빌더 생성 & 활성화 (최초 1회)docker buildx create --name multiarch --use# 여러 아키텍처를 동시에 빌드해 레지스트리에 바로 pushdocker buildx build \ --platform linux/amd64,linux/arm64 \ -t myrepo/myapp:1.0.0 \ --push .# 원격 캐시를 레지스트리에 저장 — CI 러너가 매번 새로 뜨는 환경에서도 캐시 재사용docker buildx build \ --cache-to type=registry,ref=myrepo/myapp:buildcache \ --cache-from type=registry,ref=myrepo/myapp:buildcache \ -t myrepo/myapp:1.0.0 --push .
기능
레거시 빌더
BuildKit/buildx
빌드 실행 방식
레이어를 순차적으로 하나씩 빌드
의존성 그래프를 분석해 무관한 단계를 병렬 실행
캐시 단위
레이어 전체 단위로만 캐시
RUN --mount=type=cache로 특정 디렉토리만 세밀하게 캐시
시크릿 전달
ARG/ENV로 전달 시 이미지 레이어에 흔적이 남을 위험
--secret으로 전달 시 최종 이미지에 전혀 남지 않음
멀티 플랫폼
아키텍처별로 각각 빌드 후 수동으로 매니페스트 관리
buildx build --platform으로 한 번에 빌드 + push
Docker 네트워크
여기서는 Docker 네트워크을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
컨테이너를 아무 옵션 없이 실행하면 기본적으로 bridge 네트워크에 연결됩니다. 같은 bridge 네트워크에 속한 컨테이너끼리는 서비스 이름을 DNS처럼 사용해 서로를 찾을 수 있고, 호스트에서 컨테이너로 들어오려면 -p로 포트를 명시적으로 매핑해야 합니다.
다이어그램 렌더링 중…
네트워크 드라이버
용도
bridge (기본값)
같은 호스트 안 컨테이너 간 통신 — 대부분의 로컬/단일 서버 환경
host
컨테이너가 호스트의 네트워크를 그대로 사용 (포트 매핑 불필요, 격리 약화)
none
네트워크 완전 비활성화 — 배치 작업 등 통신이 필요 없는 경우
overlay
여러 호스트에 걸친 컨테이너 통신 — Docker Swarm/멀티 호스트 환경
볼륨과 데이터 영속성
여기서는 볼륨과 데이터 영속성을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
컨테이너를 삭제하면 그 안에 쓰여진 파일도 함께 사라집니다. DB나 업로드 파일처럼 컨테이너 생명주기와 무관하게 데이터를 남겨야 한다면 반드시 볼륨이나 바인드 마운트로 호스트에 데이터를 분리해야 합니다.
다이어그램 렌더링 중…
방식
설명
주로 쓰는 곳
Named Volume
Docker가 관리하는 저장 공간 (-v pgdata:/data)
DB, 애플리케이션 상태 등 컨테이너가 소유하는 데이터
Bind Mount
호스트의 특정 경로를 그대로 연결 (-v ./src:/app/src)
로컬 개발 중 소스 코드 실시간 반영
tmpfs
메모리에만 저장, 컨테이너 종료 시 소멸
민감한 임시 데이터, 캐시
Docker Compose로 다중 컨테이너 구성
여기서는 Docker Compose로 다중 컨테이너 구성을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Docker Compose는 여러 컨테이너로 이루어진 애플리케이션을 하나의 YAML 파일로 정의하고 한 번에 띄우는 도구입니다. depends_on과 healthcheck를 함께 쓰면 DB가 실제로 요청을 받을 준비가 된 뒤에 API 컨테이너가 시작되도록 순서를 제어할 수 있습니다.
여기서는 Docker Swarm — 아키텍처을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Docker Swarm은 Docker Engine에 기본 내장된 오케스트레이션 모드로, 별도 설치 없이 docker swarm init 한 줄이면 여러 대의 서버를 하나의 클러스터로 묶을 수 있습니다. 클러스터는 클러스터 상태(어떤 서비스가 몇 개 떠 있어야 하는지 등)를 Raft 합의 알고리즘으로 관리하는 Manager 노드와, 실제로 컨테이너를 실행하는 Worker 노드로 나뉩니다.
다이어그램 렌더링 중…
BASH
# 첫 번째 매니저 노드에서 클러스터 초기화docker swarm init --advertise-addr 192.168.1.10# 출력된 토큰으로 워커 노드를 클러스터에 합류docker swarm join --token SWMTKN-1-xxxxx 192.168.1.10:2377# 매니저를 추가로 합류시킬 때 쓰는 토큰은 별도 발급docker swarm join-token manager# 클러스터 노드 목록 & 역할 확인docker node ls
항목
Manager
Worker
역할
클러스터 상태 저장, 스케줄링 결정, API 응답
할당받은 태스크(컨테이너)만 실행
장애 허용
N개 매니저 중 과반수(quorum)가 살아있어야 클러스터 동작
워커 하나가 죽어도 매니저가 다른 노드에 태스크 재배치
권장 대수
3, 5, 7대처럼 홀수 — (N-1)/2대까지 장애 허용
워크로드 양에 맞춰 자유롭게 증설
Swarm 서비스 배포 & 롤링 업데이트
여기서는 Swarm 서비스 배포 & 롤링 업데이트을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Swarm에서는 docker run 대신 docker service create로 "몇 개의 복제본(replica)을 유지할지"를 선언합니다. 태스크(복제본) 하나가 죽으면 Swarm이 자동으로 다른 노드에 새 태스크를 배치해 replica 수를 원래대로 되돌리고, 이미지를 업데이트할 때도 전체를 한 번에 내리지 않고 몇 개씩 순차적으로 교체합니다.
다이어그램 렌더링 중…
BASH
# 서비스 생성 — 3개 복제본, 80번 포트로 클러스터 전체에 노출(ingress mesh)docker service create --name web --replicas 3 -p 80:80 nginx:1.27# 부하에 맞춰 즉시 스케일 조정docker service scale web=6# 이미지 롤링 업데이트 — 한 번에 1개씩, 10초 간격으로 순차 교체docker service update \ --image nginx:1.28 \ --update-parallelism 1 \ --update-delay 10s \ --update-failure-action rollback \ web# 방금 배포에 문제가 있으면 이전 버전으로 즉시 롤백docker service rollback web# 서비스 상태 & 태스크별 배치 노드 확인docker service ps web
플래그
의미
--replicas
유지할 태스크(컨테이너) 개수 — Swarm이 항상 이 수를 맞춰 자동 복구
--update-parallelism
한 번에 몇 개의 태스크를 동시에 교체할지
--update-delay
각 배치 교체 사이에 대기할 시간 — 헬스체크가 안정화될 시간을 확보
--update-failure-action
업데이트 실패 시 동작 (pause 또는 rollback)
Docker Stack & Swarm vs Kubernetes
여기서는 Docker Stack & Swarm vs Kubernetes을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Docker Stack은 이미 작성해둔 Compose 파일을 그대로 Swarm 클러스터에 배포하는 기능입니다. 여러 노드에 걸쳐 서비스를 배치해도 서로 통신할 수 있도록 overlay 네트워크를 자동으로 구성하고, 비밀번호 같은 민감한 값은 이미지에 넣지 않고 secret으로 별도 관리할 수 있습니다.
# 시크릿을 클러스터에 먼저 등록 (Git에는 값을 커밋하지 않음)echo "s3cr3t" | docker secret create db_password -# Compose 파일 그대로 Stack으로 배포docker stack deploy -c docker-compose.yml myappdocker stack services myapp # 서비스별 replica 상태docker stack ps myapp # 태스크가 어느 노드에 배치됐는지
비교 항목
Docker Swarm
Kubernetes
설치·학습 곡선
Docker Engine에 내장 — 명령어 몇 개로 즉시 시작
별도 설치 필요, 개념(Pod·Service·Ingress 등)이 훨씬 많음
적합한 규모
노드 수십 대, 단순한 서비스 구성
노드 수백~수천 대, 복잡한 멀티팀 운영
생태계
Compose 파일 재사용 가능, 생태계는 상대적으로 작음
Helm·Operator·서비스 메시 등 압도적으로 큰 생태계
선택 기준
빠르게 띄우고 운영 부담을 최소화하고 싶을 때
확장성·세밀한 제어·업계 표준 도구 연동이 필요할 때
컨테이너 보안 & 취약점 스캔
여기서는 컨테이너 보안 & 취약점 스캔을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
컨테이너는 기본적으로 root 사용자로 실행되고, 호스트와 커널을 공유합니다. 이 두 가지 특성 때문에 이미지 안에서 실행 계정을 낮추고, 불필요한 권한과 공격 표면을 줄이는 것이 VM 환경보다 더 중요합니다. 여기에 더해 베이스 이미지에 알려진 취약점(CVE)이 없는지 배포 전에 스캔하는 것도 필수적인 절차입니다.
BASH
# Docker Scout — 이미지 취약점 요약 확인 (Docker Desktop에 내장)docker scout quickview myrepo/myapp:1.0.0# CVE 상세 목록 (심각도별)docker scout cves myrepo/myapp:1.0.0# 베이스 이미지를 더 안전한 버전으로 바꿀 수 있는지 추천docker scout recommendations myrepo/myapp:1.0.0# CI 파이프라인에서: Critical/High 취약점이 있으면 빌드 실패 처리docker scout cves --exit-code --only-severity critical,high myrepo/myapp:1.0.0
항목
하지 말아야 할 것
대신 이렇게
실행 계정
root로 그대로 실행
Dockerfile에 USER 지정 (예: USER node)
베이스 이미지
불필요하게 큰 풀 OS 이미지
alpine, distroless 등 최소 이미지
시크릿 관리
ENV나 Dockerfile ARG에 비밀번호 하드코딩
Secrets Manager/환경변수 주입, --secret 빌드 옵션
이미지 취약점
검증 없이 배포
docker scout, trivy 등으로 CI에서 스캔
베스트 프랙티스
이 섹션은 베스트 프랙티스을 실무 관점에서 정리합니다. 개념을 외우기보다, 어떤 상황에서 이 기준을 꺼내 쓸지에 초점을 맞춰보세요.
지금까지 다룬 내용을 실무에 적용할 때 반드시 챙겨야 할 체크리스트로 정리하면 다음과 같습니다.
다음 단계
이 섹션은 다음 단계을 실무 관점에서 정리합니다. 개념을 외우기보다, 어떤 상황에서 이 기준을 꺼내 쓸지에 초점을 맞춰보세요.
🚀
Docker 다음은 Kubernetes
컨테이너 하나를 잘 만드는 법을 익혔다면, 다음은 여러 컨테이너를 여러 서버에 걸쳐 안정적으로 운영하는 문제입니다 — 장애 시 자동 재시작, 트래픽에 따른 오토스케일링, 무중단 배포는 Docker 단독으로는 다루기 어렵습니다.