Knowledge System — GKE Reference Deployment
Goal
Stand up a real, public, working demarkus knowledge system on GKE via GitOps. The deployment repo doubles as a GitHub template so others can instantiate their own knowledge systems by copying it.
Deployment repo: latebit-io/demarkus-knowledge-system-deploy
Hostname: knowledge.demarkus.io
Visibility: public, marked as GitHub template repository
This is a deployment-layer plan. No core/protocol/server changes. The demarkus binaries and helm charts (ghcr.io/latebit-io/charts) are consumed as-is.
Locked Decisions
| Topic | Choice | Why |
|---|---|---|
| IaC | OpenTofu (MPL 2.0) | Genuinely open source, Linux Foundation; HashiCorp's BSL ruled out Terraform |
| Cluster | GKE Standard | Need flexibility for mutating webhooks + custom node configs; Autopilot constrains both |
| Region | northamerica-northeast2 (Toronto), single region |
Operator preference for Canadian data residency; all resources (state bucket, project, cluster, DNS) co-located |
| Secrets store | OpenBao (MPL 2.0) | OSS fork of Vault 1.14; KV v2, k8s auth, GCP KMS auto-unseal, ESO + bank-vaults integration all work |
| Secret injection | bank-vaults mutating webhook | Operator preference; rewrites pod env from vault: refs at admission, no secrets in etcd |
| GitOps | ArgoCD with ApplicationSet | Already proven in deploy/k8s/examples/applicationset.yaml; chart published to ghcr.io/latebit-io/charts |
| Ingress | GKE-native NEG + Google-managed cert (tentative) | Simplest path; revisit if cert-manager + Let's Encrypt is needed for portability |
| DNS | Cloud DNS + external-dns operator | Records register themselves from Ingress annotations |
| Backups | restic CronJob → GCS bucket | OSS, encrypted, deduplicated; weakens any case for bucket-as-primary storage |
| OIDC for broker | Google login (Google Workspace / Google OAuth) | Day-one choice; broker supports generic OIDC so swap is cheap later |
| Project provisioning | OpenTofu creates the GCP project | Self-contained bootstrap; assumes org-level perms |
| State backend | GCS bucket with object versioning in a separate bootstrap project (latebit-tofu-bootstrap) |
Keeps state physically outside the blast radius of the tofu-managed project; chicken/egg solved by hand-creating bucket once |
| CI | GitHub Actions: tofu plan on PR, tofu apply on merge |
Standard; OIDC federation to GCP, no long-lived service account keys |
| License/topology choices | Public, template repo, one repo holds both envs/example/ and envs/prod/ |
YAGNI on splitting until drift becomes a real problem |
Repo Layout
demarkus-knowledge-system-deploy/
├── README.md — what this is, quick-start, template-instantiation guide
├── .github/workflows/
│ ├── tofu-plan.yml — on PR: fmt-check, validate, plan with comment
│ └── tofu-apply.yml — on merge to main: apply per env
├── tofu/
│ ├── modules/
│ │ ├── project/ — GCP project + APIs + billing
│ │ ├── network/ — VPC, subnets, Cloud NAT, firewall
│ │ ├── gke/ — cluster, node pools, Workload Identity
│ │ ├── dns/ — Cloud DNS zone + DNSSEC
│ │ ├── backups/ — GCS bucket for restic + lifecycle policy
│ │ └── argocd-bootstrap/ — helm install argocd + apply root ApplicationSet
│ └── envs/
│ ├── example/ — placeholder values; template users copy this
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ └── terraform.tfvars.example
│ └── prod/ — the real knowledge.demarkus.io deployment
│ ├── backend.tf
│ ├── main.tf
│ └── terraform.tfvars
├── bootstrap/ — one-shot manifests OpenTofu applies post-cluster
│ ├── argocd-values.yaml — values for the argocd helm chart
│ └── root-appset.yaml — seed ApplicationSet → platform/ + apps/
├── platform/ — cluster prerequisites (Argo manages these)
│ ├── cert-manager/
│ ├── openbao/ — HA Raft, GCP KMS auto-unseal
│ ├── bank-vaults-webhook/ — mutating webhook + secret-injection sidecar
│ ├── external-dns/
│ └── ingress-nginx/ — if we don't go GKE-native
├── apps/ — demarkus-specific Applications
│ ├── demarkus-broker/
│ │ ├── application.yaml
│ │ └── values-prod.yaml
│ └── demarkus-worlds/
│ ├── applicationset.yaml — derived from monorepo example
│ └── values-base.yaml
└── docs/
├── instantiate.md — template-user guide
├── runbook.md — bootstrap-from-zero, DR, rotate secrets
└── architecture.md — what's deployed where, sync waves, dependency order
Phases
Order matters. Each phase produces something testable.
Phase 1 — Bootstrap state + project
- Hand-create one GCS bucket for OpenTofu state in a separate bootstrap project (
latebit-tofu-bootstrap), innorthamerica-northeast2, object versioning on. Chicken/egg breaks here. tofu/modules/project/creates the GCP project, enables APIs (cloudresourcemanager,serviceusage,compute,container,dns,iam,cloudkms), wires billing. Nosecretmanager— OpenBao is the secrets store.tofu/envs/prod/consumes the module with real values.- Outcome:
gcloud projects describe <project_id>returns a live project.
Phase 2 — Network + DNS
tofu/modules/network/— VPC with private nodes, Cloud NAT for egress, firewall for cluster CIDR.tofu/modules/dns/— Cloud DNS managed zone fordemarkus.io(or a delegated subdomainknowledge.demarkus.io), DNSSEC on.- Outcome: zone exists, NS records propagate.
Phase 3 — GKE cluster
tofu/modules/gke/— regional cluster innorthamerica-northeast2, private nodes, Workload Identity on, node pool with autoscaling.- Outcome:
kubectl get nodesworks against the cluster.
Phase 4 — ArgoCD bootstrap
tofu/modules/argocd-bootstrap/uses helm provider to install ArgoCD into the new cluster.- Same module applies
bootstrap/root-appset.yamlviakubectl_manifest. - Root ApplicationSet points back at this repo, sync waves: -2 cert-manager, -1 OpenBao + bank-vaults, 0 ingress + external-dns, 1 demarkus apps.
- Outcome: ArgoCD UI reachable via port-forward; root app reads the repo.
Phase 5 — Platform layer
platform/cert-manager/— Application + ClusterIssuer (Google-managed certs preferred; Let's Encrypt as backup).platform/openbao/— HA mode, 3 replicas, Raft storage, GCP KMS auto-unseal, k8s auth method enabled.platform/bank-vaults-webhook/— mutating webhook, AppRole + k8s auth backend, secret-injection sidecar image.platform/external-dns/— registers records from Ingress annotations against the Cloud DNS zone.platform/ingress-*— GKE-native ingress class (or nginx-ingress, decide based on cert-manager outcome).- Outcome:
https://argocd.knowledge.demarkus.ioreturns a valid cert.
Phase 6 — Seed OpenBao secrets
- Manually (one-time per env): unseal OpenBao with GCP KMS, init root token, enable
kv-v2atsecret/, store:secret/broker/oidc-client— Google OAuth client ID + secretsecret/broker/jwks-signing-key— broker's signing key (or auto-generate at startup; decide)secret/worlds/<name>/admin-token— per-world admin token (server chart bootstrap Job currently generates this; pivot to consume from OpenBao)
- Outcome:
bao kv get secret/broker/oidc-clientreturns the values.
Phase 7 — Demarkus broker + first world
apps/demarkus-broker/Application using the broker helm chart fromghcr.io/latebit-io/charts.- Pod annotated for bank-vaults webhook:
vault.security.banzaicloud.io/vault-addr: "https://openbao.openbao.svc:8200"+ env vars asvault:secret/data/broker/oidc-client#client_secret. - Ingress:
knowledge.demarkus.io(broker HTTPS gateway),argocd.knowledge.demarkus.io,openbao.knowledge.demarkus.io(admin only).
- Pod annotated for bank-vaults webhook:
apps/demarkus-worlds/applicationset.yaml— start with one world (world-a).- Outcome:
curl https://knowledge.demarkus.io/.well-known/oauth-authorization-serverreturns RFC 8414 metadata;/knowledge-joinfrom a Claude Code plugin can complete the device flow end-to-end.
Phase 8 — GitHub Actions
.github/workflows/tofu-plan.yml— on PR:tofu fmt -check,tofu validate,tofu planper env, comment plan as PR comment..github/workflows/tofu-apply.yml— on merge to main:tofu apply -auto-approveper env. Auth via Workload Identity Federation (GitHub OIDC → GCP service account, no JSON keys).- Outcome: PRs show plan diffs; merge to main reconciles cluster automatically.
Phase 9 — Backups
apps/backups/— restic CronJob, mounts world PVCs read-only, pushes to GCS bucket created in Phase 1.- Test restore procedure: spin up an isolated test PVC, restore from latest snapshot, point a transient demarkus-server at it, verify hash chain.
- Outcome: documented restore drill that succeeds end-to-end.
Phase 10 — Public-template polish
- README rewrites: what this is, why public, how to instantiate the template, what it costs to run (~$X/month for the baseline).
docs/instantiate.md— step-by-step for someone forking the template.- GitHub repo settings: mark as Template repository.
- Announcement post (blog at
demarkus.io/blog).
Open Items
These don't block Phase 1 but need answers before the phase that needs them:
- Ingress choice (blocks Phase 5). GKE-native NEG ingress + Google-managed cert is simplest. cert-manager + Let's Encrypt is more portable and works with any ingress. Lean GKE-native unless portability is a real requirement.
- Multi-env (affects Phase 1 forward).
prodonly to start, ordev+prod? Recommendation:prodonly. Adddevwhen there's a real reason (testing destructive changes against something non-customer-facing). Saves ~50% of the recurring cost during bootstrap. - Number of worlds initially (blocks Phase 7). One (the demarkus knowledge-system soul itself) is enough to prove end-to-end. Add more on demand.
- OpenBao HA scale (Phase 5). 3 replicas (HA Raft) for production seriousness, or 1 (dev mode) to save cost during bootstrap? Recommendation: start with 1, upgrade to 3 before going live.
- broker JWKS signing key (Phase 6). Stored in OpenBao and mounted, or auto-generated at broker startup and persisted in OpenBao on first run? Check broker code before deciding.
Reference
- Monorepo helm charts:
deploy/helm/demarkus-server,deploy/helm/demarkus-broker,deploy/helm/demarkus-agent - ApplicationSet pattern:
deploy/k8s/examples/applicationset.yaml - kind harness for local proof:
deploy/kind/up.sh(already includes--with-argo) - Mock OIDC for local dev:
deploy/kind/mock-oidc.yaml(replaced in prod by Google login) - Universe Deployment plan: /plans/universe-deployment.md (parent context — this plan is the concrete GKE landing of that broader effort)
- Universe Onboarding plan: /plans/universe-onboarding.md (end-user flow that depends on this deployment being live)
Non-Goals
Explicitly out of scope for this plan; do not let them grow it:
- Bucket-backed primary storage for demarkus-server (S3 backend). Deferred — discussed and rejected for now in favor of PVC + restic backups. Revisit only if multi-writer or DR portability becomes a hard requirement.
- Multi-cluster / multi-region active-active. Single region (
northamerica-northeast2, Toronto), single cluster. - Custom OIDC IdP. Google login only at launch; broker's generic OIDC support keeps the swap cheap later.
- Migrating existing soul deployments onto this GKE cluster. This is greenfield; existing personal souls keep running where they run.
Addendum — Phase 9 implementation note (2026-05-29)
Phase 9 shipped as CSI VolumeSnapshots, not the restic CronJob the Locked Decisions table named. Rationale uncovered during build:
- demarkus-server 0.17.11 exposes only PVC storage (no object-storage backend), and the data is a hash-chained versioned store on RWO PDs. restic would force a snapshot→clone→mount→Job orchestrator (CSI snapshots are namespaced and a clone PVC + restic pod must live in the source namespace). CSI snapshots make it a one-object backup: crash-consistent, node-independent, zero OpenTofu / no GCS bucket / no Workload Identity.
- World namespaces are labeled
demarkus.io/backup=truevia the worlds ApplicationSet'smanagedNamespaceMetadata, so new worlds are backed up with no per-world config. Implementation:apps/backups/,docs/runbook-backup-restore.md.
Tradeoff accepted: GCE-snapshot data is GCP-locked (no off-GCP restore) and Google-managed-key encrypted (not client-side restic encryption). Consistent with the stack's existing all-in-on-GCP posture.
New Open Item (deferred): native object-storage (GCS/S3) backend for demarkus-server. This is the right long-term home for the "bucket-backed storage, backups built-in via versioning" idea (and what GCS FUSE can't safely fake for a transactional store). It's a demarkus-server/core feature, out of scope for this deploy-layer plan. When it lands, worlds get built-in bucket durability and the CSI snapshots for world content can be retired (OpenBao, still PVC-backed, would keep snapshot backups).
Addendum — Phase 10 status + announcement deferred (2026-05-29)
Phase 10 docs shipped: README rewritten (live endpoint, GitOps + sync-wave
architecture, cost baseline, repo layout, runbook index) and docs/instantiate.md
added (fork→live orchestration guide with the deployment-specific find/replace
checklist, linking the existing runbooks). Repo marked as a GitHub template
(description + topics set). Architecture folded into the README; no separate
docs/architecture.md.
Announcement blog post (demarkus.io/blog): DEFERRED — intentionally held.
Get real miles on the live system and reevaluate a few things first, then write
the post from operating experience rather than launch-day optimism. This is the
sole remaining Phase 10 item; everything else in phases 1–10 is done. Revisit
after a soak period.
Status — COMPLETE except deferred launch blog (verified 2026-05-31)
Corrects the stale /index.md pointer ("Phase 1 next"). Phases 1-10 are done. Verified against the live deploy repo latebit-io/demarkus-knowledge-system-deploy (public, isTemplate, pushed 2026-05-30):
- Tofu (P1-P3):
project/network/dns/gke/argocd-bootstrapmodules (+platform-iam,billing-budget);envs/prod/populated. - ArgoCD (P4):
bootstrap/root-appset.yaml+ values. - Platform (P5): cert-manager, openbao, bank-vaults-webhook, external-dns, ingress-nginx (+ external-secrets, dex, oauth2-proxy).
- OpenBao seed (P6):
scripts/seed-openbao.sh+ runbook. - Broker + worlds (P7):
apps/demarkus-broker/+apps/demarkus-worlds/applicationset.yaml. - CI (P8):
tofu-plan.yml/tofu-apply.yml; atofu-applyon main succeeded 2026-05-29. - Backups (P9):
apps/backups/via CSI VolumeSnapshots — a documented pivot from the planned restic→GCS (accepted tradeoff: GCP-locked, Google-managed keys; native object-storage backend filed as a deferred core item). - Polish (P10): README rewrite,
docs/instantiate.md, template flag set.
Live corroboration: https://knowledge.demarkus.io/.well-known/oauth-authorization-server returns valid RFC 8414 metadata over TLS (issuer broker.knowledge.demarkus.io, device-code grant, RS256 JWKS) — the exact Phase 7 acceptance test, passing against the real domain.
Sole remaining item: the announcement blog post, intentionally deferred for a soak period.