ADR-013: GitHub Container Registry (GHCR) as Container Registry
Status
Accepted (2026-03-06 — shipped in sv0-platform PR #35)
Context
The CI/CD pipeline (automated dev deploys, manual prod deploys) needs a place to store built Docker images so that the deploy SSH step can pull them onto the target servers without re-building from source. The build runs in GitHub Actions; the deploy step runs on two Hetzner VPS hosts via SSH.
We need a registry that:
- Is accessible from both GitHub Actions (push) and Hetzner VPS hosts (pull)
- Requires no additional account setup beyond what we already have
- Supports private images (security)
- Can tag images by commit SHA for immutable, rollback-safe deploys
Options Considered
| Option | Notes |
|---|---|
| GHCR (GitHub Container Registry) | Built into GitHub. GITHUB_TOKEN authenticates in CI automatically. Org-scoped, private by default. No additional cost within org plan limits. |
| Docker Hub | Public registry. Free tier has pull rate limits (100 pulls/6h for anonymous). Private repos require a paid plan and a separate account/token to manage. |
| Amazon ECR | Requires an AWS account and IAM credentials. Adds AWS dependency to an otherwise AWS-free stack. 500 MB/month free, then per-GB cost. |
| Self-hosted (registry container) | Eliminates external dependency but adds operational overhead: storage, authentication, availability, garbage collection. |
Decision
Use GitHub Container Registry (GHCR) at ghcr.io/securityv0/sv0-platform/.
All Docker images are published to GHCR on every:
- Push to
main(triggers build + dev deploy) - Pull request push (builds PR-tagged images for preview instances)
Image Naming and Tagging
ghcr.io/securityv0/sv0-platform/api:<tag>
ghcr.io/securityv0/sv0-platform/ui:<tag>
| Tag | When created | Use |
|---|---|---|
sha-<commit> | Every push to main | Immutable reference for prod deploys |
latest | Every push to main | Convenience alias — do not use for prod |
pr-<number> | Every PR push | PR preview instances on dev |
Production deploys always specify a sha-<commit> tag for immutability and rollback safety.
Authentication
| Context | Auth method |
|---|---|
| GitHub Actions (push) | GITHUB_TOKEN — automatic, no PAT needed |
| GitHub Actions (pull in deploy workflow) | GITHUB_TOKEN — automatic |
| Hetzner VPS (pull via SSH deploy) | GHCR_TOKEN secret (fine-grained PAT, read:packages scope) passed to deploy scripts |
| Manual developer pull | PAT with read:packages scope: echo $PAT | docker login ghcr.io -u <user> --password-stdin |
Consequences
Positive:
- Zero additional infrastructure to manage
- Authentication is handled by
GITHUB_TOKENin CI — no PAT rotation for the build step - Images are scoped to the SecurityV0 GitHub org — access control inherits from GitHub org membership
- Image metadata and layer caching visible in GitHub Packages UI
Negative / Trade-offs:
- Pull on VPS requires a dedicated PAT (
GHCR_TOKEN) stored as a GitHub Actions secret — one token to manage per environment - If we ever leave GitHub, image migration is required
- GitHub org plan limits apply (currently generous for our scale)
Related
- Infrastructure Index — deployment overview
- Platform
docs/deploy/deployment.md— GHCR pull troubleshooting (401/403errors)