{"id":46343926,"url":"https://github.com/devantler-tech/platform","last_synced_at":"2026-07-04T22:00:44.365Z","repository":{"id":65915180,"uuid":"494599576","full_name":"devantler-tech/platform","owner":"devantler-tech","description":"DevantlerTech Platform - including deployment artifacts for running the platform in CI/CD and in Talos Omni.","archived":false,"fork":false,"pushed_at":"2026-07-04T13:59:14.000Z","size":8685,"stargazers_count":31,"open_issues_count":47,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-07-04T14:12:53.301Z","etag":null,"topics":["infrastructure"],"latest_commit_sha":null,"homepage":"http://platform.devantler.tech/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devantler-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2022-05-20T20:32:47.000Z","updated_at":"2026-07-04T13:24:08.000Z","dependencies_parsed_at":"2026-05-05T12:02:34.303Z","dependency_job_id":null,"html_url":"https://github.com/devantler-tech/platform","commit_stats":null,"previous_names":["devantler-tech/platform"],"tags_count":783,"template":false,"template_full_name":null,"purl":"pkg:github/devantler-tech/platform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devantler-tech%2Fplatform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devantler-tech%2Fplatform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devantler-tech%2Fplatform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devantler-tech%2Fplatform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devantler-tech","download_url":"https://codeload.github.com/devantler-tech/platform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devantler-tech%2Fplatform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35136712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["infrastructure"],"created_at":"2026-03-04T21:02:39.335Z","updated_at":"2026-07-04T22:00:44.358Z","avatar_url":"https://github.com/devantler-tech.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Devantler Tech Platform ☸️⛴️\n\n\u003cimg width=\"1063\" height=\"1106\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3ab015f0-ab07-4c39-b861-c69517e0d222\" /\u003e\n\nThis repo contains the deployment artifacts for the DevantlerTech Platform. The platform is a Kubernetes cluster that is highly automated with the use of Flux GitOps, CI/CD with Automated Testing, and much more. Feel free to look around. You might find some inspiration 🙌🏻\n\n## Prerequisites\n\nFor local development:\n\n- [Docker](https://docs.docker.com/get-docker/) - For running the cluster locally.\n- [KSail](https://github.com/devantler-tech/ksail) - For developing the cluster locally, and for running the cluster in CI to ensure all changes are properly tested before being applied to the production cluster.\n\nFor the production cluster:\n\n- [Hetzner Cloud](https://www.hetzner.com/cloud/) — Infrastructure provider and managed Cloud Load Balancer for cluster ingress. KSail's native Hetzner provider handles Talos boot, CCM, CSI, and kubeconfig.\n- [Cloudflare](https://www.cloudflare.com) — DNS (A/AAAA records pointed at the Hetzner Cloud Load Balancer) and Origin CA.\n- [Flux GitOps](https://fluxcd.io) - For managing the kubernetes applications and infrastructure declaratively.\n- [SOPS](https://getsops.io) and [Age](https://github.com/FiloSottile/age) - For encrypting the seed secrets that are committed to this repository (the `*.enc.yaml` files), allowing me to store them in git with confidence.\n- [OpenBao](https://openbao.org) and the [External Secrets Operator](https://external-secrets.io) - The runtime secret store for most workloads. SOPS-decrypted seeds are pushed into OpenBao at bootstrap, and `ExternalSecret`s sync them into the namespaces that consume them. A few secrets (Dex and oauth2-proxy) are still read directly from the SOPS-encrypted bootstrap secrets via Flux `postBuild` substitution while the migration to OpenBao completes. See [`docs/secret-rotation.md`](docs/secret-rotation.md) for the full secrets architecture.\n\n## Usage\n\n\u003e [!IMPORTANT]\n\u003e Secrets committed to this repo are encrypted at rest with SOPS + Age. At bootstrap they seed OpenBao, and the External Secrets Operator distributes most of them to workloads at runtime; a few (Dex, oauth2-proxy) are still injected directly via Flux `postBuild` substitution while the OpenBao migration completes. If you want to run the platform locally, or in your own Hetzner project, you will need to:\n\u003e\n\u003e 1. Fork this repo\n\u003e 2. Create your own Age keys\n\u003e 3. Update the `.sops.yaml` file in the root of the repository.\n\u003e 4. Update GitHub secrets with your Age key.\n\u003e 5. Replace all encrypted `*.enc.yaml` files in the `k8s/` folder with new ones that are encrypted with your own keys.\n\nTo run this cluster locally, simply run:\n\n```bash\nksail cluster create\nksail workload push\nksail workload reconcile\n```\n\nPorts 80 and 443 are automatically mapped to localhost via `extraPortMappings` in `ksail.yaml`. The local cluster is a **thin manual test-bed** — a small Talos cluster for trying a component out before promoting it to prod. By default it runs only the **core infrastructure** (CNI + Gateway, DNS, TLS, Flux, Kyverno + policies, VPA, OpenBao + External Secrets, the Dex SSO stack, and CloudNativePG). Heavier infrastructure (observability, autoscaling, backup, runtime security, the VM stack, …) and all apps are opt-in — uncomment the entries you want in the docker provider overlays (`k8s/providers/docker/infrastructure/controllers/kustomization.yaml`, `…/infrastructure/kustomization.yaml`) and the apps overlay (`k8s/providers/docker/apps/kustomization.yaml`), each of which carries a copy-paste template, then re-run `ksail workload push \u0026\u0026 ksail workload reconcile`. Reach the deployed services at their `*.platform.lan` hostnames (the `hosts` file maps these to `127.0.0.1`).\n\nTo tear down:\n\n```bash\nksail cluster delete\n```\n\n### Validating Changes\n\nBefore pushing, validate manifests with schema-aware checks and Flux variable substitution:\n\n```bash\n# Validate local cluster manifests (default)\nksail workload validate\n\n# Validate prod cluster manifests\nksail --config ksail.prod.yaml workload validate\n```\n\nThis is faster than a full cluster test and catches YAML errors, missing fields, and broken kustomize overlays.\n\n## Clusters\n\n### Local\n\nLocal development cluster running on Docker via KSail. Uses Talos with the Docker provider. A small, thin manual test-bed (see [Usage](#usage)) — bring up a component, try it, then promote it to prod.\n\n- 1 control-plane node + 1 worker node (Docker containers)\n- Config: [`ksail.yaml`](ksail.yaml)\n\n### Production\n\nCloud cluster running on Hetzner Cloud via KSail's native Hetzner provider. Deployed via `v*` tags through the CD pipeline, and validated in the merge queue via the CI pipeline.\n\n- 3× [Hetzner CX33](https://www.hetzner.com/cloud/) control planes + 3× CX33 static workers + autoscaling (x86 4 vCPU 8GB RAM 80GB SSD each)\n- Config: [`ksail.prod.yaml`](ksail.prod.yaml)\n\n## Platform components\n\nA high-level inventory of what Flux reconciles onto the cluster. The manifests live under [`k8s/bases/infrastructure/`](k8s/bases/infrastructure) and [`k8s/bases/apps/`](k8s/bases/apps), with provider-specific pieces (Hetzner CCM/CSI, Longhorn, external-dns, …) under [`k8s/providers/`](k8s/providers). The exact set is overlay-dependent: the Hetzner/prod overlay deploys the full base set described below, while the local (docker) overlay is a thin manual test-bed that ships only the core controllers by default and makes the rest opt-in.\n\n**Infrastructure**\n\n- **GitOps \u0026 config** — Flux Operator, Reloader\n- **Networking** — Cilium (CNI + Gateway API), CoreDNS, external-dns (Cloudflare), Hetzner CCM (prod)\n- **Certificates** — cert-manager, trust-manager, Cloudflare Origin CA issuer\n- **Secrets** — OpenBao + External Secrets Operator (runtime), SOPS + Age (at-rest seeds)\n- **Identity / SSO** — Dex (OIDC) with oauth2-proxy / auth-proxy; see [`docs/oidc-kubectl.md`](docs/oidc-kubectl.md)\n- **Policy \u0026 runtime security** — Kyverno, Kubescape, Tetragon; see [`docs/runtime-security.md`](docs/runtime-security.md)\n- **Storage** — Longhorn (replicated block/RWX, prod via Hetzner CSI), CloudNativePG (PostgreSQL operator); see [`docs/rwx-storage.md`](docs/rwx-storage.md)\n- **Autoscaling** — Cluster Autoscaler (nodes), Vertical Pod Autoscaler, KEDA request-rate autoscaling (homepage/umami); see [`docs/node-autoscaling.md`](docs/node-autoscaling.md)\n- **Progressive delivery** — Flagger (Gateway API canary deployments with SLO-gated automated rollback, metrics from Coroot); see [`docs/progressive-delivery.md`](docs/progressive-delivery.md)\n- **Observability** — Coroot (self-hosted, eBPF: metrics, logs, traces, profiling, service map, SLO alerting), OpenCost (cost); see [`docs/dr/alerting.md`](docs/dr/alerting.md)\n- **Backup / DR** — Velero with CloudNativePG backups; see [`docs/dr/`](docs/dr)\n- **Virtualization** — KubeVirt + CDI _(local/CI only; disabled on the Hetzner/prod overlay)_\n- **Testing** — Testkube _(local/CI only; not deployed to prod)_\n\n**Apps** ([`k8s/bases/apps/`](k8s/bases/apps))\n\n- Homepage (dashboard), Headlamp (Kubernetes UI), Umami (analytics), Actual Budget (budgeting), `whoami` (debug)\n- FleetDM (device management) is parked — disabled 2026-06-03; its manifests are retained for re-enabling (see [`k8s/bases/apps/kustomization.yaml`](k8s/bases/apps/kustomization.yaml))\n- **Tenants** — apps deployed from their own repositories (`ascoachingogvaner`, `wedding-app`); see [`docs/TENANTS.md`](docs/TENANTS.md)\n\n## Structure\n\nThe cluster uses Flux GitOps to reconcile the state of the cluster with the single source of truth stored in this repository and published as an OCI image. KSail is used for local development, CI/CD testing, and production deployments. For prod, nodes are provisioned on Hetzner Cloud by KSail's native Hetzner provider, which also installs the Hetzner CCM and CSI drivers.\n\nAll environments use the Talos Kubernetes distribution. Local development and CI use Talos with the Docker provider; prod uses Talos with the Hetzner provider.\n\nThe cluster configuration is stored in the `k8s/*` directories where the structure is as follows:\n\n- [`clusters/`](k8s/clusters): Contains the cluster specific configuration for each environment.\n  - [`base`](k8s/clusters/base): Contains the shared Flux Kustomizations with sentinel paths (`__CLUSTER__`, `__PROVIDER__`).\n  - [`local`](k8s/clusters/local): Contains the local cluster specific configuration.\n  - [`prod`](k8s/clusters/prod): Contains the production cluster specific configuration.\n- [`providers/`](k8s/providers): Contains the provider specific configuration.\n  - [`docker`](k8s/providers/docker): Contains the Talos+Docker specific configuration for local development.\n  - [`hetzner`](k8s/providers/hetzner): Contains the Talos+Hetzner specific configuration for production.\n- [`bases/`](k8s/bases): Contains the different bases that are used for the different clusters and providers.\n  - [`infrastructure`](k8s/bases/infrastructure): Contains the different infrastructure components that are used for the different clusters and providers.\n  - [`apps`](k8s/bases/apps): Contains the different apps that are used for the different clusters and providers.\n  - [`bootstrap`](k8s/bases/bootstrap): The foundational **bootstrap layer** (renamed from `variables/`). Holds the shared substitution variables (`variables-base` ConfigMap + SOPS-encrypted Secret) and cluster-scoped PriorityClasses (e.g. `platform-critical`), reconciled by the `bootstrap` Flux Kustomization before everything that `dependsOn` it.\n\n### Kustomize and Flux Kustomization Flow\n\n\u003e [!IMPORTANT]\n\u003e If you know of a different way to manage kustomize and flux kustomizations that results in less boilerplate code, please let me know. I am always looking for ways to improve the structure and make it more maintainable.\n\nTo support hooking into the kustomize flow for adding or modifying resources for a specific cluster, a specific provider, or shared across all clusters, the following structure is used:\n\n#### Kustomize Overlay Flow\n\nEach cluster environment references a provider overlay, which in turn patches the shared base resources:\n\n```mermaid\ngraph LR\n  subgraph \"Cluster-specific\"\n    local[\"clusters/local\"]\n    prod[\"clusters/prod\"]\n  end\n\n  subgraph \"Provider-specific\"\n    docker[\"providers/docker\"]\n    hetzner[\"providers/hetzner\"]\n  end\n\n  subgraph \"Shared\"\n    bases[\"bases/*\"]\n  end\n\n  local --\u003e docker\n  prod --\u003e hetzner\n  docker --\u003e bases\n  hetzner --\u003e bases\n```\n\n#### Flux Kustomization Dependency Chain\n\nFlux Kustomizations are reconciled sequentially. Each layer waits for the previous to become ready:\n\n```mermaid\ngraph TB\n  bootstrap[\"bootstrap\"]\n  controllers[\"infrastructure-controllers\"]\n  infra[\"infrastructure\"]\n  apps[\"apps\"]\n\n  controllers -- \"depends on\" --\u003e bootstrap\n  infra -- \"depends on\" --\u003e controllers\n  apps -- \"depends on\" --\u003e infra\n```\n\nThis means that for every Flux Kustomization applied to a cluster, there should be a corresponding resource folder in `providers/\u003cprovider-name\u003e/` or `bases/` that contains the manifests for that scope. For example, the `infrastructure` Flux Kustomization is backed by:\n\n- `k8s/providers/\u003cprovider-name\u003e/infrastructure/`\n- `k8s/bases/infrastructure/`\n\nThe Flux Kustomizations themselves live in `k8s/clusters/base/` (with sentinel `__CLUSTER__` / `__PROVIDER__` values in `spec.path`). Each `k8s/clusters/\u003ccluster-name\u003e/` overlay patches the `cluster-meta` ConfigMap with its `cluster_name` / `provider` and uses kustomize `replacements:` to rewrite those sentinels with the cluster's real values. Only the per-cluster `bootstrap/` directory holds cluster-specific manifests.\n\nSee [`docs/TEMPLATING.md`](docs/TEMPLATING.md) for the exact set of files a fork of this repo needs to edit to stand up its own instance.\n\nSee [`docs/TENANTS.md`](docs/TENANTS.md) for how to onboard a new GitOps **tenant** (an app that runs on the platform from its own repository).\n\n## Documentation\n\nDeeper guides and design notes live in [`docs/`](docs):\n\n- [`TEMPLATING.md`](docs/TEMPLATING.md) — the exact set of files a fork needs to edit to stand up its own instance.\n- [`TENANTS.md`](docs/TENANTS.md) — onboarding a new GitOps tenant (an app that runs on the platform from its own repository).\n- [`node-autoscaling.md`](docs/node-autoscaling.md) — how the Cluster Autoscaler is configured on Hetzner.\n- [`oidc-kubectl.md`](docs/oidc-kubectl.md) — authenticating `kubectl` against the cluster via OIDC.\n- [`progressive-delivery.md`](docs/progressive-delivery.md) — Flagger Gateway API canary deployments and the per-app onboarding recipe.\n- [`runtime-security.md`](docs/runtime-security.md) — Tetragon runtime security.\n- [`rwx-storage.md`](docs/rwx-storage.md) — Longhorn replicated / RWX storage.\n- [`secret-rotation.md`](docs/secret-rotation.md) — the secrets architecture (SOPS → OpenBao → External Secrets) and rotation design.\n- [`dr/`](docs/dr) — disaster-recovery runbooks (backup/restore drills, OpenBao crypto custody, Velero + CloudNativePG, alerting).\n\n## Monthly Cost\n\n\u003e [!NOTE]\n\u003e Prices are approximate and may be outdated.\n\n| Item                      | No. | Per unit | Total in Actual | Total in $ |\n| ------------------------- | --- | -------- | --------------- | ---------- |\n| Cloudflare Domains        | 2   | $0,87    | $1,74           | $1,74      |\n| Hetzner CX33 (prod)       | 6   | €6,49    | €38,94          | $44,21     |\n| Hetzner Cloud LB LB11 (prod) | 1 | €5,39   | €5,39           | $6,12      |\n| Total                     |     |          |                 | $52,07     |\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=devantler-tech/platform\u0026type=Date)](https://star-history.com/#devantler-tech/platform\u0026Date)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevantler-tech%2Fplatform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevantler-tech%2Fplatform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevantler-tech%2Fplatform/lists"}