Browse Kubernetes and custom resource schemas with a Go backend, one React renderer, and automated upstream schema updates.
Choose a product and version, filter resources, then follow fields into their types. Descriptions, required fields, arrays, maps, unions, validation constraints, and alternate API versions come from the original schemas. Existing resource URLs and the legacy linked, oneOf, and key parameters remain supported.
The resource in the URL selects the schema, while path records the field traversal shown in the heading. For example, /kubernetes/1.34/io.k8s.api.core.v1.PodSpec?path=Deployment.spec.template.spec displays PodSpec in its Deployment context. Each field click extends that path while linking directly to its target schema. Unnamed inline schemas use a separate JSON Pointer in pointer; version switching preserves both values. Schema navigation does not redirect to canonical URLs.
Recursive schemas can be visited three times within a traversal. Links that would visit the same schema a fourth time show a red × and “Circular reference”; unrelated fields remain available. Go identifies recursive components in the resolved schema graph, covering fields, arrays, maps, and schema variants. Only recursive schemas add a trail query containing visit counts, so reloads, shared links, browser history, and version switching retain the limit without depending on browser storage. Canonical URLs omit this context. Opening a resource directly starts a new traversal.
Requirements: Go 1.27+, Node.js 26+, and npm.
make build
PORT=18080 ./build/manifestsOpen localhost:18080. make build installs locked frontend dependencies, builds the browser and server rendering bundles, compiles Go, and prerenders documentation. Node runs during the build only. Production uses one Go process.
For live frontend development after the first build:
make devVite listens on port 5173 and proxies /api to the Go service on port 8080. Use the built Go server to verify prerendering, status codes, and security headers. Vite's development fallback serves the application shell for every URL.
There is no converter or generated CRD JSON to maintain.
| Source | Location |
|---|---|
| Kubernetes OpenAPI JSON | oaspec/kubernetes/<version>.json |
| Original CRD YAML or JSON | ETL/crds/<product>-<version>/ |
ETL/crds retains its existing path to preserve source history; the ETL executable is gone. Product/version discovery is automatic. Six existing product display-name aliases preserve URLs containing spaces; Gateway API keeps its existing standard/experimental labels. The default landing page automatically selects the newest stable Kubernetes version in the catalog using semantic version ordering. Explicit version URLs continue selecting that version while it is retained.
The reader accepts OpenAPI v2 definitions and OpenAPI v3 component schemas, including schema-only documents. CRDs may be individual documents, multi-document YAML, or Kubernetes Lists. It extracts each CRD version's openAPIV3Schema, with support for older spec.validation schemas. Unresolved or external references fail loading instead of reading arbitrary files or making network requests.
API version links include each matching group/version/kind entry once. Shared schemas such as Kubernetes DeleteOptions register the same kind in many API groups; those registrations must not multiply the links rendered on every field page.
kin-openapi supplies the OpenAPI types, v2-to-v3 conversion, and reference resolution. Both sources use its openapi3.Schema model. Our code handles catalog discovery, navigation, legacy URL aliases, and the finite page data consumed by React. Kubernetes extensions are retained. See ADR 0001 for the trade-offs.
The source registry maps every supported product to its upstream GitHub repository and CRD assets or source files, including separate standard and experimental Gateway API tracks. The updater discovers each project's latest stable release and retains at most the five newest versions per product and track. Kubernetes keeps minor-version URLs; CRD additions use the full release version. Retained versions are never overwritten, including later Kubernetes patches within an already imported minor version.
make update-schemas
make update-schemas UPDATE_ARGS=-apply
make update-schemas UPDATE_ARGS='-prune-only -apply'The first command reports proposed additions and retirements as JSON without changing files. -apply downloads into staging, removes expired snapshots there, and validates the remaining corpus with the production reader before committing the file changes. New snapshots record source provenance and SHA-256 checksums; the retirement report includes checksums for every removed file. -prune-only skips upstream discovery and applies the same retention policy to the existing corpus. Cleanup also runs when there is no new upstream release. The command reads an optional GITHUB_TOKEN from the environment for GitHub API limits; never put the token in command arguments. Structured job logs go to stderr, leaving stdout as machine-readable JSON. The updater also supports -root to operate on a separate corpus checkout.
Retired snapshots disappear from the version selector, API and sitemaps, and their documentation URLs return 404 with recovery links. Git history remains the archive. Gateway API standard and experimental each retain five versions independently. ADR 0004 replaces unlimited retention while preserving immutable retained snapshots and reviewed releases.
The Update schemas workflow runs Tuesdays at 08:23 UTC and supports manual dispatch on main. It validates new data with the build, Go/frontend tests, container smoke checks, and Chromium suite before opening or updating automation/schema-updates. A separate job has the permissions to propose the PR. Review its source changes and approve any approval-required GitHub Actions runs before merging; deployment remains an explicit Spacelift promotion. ADR 0003 records why updates are immutable snapshots.
| Route | Response |
|---|---|
/ |
Redirect to /kubernetes/1.34 |
/<item>/<version> |
Resource index |
/<item>/<version>/<resource> |
Schema documentation |
/api/catalog |
Products and versions as JSON |
/api/page?item=...&version=...&resource=... |
The same page data used by React |
/api/definitions?item=kubernetes&version=1.34 |
All named types and nested CRD aliases for quick search |
/robots.txt |
Crawl policy and the configured site's sitemap location |
/sitemap.xml, /sitemap-<number>.xml |
Sitemap index and canonical documentation URLs |
/healthz, /readyz |
Ready after the corpus loads successfully |
Nested inline schemas use pointer, a JSON Pointer through schema keywords such as /properties/spec/properties/containers/items; path carries the displayed field traversal. References are resolved by the library. Canonical schema locations make recursive references navigable without infinitely expanding the tree. /api/page returns canonical data selected by item, version, resource, pointer, and the legacy oneOf/key selectors. /api/definitions uses only item and version. Both ignore frontend traversal context (path, linked, and trail). Field filters remain local to the browser and are never sent to the API.
Sitemaps use the same finite catalog routes as prerendering. They include canonical inline pointers, omit traversal history and legacy aliases, and split at the sitemap protocol's URL-count and byte limits. Their origin comes from SITE_URL, never the incoming Host header. The old apiextensions crawler exclusion is removed; recursive documentation uses the same finite navigation rules as other pages.
Prerendering calls the same React component used by the browser. Pages are stored compressed to bound image size. HTML is shared by schema identity: path, legacy linked, and trail do not affect it, while pointer, oneOf, and key still select the documented schema. Canonical requests hydrate directly. On contextual visits, a synchronous startup guard prevents canonical links from being clicked while the application loads. The browser restores headings, links, and circular-reference limits entirely from the embedded canonical page and URL, without fetching contextual API data. Invalid traversal history shows recovery controls. Without JavaScript, schema documentation and canonical navigation remain available, but traversal history is not restored. Unknown resources return HTTP 404; malformed schema queries return HTTP 400. ADR 0006 records the embedded-data design and replaces contextual API restoration.
The build bundles the synchronous React renderer with a URLSearchParams polyfill into frontend/dist-render/renderer.js. Production runs two isolated Goja workers with exclusive access, a two-second rendering deadline, cancellation, and a call-stack limit. Concurrent requests for identical page data share active rendering work. Each caller retains its deadline, and the last waiting caller's departure cancels abandoned work. Completed results and failures are not retained. JavaScript receives page JSON and no filesystem or network bindings. See ADR 0002 for runtime rendering and ADR 0007 for shared work and cancellation.
The embedded bundle collects React's HTML chunks and joins them once. React's original repeated string append copies the growing Unicode output in Goja, causing quadratic allocation and rendering time on large contextual pages. The build checks the upstream collector's shape and fails if it changes, requiring review during React upgrades. Browser and Node bundles use unmodified React. Integration tests compare complete large-page output with Node and enforce an allocation budget. Run go test ./internal/server -run '^$' -bench '^BenchmarkLargeContextualBurst$' -benchtime=1x without race instrumentation to check forty concurrent contextual requests at the production deadline.
Use Search all types in the header, or press Ctrl/Cmd+K (also Ctrl/Cmd+P), to jump directly to any named type in the selected specification and version. This includes nested types such as ContainerStatus, not just top-level resources. Search tolerates typos and shows full type identifiers to distinguish API versions. Use arrow keys and Enter to open a result, or Escape to close the dialog and return focus.
The browser loads the selected specification's definition index when search is first opened, then uses Fuse.js locally. Search text never enters URLs, API requests, logs, or telemetry. The existing / shortcut still focuses the current table's field filter. Search requires JavaScript; documentation and field navigation remain server-rendered.
make build
make test
go vet ./...
npm --prefix frontend run typecheck
tofu -chdir=infra init -backend=false
tofu -chdir=infra validateGo tests cover the entire original corpus, including the 7,174 legacy definition names captured before removing generated files. Additional tests cover references and cycles, CRD envelopes, unions, required fields, invalid input, HTTP errors, script-safe serialization, and OTLP correlation/privacy. Frontend tests exercise filters, selectors, navigation, error states, and the real Faro transport.
docker build --platform linux/amd64 --build-arg VERSION="$(git rev-parse --short HEAD)" -t manifests:local .
docker run --rm -p 18080:8080 manifests:localWith the container running, node scripts/smoke.mjs http://localhost:18080 verifies its API, original field descriptions, nested HTML, legacy links, required fields, crawler endpoints, and error responses.
Run the Chromium regression suite against that same container:
npm --prefix frontend exec -- playwright install chromium
PLAYWRIGHT_BASE_URL=http://localhost:18080 npm --prefix frontend run test:browserThe browser suite covers quick search, keyboard navigation, local search privacy, nested CRD links, restored traversal context and circular-reference limits, canonical navigation without JavaScript, failed-request recovery, and mobile overflow. CI runs it against the exact container before publishing and retains screenshots, traces, reports, and container logs on failure. Browser tests use isolated contexts and fail on browser errors; they do not send production telemetry from local hosts.
Dependabot checks the Go module, frontend/ npm dependencies, GitHub Actions, Docker base images, and infra/ OpenTofu dependencies weekly. Minor and patch updates are grouped for Go and frontend dependencies; major upgrades remain separate review items. Root Yarn dependencies belong to the retired implementation.
The image includes the immutable corpus, prerendered HTML, and browser assets. It runs as a non-root user, listens on 0.0.0.0:$PORT, and needs no database, persistent disk, cluster access, or Node runtime. Schema changes require a new build. SIGTERM drains requests and flushes telemetry within Cloud Run's shutdown window.
The OpenTofu service module defines one Cloud Run service with an immutable image digest, a dedicated service account, telemetry secret access, and scale-to-zero behavior. It uses an existing GCP project and Secret Manager secret.
The Verify workflow runs the application checks and smoke-tests a Linux AMD64 container. After a successful push to main, or a manual workflow run against main, its publish job sends that exact tested image to us-east1-docker.pkg.dev/nwf-shared/apps/manifests-io. The full Git commit SHA is the immutable revision tag. The production tag identifies the latest verified candidate from the current main commit. Older runs cannot replace a newer candidate, and an existing SHA tag cannot be overwritten with a different image.
Publishing uses GitHub OIDC through Google Workload Identity Federation with manifests-builder@nwf-shared.iam.gserviceaccount.com; no service-account key is stored in GitHub. Only the publish job requests an identity token. Pull requests and other branches run verification without publishing.
The production Spacelift stack uses TheOutdoorProgrammer/configurations, manifests/production. Its OpenTofu configuration resolves the production tag to an immutable digest and plans the Cloud Run update. GitHub Actions publishes images; Spacelift owns infrastructure and deployment.
Successful public HTML, JSON, crawler documents, redirects, ordinary assets, and stable missing-schema HTTP 404 responses advertise a seven-day shared cache lifetime. Browsers revalidate those URLs, so a Cloudflare purge exposes updated content without requiring users to clear their browser cache. Hashed assets retain a one-year immutable lifetime. Health checks, malformed requests, missing assets, and transient failures remain uncached. Missing-schema HTML becomes cacheable only after successful rendering.
The Cloudflare proxy keeps only each route's semantic query parameters in both the cache key and origin request. Documentation preserves pointer, oneOf, and key; /api/page also preserves item, version, and resource; /api/definitions preserves item and version. Traversal and tracking parameters do not fragment these entries. Ordinary reloads and cookies share public cached responses, and cookies are stripped before a cache fill reaches origin. Requests containing Authorization, Range, If-Match, or If-Unmodified-Since bypass shared caching. Use X-Manifests-Cache-Bypass: 1 for an explicit origin diagnostic; it does not refill the cache. X-Manifests-Cache reports HIT, MISS, or BYPASS.
Deploy browser restoration from embedded data and canonical API responses before enabling API query normalization, then purge existing entries. Roll back API normalization before reverting to contextual API responses. The Spacelift deployment hook purges the zone after a successful apply. To bust the cache manually, run python3 ../../cloudflare/workers/purge-manifests-cache.py as a task on manifests-production, or use Cloudflare's zone-wide Purge Everything action. Publishing an image alone does not invalidate the cache. Edge eviction, regional cache placement, expiration, and deliberate invalidation can still require legitimate origin fills.
- Merge the application change into
mainand wait for both Verify jobs to succeed. A manual run onmainfollows the same checks. - Start a production run in Spacelift and review the planned container digest and infrastructure changes.
- Approve the plan to deploy the candidate. Publishing an image alone does not change the live service.
To rebuild an already published commit with updated dependencies or base images, create a new commit so the revision tag remains immutable.
Runtime configuration:
| Variable | Default |
|---|---|
PORT |
8080 |
DATA_DIR |
. |
WEB_DIR |
frontend/dist |
RENDER_DIR |
frontend/prerender |
PUBLIC_DIR |
public |
SITE_URL |
https://www.manifests.io |
Directory options also have corresponding command flags; run ./build/manifests -help. The -export flag emits page data as NDJSON for the React prerender build.
Structured stdout logs include trace/span IDs. Configure OTEL_EXPORTER_OTLP_ENDPOINT and runtime-only OTEL_EXPORTER_OTLP_HEADERS for OTLP HTTP/protobuf traces and logs. Without an endpoint, export is disabled. OTEL_SDK_DISABLED=true disables export explicitly. W3C TraceContext and Baggage propagation are configured in both cases.
Production browser telemetry uses the existing public Grafana Faro collector. The existing PostHog integration retains manual pageview events on the production domains, with automatic capture, recording, persistence, and person profiles disabled. Local previews do not send production telemetry. Search values, query strings, request bodies, cookies, authorization headers, raw URLs, and freeform exceptions are excluded from exported telemetry. General OTLP credentials never enter the browser build. See deployment telemetry configuration for details.
Production browser source maps are uploaded privately to Grafana before CI publishes a deployable image. The full Git SHA identifies both the uploaded bundle and Faro metadata. The Docker source-maps target exports maps from the same frontend build; runtime images omit them, and HTTP handlers reject map requests. FARO_SOURCEMAP_API_KEY is a GitHub Actions secret scoped to source-map operations, available only to the main-branch upload step. An absent credential or failed upload blocks publication.
MIT. Original authorship remains in the site footer.