Skip to content

feat: container build and k3s manifests for public hosting - #21

Merged
MrGuato merged 1 commit into
mainfrom
feat/cluster-hosting
Oct 16, 2026
Merged

MrGuato merged 1 commit into
mainfrom
feat/cluster-hosting

Conversation

@MrGuato

@MrGuato MrGuato commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Summary

Makes the site deployable to the k3s homelab as a container behind a public Cloudflare Tunnel, without touching the GitHub Pages deploy.

  • astro.config.mjs reads ASTRO_BASE (default /gitops-notes) and ASTRO_SITE (default https://mrguato.github.io), so the Pages build is unchanged and the container builds with / and the cluster hostname.
  • The theme script moves from two inline blocks in the layout to public/theme.js, and small hoisted scripts are no longer inlined, so the container can send script-src 'self'.
  • src/pages/404.astro gives nginx a real 404 page. GitHub Pages serves it too.
  • Dockerfile: node:20-alpine builds dist once on the build platform, nginxinc/nginx-unprivileged:1.31-alpine serves it on 8080 as uid 101. nginx.conf is static only, GET and HEAD only, gzip, immutable cache for /_astro/, server_tokens off, nosniff, referrer policy, HSTS, frame denial and a strict CSP.
  • .github/workflows/image.yml builds linux/amd64 and linux/arm64 and pushes ghcr.io/mrguato/gitops-notes:sha-<short> plus latest on main with GITHUB_TOKEN. Pull requests build without pushing. deploy.yml is untouched.
  • deploy/k8s/ mirrors the pi-cluster layout: restricted namespace, two nginx replicas with the full restricted security context and emptyDir mounts for the paths nginx writes, ClusterIP service, a NetworkPolicy that allows ingress only from cloudflared and egress only to DNS, disruption budgets, and a cloudflared template that reads the SOPS-encrypted cloudflared-tunnel Secret. The README there lists the pi-cluster and Cloudflare dashboard steps.
  • README.md documents both targets and why the hostname is public.

Verification

  • npm run build with defaults: links under /gitops-notes/, theme.js and 404.html emitted, no inline scripts.
  • ASTRO_BASE=/ ASTRO_SITE=https://notes.deleontech.net npm run build: root-relative links, no inline scripts, no /gitops-notes paths.
  • Every manifest and the workflow parse as YAML; selectors, namespaces, ports and security contexts were cross-checked by script.
  • Not run locally: docker build and kubectl apply --dry-run=client, since neither tool is installed on this machine. The image workflow runs on this pull request, so its check exercises the Dockerfile for both architectures.

Assumptions

  • Public hostname notes.deleontech.net. To use another, change it in the staging secret and set the CLUSTER_SITE_URL repository variable.
  • The Deployment pins sha-0000000 as a placeholder. Replace it with the tag the image workflow prints after this merges.

Make the Astro base path and site URL configurable through ASTRO_BASE and
ASTRO_SITE so the GitHub Pages build is unchanged while the container build
serves from the root of the cluster hostname. Move the theme script out of
the layout into public/theme.js and stop inlining small scripts so the
container can send a Content-Security-Policy with no inline scripts. Add a
404 page for nginx to serve.

Add a multi-stage Dockerfile on nginx-unprivileged with a hardened static
config, a multi-arch image workflow that pushes to ghcr.io, and manifests
under deploy/k8s that follow the pi-cluster layout: restricted namespace,
two locked-down nginx replicas, NetworkPolicy isolation, disruption budgets
and a per-namespace cloudflared template. The hostname is public with no
Access policy on purpose; the deploy README covers the tunnel, SOPS and
Cloudflare edge steps, and README.md documents both targets.
@MrGuato
MrGuato merged commit f0ce8ff into main Oct 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant