이미지 파일 수동 전달에서 자동 배포까지
홈서버에 서비스를 배포할 때 처음에는 Docker 이미지를 로컬에서 빌드하고 tar 파일로 저장해 NAS에 올렸습니다. NAS에서 이미지를 읽고 컨테이너를 다시 만드는 방식입니다.
서비스가 적을 때는 쓸 수 있는 방법이었지만, 홈페이지와 이력서 사이트를 수정할 때마다 같은 전달 과정을 반복해야 했습니다. 먼저 이미지 전달을 Registry로 바꾸고, 이후 빌드와 컨테이너 갱신도 자동화했습니다.
Registry를 통한 이미지 전달
시놀로지에 개인 Registry를 구성했습니다. 로컬에서는 이미지를 push하고 NAS에서는 pull하도록 바꿨습니다. DSM에서 사용하던 포트와 겹치지 않게 호스트 포트를 나누고, 인증 파일과 이미지 저장 공간을 따로 관리했습니다.
| 단계 | tar 파일 사용 | Registry 사용 |
|---|---|---|
| 빌드 | 로컬에서 실행 | 로컬에서 실행 |
| 전달 | 이미지 tar 업로드 | Registry에 push |
| 반영 | 파일 읽기와 컨테이너 재생성 | pull 뒤 컨테이너 재생성 |
이 단계에서는 이미지를 전달하는 방법만 바뀌었습니다. 로컬 빌드와 서비스 반영은 여전히 직접 해야 했습니다.
CI 빌드와 Watchtower 배포
다음에는 GitHub Actions가 main 변경을 기준으로 이미지를 빌드해 Registry에 올리도록 했습니다. Registry 인증값은 저장소 Secrets에서 읽고 HTTPS로 연결했습니다.
NAS에서는 Watchtower가 대상 컨테이너의 이미지 변경을 확인하고 갱신하도록 구성했습니다. 이 방식은 Docker로 운영하는 홈페이지와 이력서 사이트에 적용했습니다. 정적 블로그는 별도의 Pages 배포를 유지합니다.
로컬에서 파일을 옮길 필요는 없어졌지만, CI가 이미지를 올리는 것과 NAS가 그 이미지를 사용하는 것은 다른 단계입니다. 이후 인증 오류를 확인할 때도 이 구분이 필요했습니다.
Docker 소켓 연결과 Registry 인증
10월에는 Watchtower에 연결할 Docker 인증 파일이 없어 실행에 실패했습니다. 파일 마운트를 제거하자 프로세스는 시작됐지만, 이번에는 비공개 Registry에서 이미지를 받지 못했습니다.
Docker 소켓은 NAS의 컨테이너를 확인하고 바꾸는 데 사용합니다. Registry 인증은 이미지를 다운로드할 때 필요합니다. Docker 데몬에 접근할 수 있다고 Registry에서도 인증되는 것은 아닙니다. sudo로 명령을 실행하는 것만으로 로그인 설정이 준비되지도 않습니다.
Watchtower 공식 문서도 비공개 Registry 인증을 위한 config.json 전달을 설명합니다. 다음은 두 설정의 역할을 보여주는 Compose 일부 예시입니다. 전체 실행 설정은 아닙니다.
services:
watchtower:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/docker/config.json:/config.json:ro이후에는 인증 파일과 Registry 로그인을 먼저 확인하고, 준비가 끝난 뒤 Watchtower를 재생성하는 순서로 정리했습니다. 인증 준비에 실패한 상태에서 기존 감시기부터 제거하지 않도록 했습니다.
이미지 이름과 실행 중인 컨테이너 비교
Registry를 쓰면 파일 전달은 줄어들지만 어떤 버전이 올라갔는지 확인할 정보가 필요합니다. 같은 태그를 계속 덮어쓰면 화면에서 보이는 latest만으로는 이전 이미지와 구분하기 어렵습니다. 빌드한 커밋에 대응하는 태그나 이미지 ID를 함께 남기면 비교하기 편합니다.
다음은 이미지 전달 과정을 설명하는 명령 예시입니다. registry.example.com과 이름은 실제 Registry 주소와 이미지로 바꿔야 합니다. 이 글의 자동화를 새로 설치하는 전체 명령은 아닙니다.
docker build -t registry.example.com/example/site:sample-revision .
docker push registry.example.com/example/site:sample-revision운영 측에서는 실행 중인 컨테이너가 사용한 이미지 ID를 조회할 수 있습니다.
docker inspect --format '{{.Config.Image}} {{.Image}}' example-site
docker image inspect --format '{{.Id}} {{json .RepoDigests}}' \
registry.example.com/example/site:sample-revision첫 번째 명령은 컨테이너 생성 당시 지정한 이미지 이름과 실제 이미지 ID를 함께 보여줍니다. 태그가 같은데 ID가 다르다면 이름만 보고 동일한 이미지라고 판단할 수 없습니다. 조회한 ID와 CI·배포 기록을 맞춘 뒤 실제 웹 응답까지 봅니다. 예시는 조회 방법을 보여주기 위한 것으로 특정 배포가 성공했다는 출력은 넣지 않았습니다.
인증 파일을 전달할 때의 차이
로컬 Docker에서 login한 환경과 NAS에서 Watchtower가 실행되는 환경은 다릅니다. Docker CLI가 쓴 인증 파일에 auths가 들어 있는지, 별도 credential helper를 참조하는지부터 봐야 합니다. helper를 참조하는 파일만 컨테이너에 전달했는데 그 helper가 없다면 같은 로그인이 재현되지 않을 수 있습니다.
또한 컨테이너를 실행한 사용자의 홈과 sudo로 실행한 Docker의 홈 설정이 다를 수 있습니다. 그래서 “서버에서 로그인했다”는 설명보다 어느 설정 파일을 어느 컨테이너에 읽기 전용으로 연결했는지가 중요했습니다. Watchtower의 Registry 인증 문서 를 기준으로 전달 경로를 확인했습니다.
| 실패 위치 | 먼저 볼 대상 |
|---|---|
| CI에서 push 실패 | Actions의 Registry 주소와 인증 설정 |
| NAS에서 pull 실패 | NAS 측 자격 정보와 Registry 접근 |
| Watchtower만 인증 실패 | 컨테이너에 전달한 파일과 helper 의존성 |
| 스캔은 성공, 교체 0건 | 대상 이미지에 실제 변경이 있는지 |
| 교체 후 화면 오류 | 앱 로그·설정·HTTP 응답 |
인증 문제를 해결하려고 모든 컨테이너를 먼저 갈아 끼울 필요는 없었습니다. 이미 실행 중인 서비스는 유지한 채 이미지 조회와 감시기 설정을 확인하고, 교체가 필요한 단계에서만 반영하도록 순서를 정리했습니다. 여기서 Watchtower는 당시 사용한 배포 구성의 기록이며 새로운 시스템의 배포 도구를 비교 선정한 글은 아닙니다.
이미지 스캔과 서비스 반영 확인
후속 로그에서는 대상 컨테이너 검사가 오류 없이 끝났지만 교체 건수는 0이었습니다. 이미지가 그대로라면 정상적인 결과일 수 있습니다. 다만 이 로그만으로 새 버전이 배포됐다고 판단할 수는 없습니다.
이미지 전달은 파일을 옮기는 방식에서 레지스트리와 Watchtower를 사용하는 구성으로 바꿨습니다. 다만 후속 로그에서 확인한 것은 검사 성공과 교체 0건까지였고, 그 로그로 새 버전 배포까지 확인할 수는 없었습니다. 다음 변경에서도 CI의 이미지 업로드, 실제 컨테이너 교체, 웹 응답을 순서대로 봐야 합니다.