Develop
Docker Compose로 Node.js 개발 환경을 구성하는 방법 — 앱과 DB를 한 번에 올리는 방법
Node.js 앱과 PostgreSQL을 docker-compose.yml 하나로 묶어서 실행하면 팀원 누구나 동일한 개발 환경을 docker compose up 한 줄로 구성할 수 있습니다. 볼륨, 핫 리로드, 환경변수 설정까지 정리했습니다.

- ·docker-compose.yml: 여러 컨테이너 서비스를 하나의 파일로 선언적으로 관리
- ·depends_on: 서비스 시작 순서를 지정하지만 앱 준비 완료는 보장하지 않음
- ·볼륨 마운트: 소스 코드를 컨테이너와 공유해 핫 리로드 구현
- ·docker compose down -v: 컨테이너와 볼륨을 함께 삭제해 DB 초기화
신규 개발자가 로컬에 DB 깔고 버전 맞추느라 반나절 동안 땀을 뻘뻘 흘리는 모습을 보고 안타까운 마음에 Docker Compose 환경을 도입했습니다. 적용 후에는 터미널에 `docker compose up` 딱 한 줄 치니까 1분 만에 앱과 DB가 유기적으로 연결되어 뜨는 걸 보고 다들 진작 도입할 걸 그랬다며 감탄하더군요. 다만 초기에 DB 컨테이너가 켜지는 도중에 앱 서버가 성급하게 연결을 시도하다가 Crash가 나며 뻗는 현상으로 고생했는데, healthcheck의 `service_healthy` 연동을 추가하고 나서야 이 빌드 타이밍 버그를 완벽하게 정복할 수 있었습니다.
1. 개발 프로세스를 단축시키는 컨테이너 오케스트레이션
왜 Node.js 개발 환경에 Docker Compose 도입이 절실할까
새로운 팀원이 합류했을 때 겪는 가장 큰 장벽 중 하나가 바로 로컬 개발 환경 셋업입니다. 각자의 PC에 PostgreSQL 설치하고, 버전 맞추고, 데이터베이스 생성하고, 환경변수 설정 파일 붙여 넣는 자잘한 작업들을 하다 보면 반나절은 우습게 흘러갑니다. 게다가 팀원 각자의 OS(Windows, macOS 등) 환경에 따라 특정 버전의 라이브러리가 꼬여서 첫 날부터 삽질을 시작하는 경우도 허다하죠. Docker Compose는 이 온보딩의 고통을 획기적으로 해결해 주는 구원투수입니다. 프로젝트 루트에 docker-compose.yml 파일 하나만 예쁘게 정의해 두면, 새로 합류한 개발자도 터미널에서 docker compose up 명령어 한 줄만 치는 것으로 백엔드 앱과 데이터베이스가 컨테이너 격리망 내에서 한 번에 기동됩니다. OS가 더러워질 걱정도 없고, 이미 개인 컴퓨터에 다른 버전의 PostgreSQL이 돌아가고 있어도 포트 충돌 없이 깔끔하게 격리되어 돌아가므로 개발 환경 표준화에 이만한 도구가 없습니다.
2. docker-compose.yml 명세 정의
서비스 정의를 통해 Node.js 앱 컨테이너와 PostgreSQL 연동하기
개발용 Compose 스펙은 보통 services 키 아래에 백엔드 애플리케이션 서비스와 데이터베이스 서비스를 나란히 정의하는 것으로 시작합니다. Node.js 앱 블록의 경우 build: . 지시어를 주어 현재 경로의 Dockerfile 기반으로 로컬 빌드를 수행하게 구성하고, 외부 포트와 컨테이너 포트를 3000:3000 형태로 브릿지 매핑합니다. 여기서 PostgreSQL 컨테이너와 안전하게 맞물려 돌아가게 하려면 environment 필드에 DB 커넥션 스트링을 적절히 주입해야 합니다. 이때 데이터베이스 파일들이 컨테이너 삭제와 함께 휘발되지 않도록 volumes에 로컬 볼륨 매핑(postgres_data:/var/lib/postgresql/data)을 달아두는 보안 조치도 절대 잊지 말아야 합니다. 또한 호스트 측의 무거운 node_modules 폴더가 컨테이너 안으로 덮어씌워져 플랫폼 빌드 충돌이 나는 문제를 막기 위해, /app/node_modules 경로를 익명 볼륨으로 따로 선언해서 격리해 두는 것이 사소하지만 아주 강력한 실무 구축 팁입니다.
services:
app:
build: .
ports:
- '3000:3000'
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:password@db:5432/mydb
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U user']
interval: 5s
retries: 5
volumes:
postgres_data:Docker Compose 의존성 구동 완료 상태를 추적하여 서비스 실행 순서 보장하기
Docker Compose를 처음 쓸 때 가장 흔히 겪는 에러가 바로 백엔드 컨테이너가 켜지자마자 데이터베이스 연결 실패 경고를 뿜으며 사망하는 현상입니다. 단순하게 depends_on: - db 설정을 달아두면 해결될 것 같지만, Nginx나 OS 단에서 DB 컨테이너 프로세스가 '시작'되는 타이밍과 DB 엔진이 실제로 포트를 열고 클라이언트를 받아들일 '준비(Ready)'가 완료되는 타이밍 사이에는 분명한 시간차가 존재하기 때문이죠. 이 타이밍 문제를 깔끔하게 해결하기 위해서는 DB 컨테이너 쪽에 pg_isready 유틸리티를 활용한 healthcheck 블록을 구성해야 하고, 백엔드 앱 컨테이너의 depends_on 아래에 condition: service_healthy 트리거 옵션을 엮어 주어야 합니다. 이렇게 세팅해 두면 PostgreSQL 엔진이 완전히 구동 완료 신호를 보낸 직후에 비로소 Node.js 서버 컨테이너가 기동되므로, 접속 재시도 헬퍼 코드를 지저분하게 작성하지 않고도 안전하게 부트스트랩 시퀀스를 보장할 수 있습니다.
3. 개발 생산성 극대화를 위한 실무 팁
Docker 볼륨 매핑으로 Node.js 실시간 핫 리로드 활성화하기
개발 환경에서 매번 코드를 한 줄 고칠 때마다 컨테이너를 빌드하고 껐다 켜야 한다면 개발 생산성은 곤두박질치고 말 것입니다. 이를 방지하고 로컬 소스 코드의 수정 사항이 컨테이너 내부로 즉각 반영되도록 하기 위해 호스트의 현재 경로와 컨테이너 내부 디렉토리를 바인딩 마운트(- .:/app)하고, 구동 명령(command)에 nodemon을 연동해 주면 실시간 핫 리로드 환경이 가볍게 세팅됩니다. 다만 윈도우(WSL2 포함) 환경이나 특정 macOS 파일 시스템 가상화 레이어 아래에서는 파일 수정 체인지 이벤트가 컨테이너 내부 커널로 제대로 전달되지 않는 버그가 간혹 발생하기도 합니다. 이럴 때는 nodemon 실행 인자에 --legacy-watch 또는 -L 플래그를 추가해 주면 폴링 방식으로 파일 변경을 주기적으로 감지하여 컨테이너 재빌드 없이도 쾌적하게 실시간 핫 리로드 환경을 누릴 수 있어 무조건 기본 템플릿에 추가해 두는 편입니다.
Docker Compose 환경 변수 유출을 방지하는 .env 파일 매핑 기법
데이터베이스의 마스터 비밀번호나 API 시크릿 키 등을 docker-compose.yml 파일 내에 상수로 하드코딩해서 깃허브 공개 저장소에 푸시하는 것은 해커들에게 '제발 털어주세요'라고 문을 열어주는 꼴입니다. Docker Compose는 실행 폴더에 위치한 .env 환경 설정 파일을 자동으로 로드하여 야믈 파일 내부에서 ${POSTGRES_PASSWORD} 형식의 템플릿 문법으로 안전하게 참조 매핑할 수 있게 지원해 줍니다. 민감한 원천 파일인 .env는 당연히 .gitignore 리스트에 박아두어 버전을 엄격히 통제하고, 대신 변수 틀만 남겨둔 .env.example 파일을 공유 저장소에 올려 신규 팀원들이 복사해 쓸 수 있도록 유도해야 합니다. 서비스 성격에 따라 데이터베이스 전용 변수 파일과 백엔드 전용 환경변수를 격리하여 선언하고 싶다면, env_file 옵션을 활용해 외부 패스워드 파일을 주입함으로써 인프라 구성의 독립성과 파일 보안 수준을 한층 업그레이드할 수 있습니다.
자주 묻는 질문
docker compose up 실행 시 DB 연결 실패 로그를 뿜으며 Node.js 앱 컨테이너가 뻗어 버립니다.+
단순히 depends_on만 명시하면 DB 컨테이너가 '구동 시작'된 직후 앱이 바로 켜지기 때문에, DB 엔진이 내부 초기화를 마치고 포트를 열기 전에 연결을 시도하여 에러가 납니다. DB 쪽에 healthcheck 검사 루틴을 기술하고, 앱의 depends_on에 condition: service_healthy 상세 속성을 엮어 구동 타이밍을 조절해야 합니다.
로컬에서 npm 패키지를 설치했는데 컨테이너 내부로 모듈 반영이 되지 않습니다.+
호스트의 node_modules와 컨테이너 내의 빌드 결과물이 꼬이지 않도록 volumes 항목에 - /app/node_modules 익명 볼륨 마운트를 분리 선언했는지 확인하세요. 이미 모듈 의존성이 깨진 상태라면 docker compose down -v로 잔여 캐시 볼륨을 시원하게 날린 뒤, docker compose up --build로 이미지를 새로 빌드하시는 게 가장 빠릅니다.
관련 글
Docker로 Jenkins 설치하고 초기 설정까지 완료하는 방법
운영체제 환경의 꼬임 없이 깔끔하게 Docker 컨테이너 위에 Jenkins를 실행하는 방법과 데이터 휘발을 막기 위한 볼륨 마운트 전략, 초기 어드민 인증을 푸는 DevOps 가이드를 제공합니다.
Nginx 리버스 프록시로 Node.js 앱을 도메인에 연결하는 방법
Node.js 앱을 직접 80 포트로 띄우는 대신 Nginx를 리버스 프록시로 앞에 세우면 도메인 연결과 HTTPS 적용이 깔끔해진다. 설정 파일 작성부터 Certbot SSL 적용까지 정리했다.
PM2로 Node.js 앱을 서버에서 관리하는 방법 — 프로세스 자동 재시작과 startup 설정
nohup으로 Node.js를 띄우다가 PM2로 전환하면 프로세스 자동 재시작, 서버 재부팅 후 자동 실행, 로그 관리까지 한 번에 해결된다. 설치부터 ecosystem.config.js 설정까지 정리했다.