
Docker와 컨테이너: '내 컴퓨터에서는 되는데?'를 끝낸 기술의 모든 것
1979년 Unix의 chroot에서 2013년 Docker의 탄생까지. 컨테이너 기술이 왜 등장했고, 어떻게 작동하며, 소프트웨어 개발과 배포를 어떻게 바꿔놓았는지를 역사·원리·실전 사례로 풀어본다.

1979년 Unix의 chroot에서 2013년 Docker의 탄생까지. 컨테이너 기술이 왜 등장했고, 어떻게 작동하며, 소프트웨어 개발과 배포를 어떻게 바꿔놓았는지를 역사·원리·실전 사례로 풀어본다.
소프트웨어 개발자라면 한 번쯤 이 말을 하거나 들어봤을 것이다.
개발자의 노트북에서는 완벽하게 동작하던 애플리케이션이, 테스트 서버에 올리면 에러가 난다. 운영 서버에 배포하면 또 다른 에러가 난다. 원인은 대부분 같다 — 환경이 다르다.
같은 코드인데 실행 환경이 다르니 다른 결과가 나온다. 라이브러리 버전 하나, 시스템 설정 하나가 달라도 프로그램은 깨진다. 이 문제를 해결하기 위해 개발자들은 "환경 설정 문서"를 작성하고, "셋업 스크립트"를 만들었지만, 문서는 금방 구식이 되고 스크립트는 특정 OS에서만 돌아갔다.
이전 글에서 살펴본 EC2(가상 머신)는 이 문제를 부분적으로 해결했다. 같은 AMI로 서버를 찍어내면 동일한 환경을 만들 수 있었다. 하지만 VM은 너무 무거웠다. 운영체제 전체를 복제하니 수 GB의 이미지, 수 분의 부팅 시간, 그리고 상당한 자원 오버헤드가 필요했다.
"애플리케이션과 그 실행 환경만 묶어서, 어디서든 동일하게 실행할 수 있는 가벼운 방법은 없을까?"
이 질문에 대한 답이 컨테이너(Container) 다. 그리고 컨테이너를 대중화한 기술이 바로 Docker다.
Docker가 2013년에 갑자기 나타난 것처럼 보이지만, 컨테이너의 핵심 아이디어는 44년 전으로 거슬러 올라간다.
1979년, Unix V7 개발 중에 chroot(change root) 시스템 콜이 도입됐다. 프로세스가 보는 파일 시스템의 루트 디렉토리를 바꿔버리는 기능이다.
일반적으로 프로세스는 /(루트)부터 모든 파일에 접근할 수 있다. chroot를 사용하면 특정 프로세스에게 /home/jail/을 /로 보이게 만들 수 있다. 해당 프로세스는 /home/jail/ 밖의 파일이 존재하는지조차 모른다.
chroot는 파일 시스템만 격리할 뿐, 프로세스·네트워크·사용자는 격리하지 못했다. 하지만 **"프로세스에게 제한된 세계를 보여준다"**는 핵심 아이디어는 이후 모든 컨테이너 기술의 출발점이 된다.
2000년, FreeBSD 4.0에 도입된 Jails는 chroot를 대폭 확장했다. 파일 시스템뿐 아니라 프로세스, 네트워크, 사용자까지 격리하는 최초의 운영체제 수준 가상화 기술이었다.
Jails의 핵심 설계 원칙은 Poul-Henning Kamp와 Robert Watson이 2000년 USENIX 논문 "Jails: Confining the omnipotent root"에서 발표했다. 이 논문의 요점: root 권한을 가진 사용자조차 jail 밖을 볼 수 없어야 한다. 웹 호스팅 업체들이 한 서버에 여러 고객을 안전하게 수용하기 위해 Jails를 적극 채택했다.
Linux 진영에서도 격리 기술이 발전하기 시작했다. 이 두 기술이 오늘날 Docker를 포함한 모든 Linux 컨테이너의 기반이다.
Namespaces (2002~2013):
Linux 커널에 점진적으로 추가된 격리 메커니즘이다. 프로세스가 볼 수 있는 시스템 자원의 "뷰(view)"를 제한한다.
| 네임스페이스 | 격리 대상 | 도입 시기 |
|---|---|---|
| Mount (mnt) | 파일 시스템 마운트 포인트 | 2002 (Linux 2.4.19) |
| UTS | 호스트네임, 도메인네임 | 2006 (Linux 2.6.19) |
| IPC | 프로세스 간 통신 | 2006 (Linux 2.6.19) |
| PID | 프로세스 ID | 2008 (Linux 2.6.24) |
| Network (net) | 네트워크 스택 | 2009 (Linux 2.6.29) |
| User | 사용자/그룹 ID | 2013 (Linux 3.8) |
각 네임스페이스는 프로세스에게 **"내가 이 시스템의 유일한 사용자"**라는 환상을 준다. PID 네임스페이스 안에서 프로세스는 자신이 PID 1(init)이라고 생각한다. 네트워크 네임스페이스 안에서는 자신만의 IP와 포트를 가진다.
cgroups(Control Groups, 2006~2008):
Google의 엔지니어 Rohit Seth과 Paul Menage가 2006년에 "Process Containers"라는 이름으로 개발을 시작했다. 이름 충돌을 피해 2007년 "cgroups"로 개명되었고, 2008년 Linux 2.6.24에 병합됐다.
cgroups의 역할은 자원 제한이다. Namespaces가 "무엇을 볼 수 있는가"를 통제한다면, cgroups는 **"얼마나 쓸 수 있는가"**를 통제한다.
2008년, IBM 엔지니어 Daniel Lezcano와 프랑스 개발자 Stéphane Graber 등이 Namespaces와 cgroups를 결합한 LXC(Linux Containers)를 만들었다. 최초의 완전한 Linux 컨테이너 구현체였다.
LXC는 기술적으로 훌륭했지만 대중화에는 실패했다. 이유:
기술은 준비됐지만, 포장이 부족했다. 바로 이 빈자리를 Docker가 채웠다.
2013년 3월, PyCon US 컨퍼런스. PaaS 스타트업 dotCloud의 창업자 Solomon Hykes가 라이트닝 토크(5분 발표)에 올랐다. 제목은 "The future of Linux Containers".
Hykes는 자사의 내부 도구를 시연했다. 컨테이너 이미지를 만들고, 공유하고, 실행하는 과정이 놀라울 정도로 간단했다. 5분 발표가 끝나자 개발자들은 열광했다.
dotCloud은 곧 사명을 **Docker Inc.**로 바꿨다. PaaS 사업보다 이 내부 도구의 가능성이 훨씬 크다는 것을 깨달은 것이다.
기술적으로 초기 Docker는 LXC 위에서 동작했다. 커널 기술은 같았다. 그런데 왜 Docker만 성공했을까?
Docker의 혁신은 커널 기술이 아니라 개발자 경험(Developer Experience) 이었다. 세 가지 핵심 요소:
1. Dockerfile — 환경을 코드로
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
이 6줄이 "Python 3.11 환경에 의존성을 설치하고 앱을 실행하는 컨테이너"를 완전히 정의한다. 누구든 이 파일을 가지고 있으면 동일한 환경을 재현할 수 있다. "내 컴퓨터에서는 되는데?"가 원천적으로 불가능해진다.
2. Docker Image — 불변의 실행 단위
Dockerfile을 빌드하면 Docker 이미지가 만들어진다. 이미지는 불변(immutable) 이다. 한번 만들어지면 변하지 않는다. 이 동일한 이미지를 개발 환경, 테스트 환경, 운영 환경에서 실행하면 결과가 100% 같다.
3. Docker Hub — 이미지의 GitHub
이미지를 공유하는 중앙 저장소. docker pull nginx로 공식 Nginx 이미지를 받고, docker push my-app으로 내 앱을 공유할 수 있다. 이것이 생태계를 만들었다. 2026년 현재 Docker Hub에는 수백만 개의 이미지가 등록되어 있다.
이전 글에서 EC2가 하이퍼바이저로 물리 서버를 나눈다고 설명했다. 컨테이너는 같은 "격리"를 훨씬 가벼운 방식으로 달성한다.
핵심 차이: VM은 각각 자체 OS를 포함하고, 컨테이너는 호스트의 커널을 공유한다.
VM에서 App A와 App B를 실행하면 Ubuntu 같은 Guest OS가 2카피 필요하다. 각 OS는 1~10GB이고, 부팅에 수십 초가 걸린다. 컨테이너에서는 Guest OS가 없다. App A, B, C가 모두 호스트의 Linux 커널을 직접 공유하되, Namespaces와 cgroups로 서로 격리된다.
IBM Research의 Wes Felter 등은 2014년 논문 "An Updated Performance Comparison of Virtual Machines and Linux Containers"에서 VM과 컨테이너의 성능을 엄밀하게 비교했다.
이 논문의 결론: 컨테이너의 CPU·메모리 성능은 네이티브(직접 실행)와 거의 동일하다. VM은 하이퍼바이저를 거치면서 성능 손실이 발생하지만, 컨테이너는 커널을 직접 사용하므로 오버헤드가 거의 없다.
Docker 이미지는 단일 파일이 아니라 레이어(layer)의 스택이다. 이것이 컨테이너를 효율적으로 만드는 핵심 메커니즘이다.
레이어 시스템의 장점:
python:3.11-slim 베이스를 쓴다면, 이 레이어는 한 번만 저장되고 10개 컨테이너가 공유한다Docker 이전의 신규 개발자 온보딩:
Docker 이후:
git clone repo && docker compose up
이 한 줄로 모든 의존성(DB, 캐시, 메시지 큐)이 설정된 개발 환경이 수 분 만에 뜬다. 첫 커밋까지 30분.
Netflix는 이전 글에서 다뤘듯이 7년에 걸쳐 AWS로 마이그레이션했다. 이 과정에서 모놀리식 아키텍처를 수백 개의 마이크로서비스로 분해했고, 각 마이크로서비스는 독립된 컨테이너로 배포된다.
왜 컨테이너가 마이크로서비스에 최적인가:
Netflix는 하루에 수천 번의 배포를 수행하며, Titus라는 자체 컨테이너 플랫폼으로 수십만 개의 컨테이너를 관리한다.
Spotify는 2014년부터 컨테이너를 채택했다. 2026년 현재 약 1,800개의 마이크로서비스가 컨테이너로 운영된다. 각 팀(Squad)이 독립적으로 서비스를 개발·배포할 수 있는 것은 컨테이너의 격리 덕분이다.
코드가 커밋되면 자동으로 테스트하고 배포하는 CI/CD(지속적 통합/배포) 파이프라인에서 컨테이너는 필수가 됐다.
핵심은 빌드된 이미지가 테스트와 운영에서 동일하다는 것이다. "테스트 환경에서는 통과했는데 운영에서 실패"하는 일이 구조적으로 불가능해진다.
Dockerfile은 컨테이너 이미지를 정의하는 설계도다. 각 명령어가 하나의 이미지 레이어를 만든다.
# 1단계: 베이스 이미지 선택
FROM node:20-alpine
# 2단계: 작업 디렉토리 설정
WORKDIR /app
# 3단계: 의존성 파일 복사 (캐싱 최적화)
COPY package*.json ./
# 4단계: 의존성 설치
RUN npm ci --only=production
# 5단계: 소스 코드 복사
COPY . .
# 6단계: 포트 노출
EXPOSE 3000
# 7단계: 실행 명령
CMD ["node", "server.js"]
package.json을 먼저 복사하고 npm install을 한 뒤, 소스 코드를 복사한다. 코드만 바꿨을 때 npm install 레이어가 캐시에서 재사용되어 빌드 시간이 수 분에서 수 초로 줄어든다.# 이미지 빌드
docker build -t my-app:1.0 .
# 컨테이너 실행
docker run -d -p 3000:3000 --name web my-app:1.0
# 실행 중인 컨테이너 확인
docker ps
# 로그 확인
docker logs web
# 컨테이너 안으로 들어가기
docker exec -it web /bin/sh
# 이미지 레지스트리에 푸시
docker push my-app:1.0
실제 애플리케이션은 웹 서버 + 데이터베이스 + 캐시처럼 여러 서비스로 구성된다. Docker Compose는 이들을 한 파일로 정의하고 한 명령으로 실행한다.
# docker-compose.yml
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
- redis
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
docker compose up -d # 세 서비스 모두 시작
docker compose down # 모두 중지 및 정리
docker-compose.yml을 리포지토리에 포함시키면, 새 팀원이 docker compose up 한 줄로 전체 개발 환경을 구축할 수 있다. 환경 설정 문서 10페이지가 2줄짜리 명령어로 대체된다.Docker가 성공하면서 우려도 생겼다. 컨테이너 기술이 Docker라는 한 회사에 종속되는 것 아닌가? 2015년, Docker Inc., Google, CoreOS, AWS, Microsoft 등이 OCI(Open Container Initiative)를 설립하고 두 가지 표준을 정의했다:
덕분에 Docker가 아닌 다른 런타임(containerd, CRI-O, Podman)도 동일한 이미지를 실행할 수 있게 됐다. 컨테이너가 Docker의 제품에서 산업 표준으로 전환된 것이다.
Docker의 핵심 런타임 부분은 containerd라는 독립 프로젝트로 분리됐다. CNCF(Cloud Native Computing Foundation) 졸업 프로젝트로, 현재 대부분의 쿠버네티스 클러스터는 Docker 대신 containerd를 직접 사용한다.
CRI-O는 Red Hat이 개발한 경량 컨테이너 런타임으로, 쿠버네티스를 위해 특별히 설계됐다.
컨테이너 하나를 실행하는 것은 쉽다. 하지만 수백, 수천 개의 컨테이너를 관리해야 한다면?
이 문제를 해결하는 것이 컨테이너 오케스트레이션이고, 그 사실상의 표준이 쿠버네티스(Kubernetes, K8s)다. Google이 내부 시스템 Borg의 경험을 바탕으로 2014년에 오픈소스로 공개했다.
컨테이너의 최신 진화는 서버리스 컨테이너다. 서버 관리는커녕 컨테이너 관리조차 신경 쓸 필요 없다.
개발자는 Docker 이미지만 만들면 된다. 나머지(서버 프로비저닝, 스케일링, 로드밸런싱)는 클라우드가 알아서 한다.
컨테이너는 VM보다 격리 수준이 약하다. 커널을 공유하기 때문이다. 실전에서 반드시 지켜야 할 보안 원칙:
USER nonroot를 Dockerfile에 명시ubuntu:latest 대신 alpine이나 distroless 사용. 공격 표면 최소화COPY . .로 소스 전체를 복사할 때 .env 파일이나 .git 디렉토리까지 포함되는 경우. .dockerignore 파일을 반드시 작성하여 민감한 파일을 제외하라.프로덕션 이미지는 가능한 한 작아야 한다. 작은 이미지 = 빠른 배포 + 작은 공격 표면 + 적은 스토리지 비용.
| 베이스 이미지 | 크기 | 적합한 경우 |
|---|---|---|
| ubuntu:22.04 | ~77MB | 디버깅용, 개발 환경 |
| python:3.11-slim | ~120MB | Python 앱 (데비안 기반) |
| node:20-alpine | ~50MB | Node.js 앱 |
| gcr.io/distroless/base | ~20MB | 프로덕션 최적화 |
| scratch | 0MB | Go 바이너리 같은 단일 실행 파일 |
멀티스테이지 빌드를 사용하면 빌드 도구는 빌드 단계에서만 쓰고, 최종 이미지에는 실행 파일만 포함시킬 수 있다:
# 빌드 스테이지
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .
# 실행 스테이지 — 빌드 도구 없이 바이너리만
FROM scratch
COPY --from=builder /app/server /server
CMD ["/server"]
이 패턴으로 Go 앱의 최종 이미지를 10~20MB로 줄일 수 있다. 1GB짜리 개발 이미지가 50분의 1로 줄어드는 것이다.
컨테이너의 본질을 한 문장으로 요약하면 이렇다:
애플리케이션과 그 실행에 필요한 모든 것(라이브러리, 설정, 의존성)을 하나의 불변 패키지로 묶어, 어디서든 동일하게 실행되도록 보장하는 기술.
44년의 기술 진화를 관통하는 핵심 아이디어는 변하지 않았다 — 격리(Isolation)와 재현성(Reproducibility). chroot가 파일 시스템을 격리했고, FreeBSD Jails가 프로세스를 격리했고, cgroups가 자원을 격리했고, Docker가 이 모든 것을 개발자가 쓸 수 있는 형태로 포장했다.
컨테이너는 개발자와 운영자 사이의 합의된 계약서다. "이 이미지 안에 필요한 모든 것이 들어 있다. 이것만 실행하면 된다." 이 합의가 DevOps 문화를 가능하게 했고, 마이크로서비스를 실현 가능하게 했고, CI/CD를 신뢰할 수 있게 했다.
코어닷투데이의 AI 제품들도 이 인프라 위에 서 있다. AI 아르스 키오스크의 추론 엔진, Sharp-PINN의 물리 시뮬레이션 — 이런 복잡한 AI 워크로드가 어느 환경에서든 동일하게 동작할 수 있는 것은, 컨테이너가 "환경"이라는 변수를 제거해 주었기 때문이다.
다음 글에서는 수천 개의 컨테이너를 관리하는 쿠버네티스(Kubernetes)의 세계로 들어가 보겠다.