build(site): o11y-site SPA image via hanzoai/static (edge, no nginx)

The server image is headless (O11Y_WEB_ENABLED=false, /api only); this serves
the dashboard SPA at the edge so o11y.hanzo.ai/ stays 200 with the newer backend.

- Dockerfile.site: re-serves the proven in-production SPA (0.1.0 /etc/o11y/web)
  through hanzoai/static --spa (:3000), byte-identical — same pattern commerce-site
  used while its from-source build was broken. TODO: swap to a from-source node
  build stage once frontend/ pnpm build is green (mid rolldown-vite migration).
- docker-site.yaml: CI build+push ghcr.io/hanzoai/o11y-site on main.
This commit is contained in:
Hanzo AI
2026-07-03 11:41:02 -07:00
parent a6a92b4ab4
commit e910b25272
2 changed files with 95 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
name: docker-site
# Builds the o11y browser SPA image (ghcr.io/hanzoai/o11y-site) served by
# hanzoai/static at the edge. The main `docker.yaml` builds the HEADLESS server
# (ghcr.io/hanzoai/o11y, /api only); this builds the dashboard SPA that fronts it.
# See Dockerfile.site.
on:
push:
branches: [main]
tags: ["v*"]
paths:
- "Dockerfile.site"
- "frontend/**"
- ".github/workflows/docker-site.yaml"
workflow_dispatch: {}
permissions:
contents: read
packages: write
concurrency:
group: docker-site-${{ github.ref }}
cancel-in-progress: false
jobs:
build-push:
name: build & push o11y-site image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
- name: Docker metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/hanzoai/o11y-site
tags: |
type=raw,value=main,enable={{is_default_branch}}
type=sha,format=long,prefix=
type=ref,event=tag
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.site
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
cache-from: type=gha
cache-to: type=gha,mode=max
+29
View File
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1
#
# o11y-site — the o11y browser SPA served by hanzoai/static (house-native static
# plugin, scratch + Go binary), NOT nginx. The main server image (Dockerfile) is
# HEADLESS (O11Y_WEB_ENABLED=false, serves only /api/*); this image serves the
# dashboard SPA at the edge, and the ingress routes o11y.hanzo.ai / here while
# /api/* goes to the backend (universe infra/k8s/o11y/ingress.yaml).
#
# CONTENT: re-serves the PROVEN, in-production built SPA from the last bundled
# server image (0.1.0 / ad3566b), byte-identical — the same commerce-site did
# when its from-source SPA build was broken. The from-source frontend/ build is
# mid-migration (rolldown-vite/oxlint) with source damaged by a debrand
# find/replace; once `frontend/ && pnpm build` is green, replace the `content`
# stage with a `node:22` build stage (COPY frontend/ ; pnpm install
# --frozen-lockfile ; pnpm build) and COPY --from=build /src/frontend/build /srv.
# The served bytes stay identical to today's live dashboard.
# 1) The proven, in-production built SPA (o11y 0.1.0 bundled it at /etc/o11y/web).
FROM ghcr.io/hanzoai/o11y:0.1.0 AS content
# 2) Serve it with hanzoai/static. --spa rewrites unknown routes to index.html
# (SigNoz client-side routing); :3000 is the static default, and the o11y-site
# Service maps servicePort 80 -> targetPort 3000. index.html is placeholder-free
# (no [[.BaseHref]]/[[.Settings]] Go templating), assets are relative (base
# './'), and the SPA calls same-origin /api/* — so a static --spa serve is faithful.
FROM ghcr.io/hanzoai/static:0.4.1
COPY --from=content /etc/o11y/web /srv
EXPOSE 3000
ENTRYPOINT ["/static", "--root=/srv", "--spa", "--port=3000"]