Docker Compose, 소스 고칠 때마다 재빌드해야 할까?

문구 하나 고칠 때마다 이미지 빌드를 기다리다 재빌드를 뺐더니, 이번엔 고친 내용이 화면에 나오지 않는다. Compose watch에서 어떤 파일은 sync하고 어떤 파일은 rebuild할까?

Docker Compose, 소스 고칠 때마다 재빌드해야 할까?
세 줄 요약
  • 핫 리로드가 있는 개발 서버는 소스를 sync하고, 의존성 설치가 바뀔 때 rebuild하도록 나눌 수 있다.
  • sync는 파일을 복사하면서 컨테이너를 유지한다. 재시작이 필요한 설정 파일에는 sync+restart를 쓴다.
  • 컨테이너 안의 경로·쓰기 권한·개발 서버 명령이 맞아야 변경 사항이 화면까지 반영된다.

핫 리로드가 있다면 소스 변경마다 이미지를 만들 필요는 없다

앱이 실행 중에 소스 변경을 읽을 수 있으면 Compose Watch의 sync로 파일을 전달하면 된다. Compose의 develop 규격은 동기화, 재시작, 이미지 재생성을 서로 다른 작업으로 정의한다. 저장할 때마다 전부 rebuild하도록 설정할 필요는 없다.

변경 상황동작컨테이너에서 일어나는 일
핫 리로드 개발 서버가 읽는 소스sync파일을 복사하고 실행 중인 컨테이너를 유지한다.
시작할 때만 읽는 설정sync+restart파일을 복사한 뒤 컨테이너를 재시작한다.
설치할 의존성이나 이미지 빌드 결과가 달라지는 변경rebuild이미지를 다시 빌드하고 서비스 컨테이너를 재생성한다.

컴파일 결과를 이미지에 넣어 실행하는 앱이라면 소스도 rebuild 대상이 될 수 있다. 기준은 파일 확장자보다 실행 방식이다. sync 자체가 컴파일하거나 앱을 재시작하는 것은 아니다.

src는 복사하고 package 파일은 다시 빌드한다

다음은 기존 Node 개발 프로젝트에 적용할 구성 예시다. Dockerfile이 /app에 앱을 놓고 package.json·package-lock.json으로 의존성을 설치하며, npm run dev가 핫 리로드 개발 서버를 실행하는 구조를 전제로 한다.

services:
  web:
    build: .
    command: npm run dev
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
          initial_sync: true
        - action: rebuild
          path: ./package.json
        - action: rebuild
          path: ./package-lock.json

공식 Watch 예제처럼 소스 경로와 의존성 파일에 서로 다른 규칙을 준다. 여기서는 src만 동기화하므로 루트의 node_modules를 컨테이너에 복사하지 않는다. 프로젝트 전체를 sync한다면 node_modules를 ignore에 넣어야 한다. 호스트와 컨테이너의 OS·아키텍처가 다르면 패키지의 네이티브 코드가 호환되지 않을 수 있다.

initial_sync: true는 새 Watch 세션을 시작할 때 기존 컨테이너와 파일 상태를 맞추도록 한다. docker compose up --watch로 프로젝트를 시작하고 감시할 수 있다. docker compose watch를 따로 쓰면 앱 로그와 파일 동기화·재빌드 로그를 분리해 볼 수 있다.

설정 파일이 바뀔 때에만 다시 시작해야 한다면 해당 파일의 path와 컨테이너 내부 target을 지정한 sync+restart 규칙을 추가한다. 이때도 의존성 설치가 바뀌면 rebuild 규칙이 필요하다.

파일 도착과 앱의 다시 읽기를 나눠 확인한다

파일이 컨테이너에 도착했는데도 화면이 그대로라면, 먼저 실제 실행 명령이 개발 서버인지 봐야 한다. 예를 들어 소스는 /app/src로 복사하면서 앱은 미리 만든 dist만 실행한다면, 소스 파일 복사만으로 실행 결과가 달라지지 않는다. 이 경우는 앱의 빌드·실행 구조에 맞게 동작을 바꿔야 한다.

파일 자체가 전달되지 않으면 Watch의 실행 조건을 확인한다. 이미지에는 stat·mkdir·rmdir가 있어야 하고, 컨테이너의 USER가 target에 쓸 수 있어야 한다. path는 프로젝트 기준, ignore는 해당 watch 규칙의 path 기준이다. .dockerignore에 의해 제외되는 파일도 점검 대상이다.

Watch는 로컬 소스로 build하는 서비스를 위한 기능이다. 외부 이미지만 지정한 서비스를 자동으로 소스 개발 환경으로 바꾸지는 않는다. develop는 Compose 2.22.0부터, sync+restart는 2.23.0부터 제공되므로 오래된 CLI라면 지원 범위도 확인해야 한다.

5.6의 재빌드 병합은 규칙 선택을 대신하지 않는다

10월 2일 공개된 Compose 5.6.0에는 짧은 시간에 파일 변경이 몰릴 때 재빌드 요청을 합쳐 처리하는 수정이 포함됐다. 관련 변경은 코딩 에이전트가 여러 파일을 연달아 고치는 경우를 예로 든다. 이후 보완은 진행 중인 빌드가 새 변경보다 오래된 상태를 기준으로 삼는 경우도 처리한다.

이 개선이 모든 저장을 한 번의 빌드로 끝내거나 앱의 핫 리로드를 대신해 주는 것은 아니다. 소스 전달, 프로세스 재시작, 의존성 재설치를 나누는 규칙부터 맞춰야 한다. 그다음 파일과 실행 결과를 차례로 보면 필요 없는 재빌드와 반영되지 않은 변경을 구별할 수 있다.