Consortium starter pipeline — ceremony orchestrator + governance UI
Find a file
Tyler J King c6f1d07ed9 feat(deploy): Dockerfile + k8s manifests for Talos deployment
Multi-stage Elixir/OTP Dockerfile, Kubernetes manifests following
Keycloak pattern, mix release migration module, and deploy runbook.

Target: guildhall.guildhouse.dev via Hetzner LB + Cloudflare (orange
cloud). Forgejo container registry at git.guildhouse.dev/tking/guildhall.

Not yet deployed; artifacts only. See DEPLOY-RUNBOOK.md for execution.

Artifacts produced:

- Dockerfile — multi-stage, Elixir 1.17.3 / OTP 27.1.2, debian-bookworm
  builder + debian-bookworm-slim runtime. Dep-layer caching via
  explicit apps/*/mix.exs copy before source. Asset pipeline runs
  mix assets.setup + mix assets.deploy (tailwind + esbuild + phx.digest).
  Non-root uid 1000, tini as pid-1, HEALTHCHECK against /health.
- .dockerignore — excludes _build/, deps/, k8s/, .git/, test artifacts,
  and apps/guildhall_web/priv/static/assets/ (regenerated by phx.digest
  inside the builder).
- apps/guildhall_web/.../router.ex — adds `/health` route under :api
  pipeline. Unauthenticated by design (Kubernetes probes + LB target).
- apps/guildhall_web/.../controllers/health_controller.ex — shallow
  health: Phoenix up + Ecto pool can `SELECT 1`. Returns 200 ok or 503
  degraded with reason.
- apps/guildhall_ops_db/lib/guildhall/ops_db/release.ex — Release
  module for migrations. `Guildhall.OpsDb.Release.migrate/0` and
  `rollback/2`. Called from the migration Job via
  `bin/guildhall eval`. Module path reflects actual repo location
  (repo is `Guildhall.OpsDb.Repo` in `:guildhall_ops_db`, not the
  prompt's suggested `Guildhall.Repo`).

Kubernetes manifests in k8s/ (numbered for apply order):
  00-namespace.yaml                  — guildhall namespace w/ guildhouse labels
  10-registry-secret-template.yaml   — doc-only template for dockerconfigjson
  20-postgres-pvc.yaml               — 5Gi longhorn RWO
  30-postgres-deployment.yaml        — postgres:16, keycloak-matched resources
                                       + pg_isready probes, PGDATA subpath
  40-postgres-service.yaml           — ClusterIP :5432
  50-guildhall-secrets-template.yaml — doc-only template for app + DB secrets
  60-migration-job.yaml              — ecto migration Job, name includes tag
                                       for per-deploy uniqueness, TTL 24h
  70-guildhall-deployment.yaml       — RollingUpdate maxSurge 1 maxUnavailable 0,
                                       /health probes, 200m/256Mi requests
                                       and 1/1Gi limits, 5s preStop sleep
  80-guildhall-service.yaml          — LoadBalancer with exact Keycloak-
                                       matched Hetzner annotations
                                       (location nbg1, type lb11, name
                                       guildhall, use-private-ip false),
                                       port 80 origin (Cloudflare TLS)

- DEPLOY-RUNBOOK.md — 6-phase deploy sequence (build + push, cluster
  prep, DB, migrate, app rollout, DNS + smoke), iteration helper with
  sed-based tag-bump, rollback procedure (image rollback, schema
  rollback via Release.rollback, full teardown), and v0.1 limitations
  (Cloudflare-edge TLS not cluster-terminated; no Flux integration;
  no OIDC wiring; no substrate CRD integration; single replica).

Decisions made during artifact production that weren't explicit in
the prompt:

- Release module name is `Guildhall.OpsDb.Release` (not
  `Guildhall.Release`) matching the actual repo namespace. Migration
  Job command adjusted to `Guildhall.OpsDb.Release.migrate()`.
- Dockerfile uses `-slim` builder variant (not the full bookworm
  builder) to keep the builder stage closer to the runtime image
  size, reducing multi-stage layer transfer during build.
- Asset compilation runs `mix assets.setup` before `mix assets.deploy`
  so tailwind + esbuild binaries install cleanly inside the container
  (the dev-only :runtime flag on those deps means they need explicit
  install in a prod builder).
- tini added as pid-1 in the runtime stage. Not in the prompt, but
  standard-practice for OTP containers to ensure signal propagation
  and zombie reaping under Kubernetes.
- Rolling update strategy: maxSurge 1 / maxUnavailable 0 (zero-
  downtime rollout at replicas=1; the new pod comes up alongside the
  old, health-checks, then the old is terminated). Matches typical
  single-replica LiveView pattern.
- preStop `sleep 5` — gives in-flight HTTP + LiveView connections a
  grace window before termination.
- Hetzner LB annotations: verified exact set from cluster keycloak
  service — location=nbg1, name=guildhall, type=lb11,
  use-private-ip=false. The prompt asked about uses-proxyprotocol
  and algorithm-type; neither is set on Keycloak's service and both
  are omitted here for consistency.
- Migration Job name includes the tag (`guildhall-migrate-v0-1-0`) so
  multiple deploys don't collide on Job name reuse. Runbook documents
  the sed helper to bump both the image tag and the Job name for
  subsequent deploys.
- Both exploratory docs (`DEPLOY-EXPLORATORY-2026-04-21.md`,
  `FORGEJO-REGISTRY-INVESTIGATION-2026-04-21.md`) are currently
  untracked in the repo. They're left out of this commit per the
  prompt's explicit `git add` list. They can be committed separately
  (or ignored) at Tyler's discretion.

Not done tonight (per prompt's NOT PERMITTED list):
- docker build / docker push
- kubectl apply of any manifest
- Forgejo PAT creation
- Cloudflare DNS changes
- git push (this commit is local-only pending review)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Tyler J King <tking@guildhouse.dev>
2026-04-22 04:00:40 -04:00
.forgejo/workflows docs: README, runtime config, CI placeholder 2026-04-18 07:23:08 -04:00
apps feat(deploy): Dockerfile + k8s manifests for Talos deployment 2026-04-22 04:00:40 -04:00
config feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
k8s feat(deploy): Dockerfile + k8s manifests for Talos deployment 2026-04-22 04:00:40 -04:00
.dockerignore feat(deploy): Dockerfile + k8s manifests for Talos deployment 2026-04-22 04:00:40 -04:00
.formatter.exs feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
.gitignore feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
AGENTS.md feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
DEPLOY-RUNBOOK.md feat(deploy): Dockerfile + k8s manifests for Talos deployment 2026-04-22 04:00:40 -04:00
Dockerfile feat(deploy): Dockerfile + k8s manifests for Talos deployment 2026-04-22 04:00:40 -04:00
mix.exs feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
mix.lock feat: scaffold guildhall Elixir umbrella 2026-04-18 07:09:20 -04:00
README.md docs: README, runtime config, CI placeholder 2026-04-18 07:23:08 -04:00

Guildhall

Ceremony orchestrator and governance UI — Elixir/Phoenix umbrella over substrate CRDs.

guildhall presents and coordinates; substrate decides and enforces. The ceremony engine is a Rust Kubernetes operator with CRDs and etcd-backed state. guildhall is the orchestrator: it coordinates humans around those CRDs — notifying witnesses, collecting signatures via LiveView, tracking status, rendering dashboards.

┌────────────────────────────────────────────────────────┐
│ SUBSTRATE (Rust, K8s operators) — decides + enforces    │
│   CeremonyEngine (CRD), AccordEvaluator (CRD),          │
│   CorpusReconciler (CRD), PostureEvaluator (CRD),       │
│   Chronicle collector (agent)                           │
└────────────────────┬───────────────────────────────────┘
                     │ watches CRDs + emits Chronicle events
                     ▼
┌────────────────────────────────────────────────────────┐
│ GUILDHALL (Elixir/Phoenix) — orchestrates + presents    │
│   CeremonyOrchestrator (workflow coordinator)           │
│   AccordComposer (UI + submission)                      │
│   ArtifactBrowser (UI + lifecycle)                      │
│   PostureDashboard (visualization)                      │
│   ChronicleConsumer (projector + UI)                    │
└────────────────────────────────────────────────────────┘

Naming discipline: guildhall components are orchestrators (workflow, coordination, presentation). The substrate components are engines and reconcilers (enforcement, state-machine advancement). Never call a guildhall component by a substrate name.


Umbrella apps

App Role
guildhall_web Phoenix LiveView UI — dashboards, ceremonies, artifacts, posture
guildhall_orchestrator Watches substrate CRDs (future), notifies witnesses, broadcasts ceremony status over PubSub
guildhall_ops_db Ecto schemas for the five Ops DB tables (per DESIGN-OPS-DB-CHAIN-OF-CUSTODY-0001)
guildhall_graph_bridge Microsoft Graph API reconciler — Intune deployment (stub)
guildhall_chronicle Chronicle event consumer + Ops DB projector (stub)

Local development

Prerequisites

  • Elixir 1.17.x + OTP 27 (via mise or asdf)
  • Postgres 14+ running on localhost:5432 with a postgres superuser (password postgres for dev)

First-time setup

mix deps.get
mix ecto.create
mix ecto.migrate
mix run apps/guildhall_ops_db/priv/repo/seeds.exs

Run the server

mix phx.server

Then visit:

Run tests

mix test

Configuration

Development defaults are in config/dev.exs (Postgres at localhost:5432 as postgres/postgres, database guildhall_dev). Production runtime configuration reads from environment variables in config/runtime.exs:

Env var Purpose
DATABASE_URL Postgres connection (required in prod)
SECRET_KEY_BASE Phoenix cookie/session signing (required in prod)
PHX_HOST Public hostname (default guildhall.guildhouse.dev)
PHX_SERVER Set to true to run the HTTP server under mix release
POOL_SIZE DB pool size (default 10)
ECTO_IPV6 Set to true for IPv6 DB connections

Commented placeholders exist for future sprints: KUBECONFIG (substrate CRD watcher) and OIDC_ISSUER / OIDC_CLIENT_ID / OIDC_CLIENT_SECRET (Keycloak auth).


Relationship to the rest of the stack

guildhall is one of the PaaS components (ROADMAP WS1). It sits alongside:

  • substrate — the governance Rust crates + K8s operators
  • bxnet-ops — the org-ops CLI framework (reference fork: BXNet)
  • guildhouse-mcp — MCP server for LLM mediator context
  • guildhouse-specs — the FFC specifications

See the design docs for the full picture:


License

Apache 2.0.