.github/workflows/deploy.yml은 target-tracking-service의 것을 거의 그대로 가져왔다 — 트리거 조건(branches: [main]), runs-on: ubuntu-24.04-arm(클러스터가 aarch64라 네이티브로 arm64 빌드), docker/build-push-action@v5로 :{sha}/:latest 두 태그 push하는 구조까지 동일하다. 차이는 트리거 경로뿐이다.
paths:
- 'app/**'
- 'requirements.txt'
- 'Dockerfile'
- '.github/workflows/deploy.yml'
Java 쪽은 src/, build.gradle이던 걸 Python 프로젝트 구조(app/, requirements.txt)에 맞게만 바꿔다. 이미 검증된 파이프라인 설계를 재사용하는 게 새로 설계하는 것보다 실패 지점을 줄인다는 판단.
CI를 처음 push했을 때(ce71091) DOCKERHUB_USERNAME/DOCKERHUB_TOKEN 시크릿 자체가 없어서 실패했다(시크릿은 리포 단위로 격리되어 target-tracking-service에 있어도 이 리포엔 없음). gh secret set을 사용자가 직접 실행해야 했다.
시크릿 등록 후 재실행했지만 다시 실패:
Error response from daemon: Get "https://registry-1.docker.io/v2/": unauthorized: incorrect username or password
원인 후보 두 가지: (1) DOCKERHUB_TOKEN에 계정 로그인 비밀번호를 넣었을 가능성 — Docker Hub는 2FA가 켜져 있으면 비밀번호 로그인 자체가 막히고 Access Token 사용을 권장한다. (2) DOCKERHUB_USERNAME에 이메일을 넣었을 가능성.
해결: Docker Hub → Account Settings → Personal access tokens에서 Read & Write 권한으로 새 토큰을 발급해서 다시 등록. docker/build-push-action은 이미지를 pull(Read)뿐아니라 push(Write)까지 해야 하므로 Read-only 토큰으로는 애초에 안 된다.
재등록 후 54초 만에 성공했고, Docker Hub API로 실제 latest, <sha> 두 태그가 올라간 걸 직접 확인했다.
이 과정에서 실제 Docker Hub 토큰 값은 한 번도 대화나 커맨드에 등장하지 않았다 — gh secret set NAME --repo ...는 인자 없이 실행하면 터미널에서 값을 프롬프트로 입력받고, 그 입력은 GitHub API로 바로 암호화되어 전송된다. 사용자에게 이 명령을 직접 실행하게 안내한 이유가 이거다 — AI 에이전트가 크리덴셔을 보거나 다루지 않아도 되는 경로를 우선한 것.
이 리포에서 push → Docker Hub까지가 CI의 책임 범위다. Docker Hub 이후 k3s-msa-infrastructure 리포에 이미지 참조를 반영하고 실제 클러스터에 배포하는 부분은 k3s-msa-infrastructure의 ArgoCD Image Updater가 담당한다.