Github Container Registry(GCR)에 업로드된 새로운 이미지를 docker compose 환경으로 자동으로 배포하기 위해서는 docker compose를 구성하는 요소가 새로운 이미지의 존재를 확인할 수 있어야 합니다. 대표적으로는 webhook 기반 방법, polling 기반 방법이 있습니다.
Webhook 기반 아키텍처 또는 이와 비슷한 모종의 방식들(HTTP 요청 등)은 실시간 배포가 가능하고 리소스 효율적이지만, 홈 서버 환경에서는 약간의 설정 복잡성이 있을 수 있습니다. GCR에 새 이미지가 푸시되면 즉시 서버의 웹훅 엔드포인트로 알림을 전송하는 방식입니다.
Polling 기반 아키텍처는 GitHub API의 시간당 5,000회 요청 제한(ref2)과 업데이트 지연이라는 문제들이 있음에도 불구하고 구현이 단순하고 방화벽과 같은 걱정에서 자유로울 수 있어 토이 프로젝트, 작은 서비스에서는 더 안정적인 선택이 될 수 있습니다.
번외로 github action에서 빌드가 완료되었을 때 SSH를 이용해 서버에 접근해 image pull을 해 버리는 방법도 있습니다.
Container CD 생태계
Diun
우선 정말 간단한 애플리케이션에 있어서는 CD같이 거창한 것 대신 알림 우선 접근방식이 더 합리적입니다. Diun도 비슷한 맥락으로 알림에만 집중합니다. 프로젝트 페이지를 보면 활발히 개발되고 있는 것을 확인할 수 있습니다. 8MB 미만의 메모리 사용량으로 경량화되어 있고 15개 이상의 알림 채널을 지원합니다. 아래 소개될 Watchtower과 같이 사용하는 사람들도 있습니다.
# Diun 설정 예시
services:
diun:
image: crazymax/diun:latest
volumes:
- "./data:/data"
- "/var/run/docker.sock:/var/run/docker.sock"
environment:
- "DIUN_WATCH_WORKERS=20"
- "DIUN_NOTIF_TELEGRAM_TOKEN=YOUR_TOKEN"
restart: always
YAML
복사
하지만 저는 개발 환경의 제약 때문에 완전 자동 CD를 고민하고 있어, 요구사항에 맞지 않아 이정도만 하고 넘어갑니다.
SSH 기반 배포
두 번째로 살펴볼 것은 SSH 기반의 배포입니다. 하지만 이것도 마찬가지로 가볍게만 살피고 넘어가야 할 것 같습니다. 아무리 비밀번호가 걸려 있다고 한들 내 컴퓨터의 특정 포트를 외부로 노출시키는 것은 정말 생각보다 위험한 일이기 때문입니다. 예전에 킥보드를 개발할 때 Jetson 컴퓨터에 특히 중국 IP로부터 브루트 포스 공격을 쉴 새 없이 받았고, 그 결과 로그 파일이 잔뜩 쌓이다 못해 컴퓨터가 용량 부족을 호소하거나 네트워크 대역폭에 영향을 받아 쓰지 못할 지경에 이르렀던 경험을 실제로 해 버렸거든요. 해커가 내 컴퓨터 문턱까지 와서 포트를 더듬거리고 있는 것도 기분나쁜 일인데, 그 더듬음이 도를 지나쳐서 컴퓨터가 고장이 날 정도라면 보안 불감증인 저조차도 생각만 해도 끔찍합니다.
그렇다고 안전하게 SSH 연결을 만들자니,
Cloudflare DocsSSH with Access for Infrastructure (recommended) 이렇게 길고 복잡한 매뉴얼을 읽어야 합니다. 그래서 이 방법도 고려하지 않게 되었습니다. 어쨌든 이런 방법도 가능합니다.

- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
script: |
cd /app
echo "DOCKER_TAG=${{ github.sha }}" > .env
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
YAML
복사
Portainer
약간의 상용 도구를 찾는다면 Portainer도 살펴볼만 합니다. Portainer는 Docker 환경을 시각적으로 관리해주는 UI 도구입니다. 웹훅 기반의 자동 배포 기능이 상용 라이선스에 한해(뒤늦게 알았지만-.-) 내장되어 있습니다. Portainer에서는 웹훅 URL을 생성할 수 있고, 이 URL을 Docker Hub, GitHub Actions, GitLab CI/CD 등의 플랫폼에서 호출하여 사용하는 방식입니다.
마찬가지로 새로운 이미지가 푸시되는 등의 이벤트가 발생하면, 해당 플랫폼이 이 웹훅 URL로 알림(HTTP 요청)을 보냅니다. Portainer는 알림을 받는 즉시 연결된 컨테이너를 최신 이미지로 자동으로 재시작합니다. 직접 docker pull 명령을 실행할 필요 없이, 웹훅 알림 하나로 CD(Continuous Delivery)를 구축할 수 있습니다.
services:
...
portainer:
image: portainer/portainer-ce:lts
container_name: portainer
environment:
- TRUSTED_ORIGINS=portainer.mydomain.com,mydomain.com
ports:
- "9000:8000"
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
restart: always
volumes:
portainer_data:
YAML
복사
ce는 커뮤니티 에디션을 의미합니다. ce에서는 이 기능을 제공하지 않습니다.
Watchtower
CD의 완전 자동화를 원한다면 Watchtower(20k+ GitHub stars)가 가장 성숙한 선택입니다. Polling 방식과 webhook 방식(ref1: Webhook과 HTTP API는 기술적으로 동일한 HTTP 요청) 모두 지원합니다.
저는 쉽고 간단한 polling 방식으로 만들고자 하는 것을 구축해 보도록 하겠습니다. Watchtower 컨테이너를 docker-compose.yml 파일에 새 서비스로 추가해야 합니다. Docker 소켓을 마운트하여 Docker 데몬과 통신할 수 있는 권한을 부여받습니다. 그래야 호스트 시스템의 docker, docker compose에 명령을 내릴 수 있겠죠.
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.docker/config.json:/config.json
YAML
복사
기본적으로 Watchtower는 모든 실행 중인 컨테이너를 감시합니다. Watchtower에게 모든 컨테이너를 감시하는 대신 특정 컨테이너만 감시하도록 지시하는 것도 가능합니다(ref4).
...
command: hegemony-backend # 감시하고자 하는 컨테이너의 이름
YAML
복사
대신 업데이트를 원하는 컨테이너에 com.centurylinklabs.watchtower.enable=true 라벨을 추가하고, WATCHTOWER_LABEL_ENABLE 환경 변수를 true로 설정하는 방식도 많이 사용됩니다.
services:
...
hegemony-backend:
image: ghcr.io/impact-ps/hegemony-backend:latest
container_name: hegemony-backend
...
restart: unless-stopped
labels:
- "com.centurylinklabs.watchtower.enable=true"
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.docker/config.json:/config.json
environment:
- WATCHTOWER_LABEL_ENABLE=true
YAML
복사
Private registry를 사용하려는 경우, docker login 명령으로 인증 정보를 ~/.docker/config.json에 추가합니다. ~/.docker/config.json:/config.json를 마운팅한 이유가 바로 이것이죠. GHCR의 경우 docker login ghcr.io -u GITHUB_USERNAME과 같은 방식으로 로그인하여 추가할 수 있습니다. 이때 비밀번호로는 Personal Access Token (PAT)를 사용합니다. read:packages 권한이 필요합니다.
일반적으로는 ~/.docker/config.json에 인증 정보가 저장되나, 저의 경우에는 그렇지 않았습니다. 이런 상황에서는 환경 변수로 인증 정보를 곧바로 주입해 봅시다. github id와 pat token을 넣으면 됩니다. ~/.docker/config.json:/config.json 마운팅도 필요가 없습니다.
services:
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- REPO_USER=your_username
- REPO_PASS=your_personal_access_token
command: hegemony-backend
YAML
복사
이처럼 Watchtower는 상당히 편리하지만 이미지 빌드 등 예상치 못한 다운타임 위험은 항상 도사리고 있으므로 홈 서버나 개발 환경에만 적합한 솔루션입니다. containrrr/watchtower의 공식 GitHub 저장소는 "상업 또는 프로덕션 환경에서 Watchtower를 사용하는 것을 권장하지 않는다"고 명시적으로 경고합니다(ref3). 더욱 안정적인 환경을 원한다면 Watchtower의 README에서도 언급하듯 쿠버네티스를 선택해야 합니다.
Kubernetess
저는 굉장히 간단한 애플리케이션을 구축하고 있기 때문에 최대한 간단한 방법들을 위주로 살폈습니다. 하지만 앞서 언급했듯, 프로덕트가 커지며 조금의 다운타임이나 배포 지연을 원치 않거나, 애플리케이션이 이미 쿠버네티스 기반으로 작동한다면 ArgoCD, Flux CD 등을 고려해야 합니다. GitHub Actions에서 매니페스트 저장소를 업데이트하면 ArgoCD가 자동으로 동기화하는 패턴이 표준화되었습니다. 실제로 우리 부대도 이런 방식으로 쿠버네티스 애플리케이션을 배포하고 있습니다. Flux CD는 ArgoCD의 경량화된 대안으로, 이미지 자동 업데이트와 정책 기반 배포를 지원하기 때문에, 허들을 조금이라도 낮추고 싶다면 충분히 고려해볼 수 있을 것입니다.
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료을 보관해 두는 영역입니다.
1.
•
앞의 글에 도커 데몬에 대해 설명되어 있다. 하지만 사실 도커 데몬이 뭐냐가 이론적으로 중요하다기보다는 실제로 도커 소켓 파일과의 연관성 이야기가 앞에 오면 좋을 것 같아서 안 붙이고 있다.
from : 과거의 어떤 원자적 생각이 이 생각을 만들었는지 연결하고 설명합니다.
1.
•
앞의 글은 Github Action → 웹훅 기반의 CD에 대해 언급한다.
2.
•
앞의 글은 Github Action → 폴링 기반의 CD에 대해 언급한다.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는지 연결합니다.
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는지 연결합니다.
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되거나 이어지는지를 작성하는 영역입니다.
1.
None
ref : 생각에 참고한 자료입니다.