현대의 프론트엔드 엔지니어링은 10년 전과는 전혀 다릅니다. 단순히 HTML과 CSS만 작성하는 것이 아닙니다. 이제 일반적인 프로젝트는 특정 Node.js 런타임, 고정된 패키지 매니저 버전, 복잡하게 얽힌 빌드 도구, 그리고 모든 것이 완벽하게 맞아떨어져야 하는 배포 파이프라인과 함께 배포됩니다. 첫 번째 npm install부터 최종 프로덕션 빌드에 이르기까지, 그 과정 어딘가에서 미세한 차이들이 발생합니다. 팀원은 Node 20을 사용하고, 당신은 Node 18을 사용합니다. 당신의 머신에 설치된 글로벌 CLI 도구가 팀원의 머신에서 누락된 의존성을 가려버리기도 합니다. 그러고 나면 아무도 듣고 싶지 않은 말이 나옵니다. "내 컴퓨터에서는 잘 되는데요."

Docker는 이러한 불확실성을 제거해주기 때문에 프론트엔드 툴킷에서 중요한 위치를 차지합니다. Docker는 애플리케이션을 실행하는 데 필요한 정확한 런타임, 시스템 라이브러리, 의존성을 하나로 묶어줍니다. Windows에서 코딩하든, macOS에서 빌드하든, Linux 클라우드 인스턴스에 배포하든 동작은 동일하게 유지됩니다.

프론트엔드 개발자가 Docker에 관심을 가져야 하는 이유

Docker가 해결하는 페인 포인트(pain points)는 추상적이지 않습니다. 매 스프린트마다 실제로 나타납니다.

버전 충돌은 시간을 낭비하게 만듭니다. 어떤 레거시 클라이언트 프로젝트는 Node 18이 필요하고, 사이드 프로젝트는 Node 20이 필요하며, 새로운 스타트업 업무는 Node 22를 요구할 수 있습니다. 컨테이너가 없다면 버전 관리자를 통해 이를 관리해야 합니다. 하지만 이는 한계가 있습니다. npm 버전의 미세한 불일치만으로도 peer dependency가 해결되는 방식이 달라질 수 있으며, 이로 인해 다른 사람의 환경에서는 잘 돌아가지만 내 환경에서는 빌드가 깨지는 상황이 발생할 수 있습니다. 프레임워크의 새로운 버전이 발표되었을 때, 피드백 루프가 두 시간 동안 재설치를 반복하는 과정이 되어서는 안 됩니다. 단 한 번의 파일 수정과 컨테이너 재시작만으로 충분해야 합니다.

글로벌 패키지 또한 소리 없는 마찰의 원인입니다. 6개월 전에 설치해둔 Angular CLI, Expo 또는 Prisma가 글로벌로 설치되어 있을 수 있습니다. 새로 합류한 개발자가 동일한 도구를 새로 설치하면 다른 버전을 받게 될 수도 있습니다. 갑자기 빌드 스크립트에서 다른 곳에서는 나타나지 않던 경고가 발생합니다. Docker는 모든 것을 프로젝트 로컬 환경으로 유지함으로써 이 문제를 해결합니다. Dockerfile에 Node 버전을 정의합니다. 의존성은 호스트 운영 체제와 격리된 컨테이너 내부에서 설치됩니다. 노트북이 macOS, Windows, Ubuntu 중 무엇이든 상관없이 애플리케이션은 매번 정확히 동일한 환경을 마주하게 됩니다.

온보딩 측면의 이점 또한 무시하기 어렵습니다. 신규 입사자에게 Homebrew 설치, nvm 별칭 설정, 글로벌 권한 수정 등을 다루는 세 페이지 분량의 README가 필요하지 않습니다. Docker를 설치하고, 저장소를 클론한 뒤, 명령어 하나만 실행하면 됩니다. 오후 내내 걸리던 설정 작업이 단 몇 분으로 줄어듭니다. 그리고 프로젝트를 전환할 때도 잔여물이 남지 않습니다. 버려진 글로벌 도구나 PATH 우선순위를 두고 싸우는 버전 관리자도 없습니다. 로컬 머신은 항상 깨끗하게 유지됩니다.

이미지와 컨테이너: 기본 개념

Docker가 처음이라면 용어가 생각보다 간단하다는 것을 알게 될 것입니다. Docker 이미지는 설계도와 같습니다. 소스 코드, Node.js 런타임, lockfile, 그리고 앱 실행에 필요한 모든 의존성을 포함합니다. 컨테이너는 그 이미지로부터 생성된 실행 인스턴스입니다. 이미지는 레시피로, 컨테이너는 실제 요리로 생각하면 쉽습니다. 하나의 레시피로 똑같은 케이크를 백 번 구울 수 있듯이, 호스트 컴퓨터에 무엇이 설치되어 있는지 걱정할 필요 없이 하나의 이미지로부터 동일한 컨테이너를 계속 생성할 수 있습니다.

실전 Dockerfile

구체적인 시작점을 살펴보겠습니다. 만약 당신이