feat(deploy): self-hosted CI/CD + dedicated edge-proxy topology

Refactor the site container from a TLS-owning, port-binding proxy into an
internal plaintext static server that sits behind a dedicated edge reverse
proxy, and add an automated build/deploy pipeline.

- Caddyfile: single :80 listener (auto_https off), Host-matcher routing for
  apex/blog/art/code with /_astro+/assets re-root and www->apex redirect
- Dockerfile: EXPOSE 80 only (TLS now handled by the edge proxy)
- compose.yml: pull registry image, join external 'edge' network, declare
  routing via caddy-docker-proxy labels; drop build/ports/cert volumes
- compose.dev.yml: local container preview on :8080
- .forgejo/workflows/deploy.yml: push-to-main -> desktop runner builds, pushes
  to the Forgejo registry, SSH-deploys to the VPS
- docs + .dockerignore updated for the new topology

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Josh Bairstow 2026-06-02 15:31:16 +10:00
parent 5c9d4a99cc
commit 7ecd1b32ae
8 changed files with 261 additions and 97 deletions

View file

@ -170,9 +170,8 @@ Recommendation for a project of this size:
**Suggested stack on the VPS:**
- **Caddy** as the reverse proxy — automatic HTTPS via Let's Encrypt, dead-simple Caddyfile, handles the host-header → path-prefix routing cleanly.
- **Docker + docker compose** — the Astro static output baked into a tiny container (Caddy + the `dist/` files, or just nginx-alpine + static files); side-hustle containers added alongside under the same compose file.
- **Deployment** options:
- Build locally → push image to a registry (GHCR or Docker Hub) → SSH to VPS → `compose pull && up -d`. Fully manual, very transparent.
- Or GitHub Actions: build + push image on tag, then a webhook or SSH step triggers the VPS pull. Slightly more setup, far less ceremony per deploy. Recommended once the workflow is steady.
- **Deployment** (decided 2026-06-02 — see §6.5):
- Self-hosted **Forgejo** on the VPS (`git.joshbairstow.com`) hosts the repo + CI + a container registry; a **Forgejo Actions runner on the home desktop** builds the image and SSH-deploys to the VPS. Earlier GHCR/GitHub-Actions options were dropped in favour of self-hosting (learning value + avoids GitHub).
### 6.4 Other open questions
@ -182,6 +181,32 @@ Recommendation for a project of this size:
- **`code.` subdomain content:** ~~is this also served from this repo~~ — confirmed in-repo (2026-06-01).
- **Design system docs source:** ~~URL 404'd~~ — imported successfully (2026-06-01); see `docs/design-system/`.
### 6.5 CI/CD + multi-project topology (decided 2026-06-02)
The site is one project among several that will eventually share the VPS via
subdomains (separate repos/containers each). To keep their lifecycles
independent, infrastructure splits in two:
- **`jb-infra` repo (bootstrapped manually on the VPS):** a dedicated
**caddy-docker-proxy** edge container owns 80/443, terminates TLS for all
hosts, and routes by **Docker labels** over a shared external `edge` network;
plus **Forgejo** (`git.joshbairstow.com`) for self-hosted git + Actions + a
container registry.
- **This repo:** refactored to an *internal* plaintext static server (drops
TLS/ACME/port-binding; keeps host→path + asset re-root in `Caddyfile`), joins
the `edge` network, and declares its hostnames via `caddy` labels in
`compose.yml`. CI lives in `.forgejo/workflows/deploy.yml`.
**Flow:** push to `main` → home-desktop runner builds the image → pushes to the
Forgejo registry → SSHes to the VPS → `compose pull && up -d`. The small VPS
never builds (build offloaded to the always-on desktop). New projects plug in by
joining `edge` + adding labels — no central config edits.
Rationale: self-hosting Forgejo is a deliberate learning investment and avoids
GitHub; the label-driven edge proxy scales to many independent repos cleanly;
offloading builds keeps the VPS on the cheap tier. RAM target bumped to 2 GB (or
1 GB + swap). See `jb-infra/README.md` for the full bootstrap runbook.
## 7. Decisions log
| Date | Decision | Rationale |
@ -196,6 +221,9 @@ Recommendation for a project of this size:
| 2026-06-01 | `code.` subdomain is in-repo (third in-repo section alongside `blog.` and `art.`) | User confirmed; routing model already supports it |
| 2026-06-01 | Architecture must remain open to adding further in-repo subdomain paths later | User flagged this as a constraint; host-aware routing in §6.2 scales to N paths without rework |
| 2026-06-01 | Brand-system docs imported to `docs/design-system/` from a fresh handoff bundle | Establishes the canonical brand spec in-repo so future work can cite it |
| 2026-06-02 | Self-host git + CI via Forgejo on the VPS (`git.joshbairstow.com`); runner on the home desktop | Learning value + avoids GitHub availability issues; offloads builds from the small VPS |
| 2026-06-02 | Dedicated `caddy-docker-proxy` edge container owns TLS/routing via Docker labels; website becomes an internal server | Decouples each project's deploy; new subdomains/containers plug in with zero central edits |
| 2026-06-02 | Infra (edge proxy + Forgejo) split into a separate `jb-infra` repo, bootstrapped manually | Infra changes rarely; keeps the app repo's CI focused on the app |
## 8. Next steps