개발을 하다 보면 한 번쯤 이런 말을 듣거나 하게 된다.
“제 컴퓨터에서는 잘 됐는데요?
이 말은 농담처럼 쓰이지만, 사실 꽤 중요한 문제를 담고 있다.
애플리케이션은 코드만으로 실행되지 않는다. 코드는 실행될 수 있는 조건이 맞아야 비로소 동작한다.
예를 들어 프론트엔드 프로젝트 하나를 실행한다고 해보자.
- 로컬에서는 분명히
npm run dev로 잘 실행 → 🚨 다른 사람의 컴퓨터에서는 에러 발생 - 🚨 테스트 서버에 올렸더니 빌드가 실패한다.
- 🚨 운영 서버에서는 API 요청이 되지 않는다.
이때 문제는 항상 코드 자체에만 있는 것이 아니다.
오히려 많은 경우 문제는 코드가 실행되는 환경에 있다.
1. 애플리케이션은 코드만으로 실행되지 않는다
우리는 보통 “프로젝트를 실행한다”고 말할 때 코드를 떠올린다.
하지만 실제로 애플리케이션이 실행되기 위해서는 코드 외에도 여러 조건이 필요하다.
예를 들면 다음과 같다.
- Node.js 버전
- Java 버전
- 운영체제 환경
- 필요한 OS 패키지
- 의존성 설치 상태
- 환경변수
- 실행 명령어
- 포트 설정
- 파일 경로
- 데이터베이스 연결 정보😦 예를 들어 이런 상황들이 있을 수 있다.
- 내 컴퓨터에는 Node 20이 설치되어 있는데, 서버에는 Node 16이 설치되어 있는 상황
- 내 로컬에는 필요한 패키지가 이미 깔려 있는데, 서버에는 없는 상황
- 내 환경에는
.env파일이 있는데, 배포 환경에는 환경변수가 누락되어 있는 상황- 내 컴퓨터에서는
localhost:3000으로 잘 붙었는데, 컨테이너나 서버 환경에서는 그 주소가 전혀 다른 의미인 상황
결국 애플리케이션은 단순히 코드 덩어리가 아니다.
애플리케이션은 코드와 실행 조건이 함께 맞아야 동작하는 시스템이다.
내 컴퓨터의 실행 조건에서는 문제가 없었지만, 다른 환경의 실행 조건에서는 문제가 발생했다.
=> 즉, 문제의 본질은 "코드가 맞냐 틀리냐"가 아니라, 실행 환경이 동일하게 재현되었는가에 있다.
- 개발자의 로컬 환경, 동료의 로컬 환경, 테스트 서버, 운영 서버는 모두 다르다.
- 운영체제도 다를 수 있고, 설치된 런타임 버전도 다를 수 있고, 네트워크 구조도 다를 수 있다.
이런 상황을 해결하는 지점에서 Docker가 등장한다.
2. Docker는 코드를 포장하는 도구가 아니다.
Docker를 처음 배우게 되면 보통 이렇게 설명한다.
Docker는 컨테이너 기반 가상화 기술이다.
틀린 말은 아니지만, 처음 Docker를 이해하는 입장에서는 조금 추상적이다. 이 설명만 들으면 Docker가 왜 필요한지 잘 와닿지 않는다.
Docker는 코드를 포장하는 도구가 아니라, 애플리케이션의 실행 환경을 포장하는 도구다.
이런 설명이 좀 더 Docker를 이해하는 데 도움이 될 것 같다.
✅ Docker는 애플리케이션을 실행하기 위해 필요한 조건들을 하나의 이미지로 묶는다.
- 어떤 OS 계층을 기반으로 할지
- 어떤 런타임을 사용할지
- 어떤 파일을 포함할지
- 어떤 의존성을 설치할지
- 어떤 환경변수를 사용할지
- 어떤 포트를 사용할지
- 컨테이너가 시작될 때 어떤 명령을 실행할지이렇게 실행 조건을 이미지로 만들어두면,
같은 이미지를 사용하는 한 어디서 실행하더라도 최대한 동일한 환경을 재현할 수 있다.
3. Docker는 실행 환경을 "이미지"라는 단위로 고정한다.
그렇다면 Docker는 실행 환경을 어떤 단위로 포장할까?
Docker에서는 이 역할을 Image가 담당한다.
Image는 애플리케이션을 실행하기 위해 필요한 파일, 런타임, 설정, 실행 명령 등을 포함한 정적인 실행 패키지에 가깝다.
- Node.js 런타임
- package.json
- package-lock.json
- 설치된 node_modules
- 빌드된 dist 파일
- Nginx 같은 정적 파일 서버
- 컨테이너 시작 시 실행할 명령- Java 런타임
- 빌드된 jar 파일
- application 설정
- 실행 명령
- 서버가 사용할 포트=> 이런 실행 조건들을 Dockerfile에 정의하고, 그 결과물을 Image라는 단위로 만든다.
- Image는 "이 애플리케이션을 실행하려면 이런 환경이 필요하다"는 내용을 고정해둔 실행 패키지다.
- 이렇게 만들어진 Image는 여러 환경에서 동일하게 실행될 수 있다.
- 내 로컬 환경
- 동료의 컴퓨터
- 테스트 서버
- 운영 서버
- 클라우드 인스턴스물론 완전히 100% 모든 차이를 없애는 것은 아니다.
- 네트워크, CPU 아키텍처, 외부 DB, 운영체제 수준의 설정처럼 이미지 밖의 요소들도 영향을 줄 수 있다.
- 하지만 적어도 애플리케이션 실행에 필요한 주요 조건들을 이미지 안에 명시적으로 고정할 수 있다.
Docker Image는 코드를 담는 압축 파일이 아니라,
코드가 실행될 수 있는 조건을 함께 담은 실행 환경의 스냅샷에 가깝다.
4. Docker가 고정하는 것과 고정하지 못하는 것
Docker Image를 사용하면 애플리케이션 실행에 필요한 많은 조건을 고정할 수 있다.
하지만 여기서 중요한 점이 있다.
Docker는 실행 환경의 모든 차이를 없애는 도구가 아니다.
애플리케이션이 실행되기 위한 주요 조건을 명시적으로 고정하는 도구에 가깝다.
Docker Image 안에 포함될 수 있는 것들은 비교적 명확하다.
- 런타임 버전
- OS 파일 시스템 계층
- 애플리케이션 소스 또는 빌드 결과물
- 설치된 패키지와 의존성
- 기본 실행 명령
- 컨테이너 내부 파일 경로
- 애플리케이션 실행에 필요한 기본 설정예를 들어 Node.js 애플리케이션이라면
Node 버전, package-lock.json 기준의 의존성, 빌드 결과물, 실행 명령을 포함할 수 있다.
Java 백엔드라면 Java 런타임, 빌드된 jar 파일, 실행 명령을 이미지 안에 포함할 수 있다.
이렇게 하면 적어도 애플리케이션이 실행되기 위한 핵심 조건을 사람의 기억이나 문서에만 의존하지 않아도 된다.
- 외부 데이터베이스 상태
- 외부 API 응답
- 네트워크 구성
- 호스트 서버의 CPU 아키텍처
- 호스트의 디스크, 메모리, 권한 설정
- 실행 시점에 주입되는 환경변수
- 컨테이너 외부에 마운트되는 볼륨 데이터즉, Docker를 사용한다고 해서 모든 환경 차이가 사라지는 것은 아니다.
예를 들어 같은 이미지를 사용하더라도 운영 서버의 환경변수가 잘못되어 있으면 애플리케이션은 실패할 수 있다. DB 주소가 다르거나, 포트가 막혀 있거나, 외부 API가 응답하지 않으면 문제는 여전히 발생한다.
- Docker는 애플리케이션 실행에 필요한 조건 중 이미지 안에 넣을 수 있는 것들을 고정한다.
- 하지만 네트워크, 외부 DB, 환경변수, 서버 리소스처럼 이미지 밖에서 결정되는 요소들은 여전히 별도로 관리해야 한다.
그래서 Docker를 제대로 이해하려면
"Docker를 쓰면 무조건 어디서든 똑같이 된다"가 아니라,
"무엇을 이미지 안에 고정하고, 무엇을 이미지 밖에서 관리해야 하는가"를 구분할 수 있어야 한다.
Docker의 장점은 모든 문제를 없애는 데 있는 것이 아니라,
실행 환경의 불확실성을 줄이고 관리 가능한 형태로 드러내는 데 있다.
5. Docker가 줄여주는 비용과 새로 생기는 비용
- Docker를 사용하면 실행 환경을 맞추는 비용이 줄어든다.
- 새로운 개발자가 프로젝트를 실행할 때마다 Node 버전, Java 버전, DB 설치, Redis 설치, 환경변수, 포트 설정을 직접 맞추는 것은 생각보다 큰 비용이다.
- 특히 프론트엔드, 백엔드, DB, Worker처럼 여러 서비스가 함께 동작하는 프로젝트라면 로컬 환경을 맞추는 것만으로도 시간이 많이 든다.
=> Docker는 이런 비용을 줄여준다.
- 개발 환경 세팅 비용
- 팀원 간 실행 환경 차이
- 테스트 서버와 운영 서버의 환경 차이
- 배포 단위의 모호함
- 실행 조건을 문서로만 관리하는 불안정성하지만 Docker가 모든 복잡도를 없애는 것은 아니다.
Docker를 쓰기 시작하면 새로 이해해야 할 개념도 생긴다.
- Image와 Container의 차이
- Dockerfile 작성 방식
- Build cache
- Volume
- Port mapping
- Docker network
- 환경변수 주입 시점
- 컨테이너 로그와 디버깅
Docker는 실행 환경의 복잡도를 없애는 도구라기보다,
그 복잡도를 더 명시적으로 관리할 수 있게 만드는 도구에 가깝다.
- 즉, Docker를 도입하면 "사람이 직접 환경을 맞추는 비용"은 줄어든다.
- 대신 "실행 환경을 코드와 설정으로 설계하는 비용"이 생긴다.
하지만 프로젝트가 커지고, 여러 사람이 함께 개발하고,
여러 서버에 배포해야 하는 상황에서는 후자의 방식이 더 안정적이다.사람의 기억과 문서에 의존하는 것보다,
실행 조건을Dockerfile과Image로 고정하는 편이 재현성이 높기 때문이다.