Docker — '제 컴퓨터에선 됐는데요'를 끝내는 법
개발자 사이의 오래된 농담이 있다. "제 컴퓨터에선 되는데요." 분명 내 노트북에선 멀쩡히 도는 앱이, 동료 컴퓨터나 서버에 올리면 안 된다. 이유는 거의 늘 같다 — 환경이 다르기 때문이다. 자바 버전이 다르고, 깔린 라이브러리가 다르고, OS 설정이 다르다. 앱은 그대로인데 그 앱이 딛고 선 바닥이 제각각이라 결과가 달라진다.
Docker는 이걸 "앱만 옮기지 말고, 앱이 딛고 선 바닥까지 통째로 옮기자"로 푼다. 앱 코드뿐 아니라 그게 필요로 하는 런타임·라이브러리·설정을 하나의 상자(컨테이너)에 함께 담아, 그 상자를 어디에 올리든 안의 환경은 똑같게 만든다. 내 컴퓨터에서 돌던 상자가, 서버에서도 글자 그대로 같은 상자로 돈다. "환경이 다르다"는 변수 자체를 없애는 것이다.
Dockerfile 은 "어떻게 만들지" 적은 레시피, 이미지 는 그 레시피로 구워낸 변하지 않는 틀, 컨테이너 는 그 틀을 실제로 띄운 실행 중인 인스턴스다. 이미지 하나로 컨테이너를 여러 개 띄울 수 있다 — 붕어빵 틀과 붕어빵의 관계다.
1. 환경 차이가 만드는 고통
왜 앱만 옮기면 안 되는지, 환경 차이가 실제로 어떤 문제를 일으키는지 보자. 이걸 알아야 "환경째 옮긴다"는 발상이 왜 강력한지 이해된다.
- 1의존성 버전이 어긋난다
내 PC는 Node 20, 서버는 Node 16 — 같은 코드가 깨진다.
앱은 자기 혼자 도는 게 아니라 수많은 라이브러리·런타임 위에서 돈다. 그 버전 조합이 조금만 달라도 되던 게 안 된다. "내 PC에선 됐다"의 진짜 범인이다.
- 2설치 절차가 문서로만 전해진다
'이거 깔고 저거 설정하고…' 위키를 따라 손으로 세팅.
새 서버를 셋업할 때마다 사람이 문서를 보고 손으로 깐다. 빠뜨리거나 순서가 틀리면 미묘하게 다른 환경이 된다. Docker는 이 절차를 코드(Dockerfile)로 고정해 매번 똑같이 재현한다.
- 3여러 앱이 한 서버에서 충돌한다
A는 라이브러리 v1, B는 v2가 필요한데 같은 서버다.
한 서버에 여러 앱을 올리면 서로의 의존성이 부딪힌다. 컨테이너는 각자 격리된 자기 환경을 들고 있어, 한 서버 위에서 충돌 없이 공존한다.
세 고통의 뿌리는 "앱과 환경이 분리돼 있어, 옮길 때 환경이 따라오지 않는다"는 것이다. 컨테이너는 둘을 한 덩어리로 묶어 이 분리를 없앤다.
2. 레시피에서 실행까지의 흐름
Docker를 쓰는 일은 "레시피를 적고 → 틀을 굽고 → 띄운다"의 세 단계다. 각 단계가 무엇을 만들고 다음으로 넘기는지 따라가자.
베이스·복사·설치·실행 명령을 기술
레시피대로 이미지를 굽는다
변하지 않는 실행 틀 완성
이미지로 컨테이너를 띄움
격리된 환경에서 앱이 동작
- 1Dockerfile — 환경을 코드로 적기
베이스 이미지 위에 복사·설치·실행 단계를 쌓는다.
FROM node:20으로 바닥을 깔고, 코드를 복사하고, 의존성을 설치하고, 실행 명령을 정한다. 사람이 손으로 하던 셋업을 한 파일에 박제한 것이다. - 2이미지 — 레이어가 쌓인 불변의 틀
각 명령이 한 겹(레이어)을 만들어 차곡차곡 쌓인다.
이미지는 한 번 구우면 바뀌지 않는다. 그래서 "내가 만든 이미지"와 "서버에 올라간 이미지"가 같음을 보장할 수 있다. 어디서 띄워도 같은 틀이다.
- 3컨테이너 — 틀을 띄운 실행 인스턴스
같은 이미지로 컨테이너를 여러 개 띄울 수 있다.
컨테이너는 이미지를 실행한 상태다. 격리돼 돌고, 끄면 사라진다. 부하가 오르면 같은 이미지로 컨테이너를 더 띄워 수평 확장한다 — MSA·오케스트레이션의 토대다.
이미지는 명령마다 레이어로 쌓이고, 바뀌지 않은 레이어는 캐시돼 재사용된다. 그래서 코드만 고쳐 다시 빌드하면, 의존성 설치 같은 앞 단계는 캐시로 건너뛰고 바뀐 부분만 다시 만든다. Dockerfile에서 잘 안 바뀌는 것(의존성)을 위에, 자주 바뀌는 것(코드)을 아래 에 두면 캐시가 잘 들어 빌드가 빨라진다.
3. 가상머신과 무엇이 다른가
"환경을 통째로 격리한다"면 가상머신(VM)도 하지 않나? 맞다. 하지만 격리하는 층위가 다르다. 이 차이가 컨테이너가 가볍고 빠른 이유를 설명한다.
- ·OS 커널까지 통째로 가상화 — 게스트 OS가 통째로 돈다
- ·무겁고 부팅이 느림 (수십 초~분)
- ·격리가 강함 — OS 자체가 분리됨
- ·한 호스트에 띄울 수 있는 수가 적다
- ·호스트 OS 커널을 공유, 프로세스 수준으로 격리
- ·가볍고 시작이 빠름 (초~밀리초)
- ·격리는 VM보다 약하지만 대부분 충분
- ·한 호스트에 수십~수백 개도 가능
컨테이너는 호스트 커널을 공유하기에 가볍지만, 그만큼 격리는 VM보다 얕다. 또 컨테이너는 끄면 안의 데이터가 사라지므로(상태가 없음), DB 데이터처럼 보존할 것은 볼륨으로 컨테이너 밖에 둬야 한다. "컨테이너에 다 담으면 된다"가 아니라, 상태 있는 것과 없는 것을 가르는 설계가 필요하다.
정리하면 Docker는 "앱이 아니라 앱+환경을 통째로 옮긴다"로 환경 차이를 없앤다. 레시피(Dockerfile)로 셋업을 코드화하고, 불변의 이미지로 재현성을 얻고, 격리된 컨테이너로 충돌 없이 띄운다. VM보다 가볍게 격리하는 대신 커널을 공유하고, 상태는 밖으로 빼야 한다는 전제를 안고 간다. 이 "어디서나 똑같이 도는 상자"가 CI/CD와 오케스트레이션이 기댈 토대가 된다.
한 장 요약
- 1문제
앱만 옮기면 환경 차이로 '내 PC에선 됐는데요'가 난다.
- 2발상
앱+런타임+라이브러리+설정을 한 상자(컨테이너)에 담아 환경째 옮긴다.
- 3세 단어
Dockerfile(레시피) → 이미지(불변의 틀) → 컨테이너(실행 인스턴스).
- 4재현성
이미지는 한 번 구우면 안 바뀌어, 내 것과 서버 것이 같음을 보장.
- 5VM과 차이
커널을 공유해 프로세스 수준으로 격리 — 가볍고 빠르지만 격리는 더 얕다.
- 6전제
컨테이너는 상태가 없다 — DB 데이터 등 보존할 것은 볼륨으로 밖에 둔다.