infraweaver/architecture
Architecture
Four models explain almost everything InfraWeaver does. If these make sense to you, the rest of the platform is predictable.
The topology a default install produces
CLUSTER TOPOLOGY — DEFAULT INSTALL SYNTHETIC
Model 1: the GitOps loop closes inside your cluster
This is the design decision the rest follows from. GitHub is a template source, read once. After the install, the cluster's own git server is the only thing ArgoCD watches.
- GitHub, the templateCloned exactly once, at deploy time. Nothing in the running platform depends on it staying available, or on you keeping a token.
- Onedev, your git server, in the clusterPrivate git, issues and CI. The generated manifests land here. This is now the source of truth.
- ArgoCD, the reconcilerWatches Onedev, roughly every three minutes, with self-heal on. Anything that drifts from git is put back.
- The cluster, the resultTo change an app you push a commit locally. Nothing leaves your network, and there is no CI queue in someone else's data centre between you and your homelab.
THE CONSEQUENCE PEOPLE MISS
Because self-heal is on, an in-cluster kubectl scale --replicas=0 is
reverted within minutes. The durable way to stop something is to declare it in git, or
to use the console's power switch, which pauses automated sync and records the previous
replica counts before scaling down.
The same property is why the platform recovers from a bad afternoon: whatever you broke by hand, git still describes what should exist.
Model 2: no secret is ever committed
Every credential the platform needs is generated at deploy time and written to OpenBao. The repository contains placeholders, and a CI gate fails the build if a real value appears in a Kubernetes Secret.
- .envExists only at deploy time on the machine running the installer. Gitignored, never committed.
- bootstrap-openbao.shInitialises and unseals OpenBao, then writes roughly 38 paths under
secret/platform/<service>. - OpenBaoRuns in the cluster. Vault-compatible API, fully open source. This is where credentials live for the rest of the platform's life.
- External Secrets OperatorAn
ExternalSecretin a namespace pulls a named property from a named path and materialises a real Kubernetes Secret next to the workload. - The podConsumes it as an environment variable or a mounted file, and knows nothing about OpenBao.
WHAT AN ExternalSecret LOOKS LIKE
Reading a value back is deliberate: vault kv get -field=<key>
secret/platform/<service>, or through the OpenBao UI on the internal tier.
Model 3: two access tiers, enforced at the ingress
PUBLICOPEN
auth.<domain> and console.<domain>. Reachable from
the internet, behind Authentik, rate limited, with security headers applied by a shared
Traefik middleware.
INTERNALIP-GATED
*.int.<domain> covers ArgoCD, OpenBao, Onedev, Longhorn and Grafana. A Traefik
middleware rejects any request whose source address is outside your configured ranges.
Resolving the name gets an attacker nothing.
THE PIECES THAT MAKE THAT WORK
- MetalLB hands out real IPsBare metal has no cloud load balancer, so MetalLB assigns addresses from a range you own. Traefik takes one, CoreDNS takes another.
- Traefik routes by hostOne ingress for HTTP, HTTPS, gRPC and WebSocket traffic, with middleware chains for auth, rate limits and headers.
- cert-manager keeps one wildcard freshACME DNS-01 against your provider, so every subdomain is covered without a certificate per service.
- CoreDNS answers on its own VIPResolves your domain to cluster addresses so internal traffic never leaves the LAN to come back in.
Model 4: the floor is admission policy, not documentation
Kyverno ships enabled. These are not recommendations in a README; a workload that violates them is rejected at admission.
| Policy | Effect |
|---|---|
| require-non-root | A container that would run as root is refused |
| disallow-privileged-containers | No privileged pods, anywhere |
| disallow-privilege-escalation | allowPrivilegeEscalation must be false |
| require-drop-all-capabilities | Every capability dropped unless explicitly re-added |
| disallow-host-namespaces | No host PID, IPC or network namespaces |
| disallow-hostpath-volumes | No mounting the node's filesystem into a pod |
| disallow-latest-tag | An image without a real tag is refused, so a rollback means something |
| require-memory-request-and-limit | Unbounded memory is not a deployment strategy |
| generate-default-deny-cnp | Every namespace is fitted with a default-deny network policy the moment it exists |
That last one is the important one. A new namespace does not start in a default-allow posture just because nobody remembered to write a policy for it: intra-namespace traffic, cluster DNS, ingress from Traefik and metrics scraping are allowed, and nothing else is.