도커, '내 컴퓨터에서는 되는데'를 없앤 이미지 레이어의 힘
Docker Engine 구조와 이미지 레이어, 컨테이너 생명주기, 네트워킹·볼륨·Compose를 실무 관점에서 정리한다
2026-08-12 · 최초 발행 2026-01-12
"내 컴퓨터에서는 되는데요"라는 말이 나오는 이유는 대개 환경 차이 때문이다. 개발자 로컬에는 있고 스테이징 서버에는 없는 라이브러리 버전, 다른 OS 설정, 다른 네트워크 구성—Docker가 2013년 dotCloud(현 Docker Inc.)에서 나온 뒤 빠르게 퍼진 이유는 이 문제를 이미지 하나로 봉인해버렸기 때문이다. Go 언어로 개발된 오픈소스 플랫폼인 Docker는 애플리케이션과 그 의존성을 컨테이너라는 격리된 환경에 통째로 패키징한다.
Build Once, Run Anywhere가 실제로 뜻하는 것
Docker의 핵심 철학은 두 가지다. 한 번 빌드한 이미지를 개발·테스트·프로덕션 어디서나 동일하게 실행하는 Build Once, Run Anywhere, 그리고 인프라를 수정하는 대신 통째로 교체하는 Immutable Infrastructure. 이 두 철학이 실무에 미치는 영향은 "버전 관리"라는 한 단어로 요약된다—인프라 구성을 코드처럼 버전을 매기고 롤백할 수 있다는 것, 그리고 "이 서버만 설정이 다른" 드리프트 문제 자체가 구조적으로 사라진다는 것이다.
클라이언트-서버로 나뉜 이유
Docker Engine은 백그라운드에서 이미지·컨테이너·네트워크·볼륨을 관리하고 API 요청을 처리하는 Docker Daemon(dockerd), 그리고 CLI로 사용자 명령을 받아 Daemon과 통신하는 Docker Client(docker)로 나뉜다. 이 분리 덕분에 로컬 CLI에서 원격 Daemon에 명령을 보내는 것도 가능하다. 이미지는 Docker Hub 같은 공식 Registry나 프라이빗 Registry에 저장되고, docker pull/push로 오간다.
이미지가 레이어로 쪼개진 진짜 이유
이미지는 컨테이너 실행을 위한 읽기 전용 템플릿이고 불변(Immutable)이다. 여러 레이어로 구성돼 각 레이어는 변경사항만 저장하는데, 이게 그냥 설계상의 선택이 아니라 Union File System을 통한 레이어 공유로 저장 공간을 절약하기 위한 장치다—같은 베이스 이미지를 쓰는 여러 이미지가 그 레이어를 중복 저장하지 않고 공유한다. Dockerfile은 이 레이어를 선언적으로 정의하는 명령어 모음이다. FROM으로 베이스 이미지를 지정하고, RUN으로 명령을 실행하고, COPY/ADD로 파일을 넣고, WORKDIR·ENV·EXPOSE로 환경을 설정하고, CMD·ENTRYPOINT로 실행 방식을 정한다.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
레이어 순서는 그냥 문서상의 순서가 아니라 캐시 전략이다. package.json을 먼저 COPY하고 npm install을 실행한 다음 나머지 소스를 COPY하는 순서는, 소스 코드만 바뀌었을 때 npm install 레이어의 캐시를 그대로 재사용하기 위한 배치다.
컨테이너는 이미지에 쓰기 가능한 레이어 하나를 더한 것
컨테이너는 이미지의 실행 인스턴스다—격리된 프로세스이고, 이미지 위에 읽기/쓰기 가능한 레이어가 하나 더 얹힌다. 호스트 커널을 공유해 경량이고, 초 단위로 시작하며, 프로세스·네트워크·파일시스템이 격리된다.
생명주기는 명확한 상태 전이로 정의된다. docker create로 Created 상태가 되고, docker start로 Running, docker pause/unpause로 Paused와 Running을 오가고, docker stop이나 docker kill로 Stopped, docker rm으로 최종 Removed에 이른다.
docker ps로 실행 중인 컨테이너를, docker logs로 로그를, docker exec로 컨테이너 내부 명령 실행을 확인하는 것이 일상적인 운영 명령어다.
네트워크 드라이버는 격리 수준을 고르는 문제다
bridge는 기본 드라이버로 단일 호스트 내 컨테이너 통신을 가상 브릿지로 처리하고 NAT로 외부와 통신한다. host는 호스트 네트워크 스택을 그대로 써서 네트워크 격리는 없지만 성능은 높다. overlay는 다중 호스트 간 통신을 VXLAN 기반으로 처리하며 Docker Swarm에서 쓰인다. none은 네트워크를 아예 비활성화해 완전한 격리를 만든다. 어떤 드라이버를 고를지는 결국 "이 컨테이너가 다른 호스트의 컨테이너와 통신해야 하는가"와 "격리와 성능 중 어느 쪽이 더 중요한가"라는 두 질문으로 좁혀진다.
볼륨을 안 쓰면 컨테이너를 지울 때 데이터도 같이 사라진다
컨테이너는 삭제되면 그 안의 데이터도 함께 사라지고, 컨테이너 간 데이터 공유도 기본적으로는 되지 않는다. 이 문제를 푸는 게 볼륨이다. Volumes는 Docker가 관리하는 영역에 저장돼 DB 같은 영속 데이터에 적합하고, Bind Mounts는 호스트 경로를 직접 마운트해 개발이나 설정 파일 공유에 쓰이며, tmpfs는 메모리에 저장돼 임시 데이터나 민감한 정보를 다룰 때 쓴다. docker volume create/ls/inspect/rm으로 관리한다.
Compose는 여러 컨테이너를 하나의 명령으로 묶는다
단일 컨테이너 명령을 하나씩 실행하는 대신, docker-compose.yml에 서비스를 선언하고 docker-compose up 한 번으로 전체 스택을 띄우는 게 Compose의 역할이다.
version: "3.8"
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:14
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
db-data:
depends_on으로 서비스 간 시작 순서를, volumes로 데이터 영속성을, environment로 설정을 함께 선언하는 방식이라 로컬 개발 환경을 통째로 재현하기 쉬워진다. docker-compose up/down/ps/logs/build가 주로 쓰는 명령이다.
VM과 비교하면 결국 격리 수준과 밀도의 문제
Docker는 OS 수준 가상화로 호스트 커널을 공유해 초 단위로 부팅되고 이미지 크기도 MB 단위인 반면, VM은 하드웨어 수준 가상화로 개별 Guest OS가 필요해 분 단위 부팅에 GB 단위 이미지가 된다. 이 차이가 그대로 밀도로 이어진다—같은 하드웨어에 VM은 수십 개, Docker 컨테이너는 수백 개를 올릴 수 있다. 대신 격리 수준은 Docker가 프로세스 격리에 머물고 VM은 완전 격리를 제공한다는 점에서 역전된다.
실제로 쓰이는 자리
일관된 개발 환경을 팀 간에 공유해 온보딩을 빠르게 하는 것, CI/CD 파이프라인에서 빌드 환경을 표준화하고 테스트를 격리하는 것, 마이크로서비스마다 독립적으로 배포하고 기술 스택을 다양화하는 것, 그리고 Kubernetes 같은 오케스트레이션과 결합해 멀티클라우드 이식성을 확보하는 것—이 네 가지가 Docker가 마이크로서비스 아키텍처와 DevOps 문화의 핵심 기술로 자리잡은 이유다.