# WORKS Drive Docker Image Archive ## 목적 WORKS Drive는 Docker Registry HTTP API v2 backend로 직접 사용하지 않는다. 대신 stage/production 공용 Docker 이미지를 `docker save` 결과물로 내보내고, zstd 압축 archive와 검증 파일을 WORKS Shared Drive에 보관하는 CLI 기반 이미지 산출물 저장소로 사용한다. 이 방식은 다음 상황을 목표로 한다. - 공용 Registry 없이 WORKS Drive 접근 권한만으로 이미지 산출물 보관 - 작은 규모의 stage/production 배포 이미지 이관 - `docker load` 기반 오프라인 배포 Gitea Actions의 shared image publish workflow는 `baron_sso/:` 형태의 로컬 이미지를 빌드한 뒤 WORKS Drive archive로 업로드한다. Harbor registry login/push/pull은 이 publish 흐름의 필수 조건이 아니다. staging/production은 같은 image tag 계약을 공유하며, WORKS Drive archive를 검증한 뒤 `docker load`로 배포 대상 호스트에 적재하는 흐름으로 확장한다. ## 현재 Gitea Actions 설정 상태 2026-06-19 기준 Docker image archive 업로드 단계는 `.gitea/workflows/image_publish.yml`의 `Upload built images to WORKS Drive archive` step에서 실행된다. 이 workflow는 stage/production 공용 산출물을 만들며 `dev` branch의 commit hash 4자리로 immutable tag를 계산한다. 업로드를 실행하려면 최소한 다음 값을 등록해야 한다. - 선택 variable `WORKS_DRIVE_DOCKER_IMAGE_DIR=baron-sso` - variable `WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID` - 선택 variable `WORKS_DRIVE_DOCKER_IMAGE_PARENT_FILE_ID` - secret `WORKS_DRIVE_ACCESS_TOKEN`, 또는 variable `WORKS_DRIVE_ACCESS_TOKEN_FILE`, 또는 variable `WORKS_DRIVE_ACCESS_TOKEN_CMD`, 또는 refresh-token 방식의 secret `WORKS_DRIVE_REFRESH_TOKEN` - refresh-token 방식을 쓸 경우 secret `WORKS_DRIVE_OAUTH_CLIENT_ID`, secret `WORKS_OAUTH_CLIENT_SECRET` 서비스 계정 JWT 방식은 upload script의 fallback으로 남아 있지만 shared image publish workflow의 기본 필수 인증값은 아니다. ## WORKS Drive 토큰 운영 WORKS OAuth의 Access Token은 Developer Console 설정에 따라 1시간 또는 24시간 동안 유효하고, Refresh Token은 90일 동안 유효하다. 따라서 Gitea secret에 `WORKS_DRIVE_ACCESS_TOKEN`만 고정해 두는 방식은 publish workflow가 장시간 중단된 뒤 재실행될 때 실패할 수 있다. `image_publish.yml`은 업로드 직전에 `Resolve WORKS Drive access token` step을 실행한다. - `WORKS_DRIVE_ACCESS_TOKEN`이 있으면 이를 마스킹한 뒤 해당 workflow run 안에서만 사용한다. - `WORKS_DRIVE_ACCESS_TOKEN_FILE` 또는 `WORKS_DRIVE_ACCESS_TOKEN_CMD`가 있으면 그 결과를 같은 방식으로 사용한다. - 위 값이 없고 Gitea secret `WORKS_DRIVE_REFRESH_TOKEN`이 있으면 workflow 내부 env `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`으로 매핑한 뒤 `grant_type=refresh_token`으로 새 Access Token을 발급하고, 이후 다섯 개 이미지 업로드는 모두 이 Access Token을 공유한다. Refresh Token Rotation이 켜져 있으면 WORKS가 refresh 응답에 새 Refresh Token을 포함할 수 있다. workflow는 이 값을 로그에 노출하지 않도록 마스킹하고 `${RUNNER_TEMP}/works-drive-rotated-refresh-token`에 `0600` 권한으로 캡처한다. 다만 Gitea repository secret을 자동 갱신하려면 별도의 secret 쓰기 권한이 있는 Gitea token과 secret update 절차가 필요하므로, 기본 publish workflow는 repository secret을 직접 변경하지 않는다. 운영 권장값은 다음 중 하나다. - Refresh Token Rotation을 끄고 `WORKS_DRIVE_REFRESH_TOKEN`으로 매 run마다 Access Token만 자동 발급한다. - Rotation을 켠 경우 publish run에서 rotated refresh token 경고가 나오면 `WORKS_DRIVE_REFRESH_TOKEN` secret을 수동 갱신한다. - secret 자동 갱신이 필요하면 Gitea secret write 전용 token을 별도 설계로 추가한다. ## 저장 구조 기본 최상위 디렉터리는 다음 환경 변수로 지정한다. ```dotenv WORKS_DRIVE_DOCKER_IMAGE_DIR=baron-sso ``` 이미지는 WORKS Shared Drive에서 다음 구조로 저장한다. ```text baron-sso// ..tar.zst ..sha256 manifest..json ``` 예시: ```text baron-sso/v1.2606.ab12/ backend.v1.2606.ab12.tar.zst backend.v1.2606.ab12.sha256 manifest.v1.2606.ab12.json ``` Registry hostname과 image namespace는 저장 경로에서 제외한다. 예를 들어 `registry.example/baron_sso/backend:v1.2606.ab12`는 `baron-sso/v1.2606.ab12/backend.v1.2606.ab12.tar.zst`로 저장한다. ## Manifest `manifest..json`에는 다음 정보를 기록한다. - archive format: `docker-save-zstd` - 원본 `image_ref` - repository path - tag - Docker image id - Git commit - archive 파일명, 크기, sha256 - 서비스별 archive 정보 (`images.`) - WORKS Drive remote path - 복원 명령 예시 복원은 다음 흐름으로 처리한다. ```bash sha256sum -c backend.v1.2606.ab12.sha256 zstd -d -c backend.v1.2606.ab12.tar.zst | docker load ``` ## 업로드 CLI 로컬 컨테이너를 먼저 이미지로 commit한 뒤 업로드하려면 다음처럼 실행한다. ```bash WORKS_DRIVE_DOCKER_IMAGE_DIR=baron-sso \ WORKS_DOCKER_COMMIT_CONTAINER=baron_backend \ DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \ scripts/docker-image/upload_works_drive.sh ``` 이미지가 이미 로컬에 있으면 `WORKS_DOCKER_COMMIT_CONTAINER`를 생략한다. ```bash DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \ scripts/docker-image/upload_works_drive.sh ``` 실제 업로드에는 기존 백업 업로드와 같은 WORKS Drive 인증 변수를 사용하되, Docker image archive 대상 drive/folder는 백업 변수와 분리한다. - `WORKS_DRIVE_TARGET=sharedrive` - `WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID` - 선택: `WORKS_DRIVE_DOCKER_IMAGE_PARENT_FILE_ID` - `WORKS_DRIVE_ACCESS_TOKEN`, `WORKS_DRIVE_ACCESS_TOKEN_FILE`, `WORKS_DRIVE_ACCESS_TOKEN_CMD`, `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`, 또는 서비스 계정 OAuth 변수 업로드 전 packaging만 확인하려면 다음을 사용한다. ```bash WORKS_DRIVE_DRY_RUN=true \ DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \ scripts/docker-image/upload_works_drive.sh ``` dry-run도 실제 `docker save`, `zstd`, checksum, manifest 생성을 수행한다. WORKS Drive API 호출만 생략하므로 업로드 전 산출물 검증에 사용할 수 있다. ## 다운로드 및 복원 검증 CLI WORKS Drive에서 다음 세 파일을 같은 로컬 디렉터리로 내려받은 뒤 검증한다. ```text backend.v1.2606.ab12.tar.zst backend.v1.2606.ab12.sha256 manifest.v1.2606.ab12.json ``` checksum, manifest, zstd stream 무결성만 확인하려면 다음을 실행한다. ```bash scripts/docker-image/verify_archive.sh /path/to/downloaded/archive ``` `make` 타깃을 사용할 수도 있다. ```bash make docker-image-verify-works \ WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/path/to/downloaded/archive ``` 실제 Docker image load까지 검증하려면 다음을 실행한다. ```bash WORKS_DOCKER_VERIFY_LOAD=true \ scripts/docker-image/verify_archive.sh /path/to/downloaded/archive ``` 검증은 다음 조건을 모두 확인한다. - `..sha256` checksum 성공 - `manifest..json`의 `schema_version=1`, `format=docker-save-zstd` - manifest의 archive 파일명, sha256, size와 실제 파일 일치 - `zstd -t` 무결성 성공 - 선택적으로 `docker load` 성공 현재 repo의 `scripts/docker-image/download_works_drive.sh`는 `WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID`와 `IMAGE_TAG`를 사용해 `baron-sso//`의 archive, checksum, manifest를 내려받고 checksum/manifest 검증 후 `docker load`를 수행한다. 공용 드라이브 root 목록은 `GET /v1.0/sharedrives//files`로 조회하고, 하위 폴더 목록만 `GET /v1.0/sharedrives//files//children`를 사용한다. ```bash WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID=@2001000000547281 \ WORKS_DRIVE_ACCESS_TOKEN= \ IMAGE_TAG=v1.2606.ab12 \ scripts/docker-image/download_works_drive.sh ``` API로 다운로드할 때는 root 목록에서 `baron-sso` 폴더의 `fileId`를 찾고, 이후 대상 archive 폴더의 children을 조회해 각 파일의 `fileId`를 얻은 뒤 다음 endpoint를 호출한다. ```text GET /v1.0/sharedrives//files//download ``` 검증 결과 이 endpoint는 `302 Location`을 반환한다. `curl -L`만 사용하면 리다이렉트 대상 요청에 인증 헤더가 유지되지 않아 `UNAUTHORIZED` JSON이 파일로 저장될 수 있다. 자동화할 때는 리다이렉트 대상에도 인증 헤더를 유지하도록 처리해야 한다. `curl` 기준으로는 다음 형태를 사용한다. ```bash curl -sS -L --location-trusted \ -H "Authorization: Bearer " \ -o backend.v1.2606.ab12.tar.zst \ "https://www.worksapis.com/v1.0/sharedrives//files//download" ``` smoke 검증에는 Alpine 계열보다 운영 환경과 libc/패키지 계열 차이가 적은 Debian slim 계열을 사용한다. ```bash docker create --name baron-works-image-smoke debian:trixie-slim \ sh -c 'printf works-drive-docker-image-smoke >/works-smoke.txt' docker start -a baron-works-image-smoke WORKS_DOCKER_COMMIT_CONTAINER=baron-works-image-smoke \ DOCKER_IMAGE_REF=registry.example/baron_sso/works-smoke:works-test-ab12 \ scripts/docker-image/upload_works_drive.sh ``` 로컬 smoke 검증 예시는 다음과 같다. ```bash WORKS_DRIVE_DRY_RUN=true \ WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/tmp/baron-sso-works-verify-smoke \ DOCKER_IMAGE_REF=alpine:latest \ scripts/docker-image/upload_works_drive.sh scripts/docker-image/verify_archive.sh \ /tmp/baron-sso-works-verify-smoke/alpine/latest WORKS_DOCKER_VERIFY_LOAD=true \ scripts/docker-image/verify_archive.sh \ /tmp/baron-sso-works-verify-smoke/alpine/latest ``` ## Staging/Production 계약 Action에서 `dev` 브랜치를 checkout한 뒤 한 번만 이미지를 빌드하고 immutable `image_tag`를 계산한다. staging과 production은 같은 image_tag를 입력받아 같은 image archive를 사용한다. ```text dev branch -> publish image tag vX.YYMM. -> staging deploy -> production deploy ``` WORKS Drive archive는 Action에서 로컬로 빌드된 이미지를 `docker save`로 내보내 생성한다. 따라서 WORKS archive, staging, production은 모두 같은 immutable image tag를 기준으로 한다. ## 제한 - 이 구조는 `docker push`/`docker pull`과 호환되는 Registry backend가 아니다. - layer deduplication이 없으므로 같은 기반 이미지가 반복 저장된다. - 배포 전에는 반드시 `..sha256` 검증 후 `docker load`를 수행해야 한다. - tag 없는 image ref와 digest-only image ref는 지원하지 않는다.