action/site-publish

Composite Gitea Action that publishes a static-content website to the fritzlab k8s cluster. Supports static, hugo, and mkdocs. Content goes to a Garage S3 bucket; Traefik fronts the bucket via an ExternalName Service with cert-manager TLS.

Containerized web apps (Dockerfile-based) are NOT handled here. Use the standard image-producer chain instead: action/image-build + action/image-push + action/image-deploy. Hand-author the apps-repo manifests once (Deployment, Service, Ingress, Certificate, kustomization with images: block) and let image-deploy pin the tag on every push. See sjc001/websites/rainsounds.vino.network/ for the canonical example. site-publish errors out explicitly if site.yaml has type: docker.

Convention

Bucket name = repo name = canonical domain. Sibling hostnames (e.g. www., ipv6.) are declared as aliases: in site.yaml — the action registers each as a Garage globalAlias on the bucket and adds it to the Ingress + Certificate on every deploy. Manual edits to manifests in the apps repo are clobbered; edit site.yaml instead.

Usage

Scaffold a new site (handles repo creation + Garage bucket):

./new-site.sh --name my-site.vino.network --domain my-site.vino.network --type static

Or do it manually. site.yaml:

domain: my-site.vino.network
type: static          # static | hugo | mkdocs
# content_dir: html   # subdirectory containing content (default: repo root)
# aliases:            # additional hostnames (each gets a globalAlias on the bucket)
#   - www.my-site.vino.network
# tidy: true          # set false to skip HTML tidy
# enabled: true       # set false to decommission

.gitea/workflows/publish.yaml:

name: Publish
on:
  push:
    branches: [main]
jobs:
  publish:
    runs-on: fritzlab
    steps:
      - uses: actions/checkout@v4
      - uses: https://code.fritzlab.net/action/site-publish@v1
        with:
          token: ${{ secrets.CI_BOT_TOKEN }}
          s3-access-key: ${{ secrets.GARAGE_S3_ACCESS_KEY }}
          s3-secret-key: ${{ secrets.GARAGE_S3_SECRET_KEY }}
          garage-admin-token: ${{ secrets.GARAGE_ADMIN_TOKEN }}

DNS: subdomains of vino.network are covered by the wildcard CNAME to traefik.edge.svc…. For other zones, add an explicit CNAME:

my-site.fritzlab.net  300  IN  CNAME  traefik.edge.svc.k8s.sjc001.fritzlab.net.

Inputs

Input Required Default Description
token yes Gitea token for apps repo push
s3-access-key yes Garage ci-deploy-key access key id
s3-secret-key yes Garage ci-deploy-key secret key
s3-endpoint no http://garage.storage.svc:3900 Garage S3 endpoint
garage-admin-token only if site has aliases Garage admin API token (admin-token from garage-rpc-secret in storage ns)
garage-admin-endpoint no http://garage.storage.svc:3903 Garage admin API endpoint
username no ci-bot Gitea username

Org secrets in websites: CI_BOT_TOKEN, GARAGE_S3_ACCESS_KEY, GARAGE_S3_SECRET_KEY, GARAGE_ADMIN_TOKEN.

Tools

  • new-site.sh — create a new site: Gitea repo, Garage bucket, web hosting enabled.
  • scripts/publish.py decommission <site> — remove a site's manifests from apps repo. Bucket purge is manual.

Architecture

push to websites/<repo>
  → CI runs site-publish action
  → reads site.yaml, builds content (static copy / hugo / mkdocs), runs tidy
  → aws s3 sync → Garage bucket named after the repo
  → admin API: ensures every alias from site.yaml is a globalAlias on the bucket
  → renders manifests in fritzlab/apps from templates: ExternalName Service →
    garage.storage.svc, Traefik Ingress (canonical + aliases), cert-manager
    Certificate (canonical + aliases as SANs), kustomization
  → commits + pushes apps repo only if diff is non-empty
  → ArgoCD syncs → site live with TLS

The Ingress + Certificate are re-rendered on every deploy from site.yaml. There is no "first-deploy vs. update" branching — every deploy is idempotent.

No nginx pods, no per-site Docker images. Garage matches Host: header to bucket name (or any of its globalAliases), so every site shares a single ExternalName target.

History

  • 2026-05-06: removed type: docker support. The single docker site (rainsounds.vino.network) migrated to the image-* chain. site-publish is now scoped strictly to static-content sites.
  • 2026-05-06: renamed from fritzlab/publish-siteaction/site-publish.
S
Description
fritzlab composite action: site-publish
Readme 76 KiB
Languages
Python 68.5%
Shell 22.2%
Jinja 9.3%