Skip to content

What you must change

This is the customization surface — every place alpstack is wired to our cluster, domain and cloud, that a different operator has to redirect. It's the answer to "if I wanted to deploy this, what would I need to change?".

How to read this

Couplings fall into three kinds:

  • :material-cog: Global — driven by values/global.yaml; set it once. The entire domain / realm / CA surface is global-driven — including literals Helm can't template in a subchart values.yaml, which are injected via the app-of-apps Application's inline helm.values.
  • :material-map-marker: Site-specific — a physical or cloud fact (an IP, a NodePort, an S3 endpoint) with no shared default. You replace it with your fact.
  • :material-link-variant: Entwined (upstream render) — lives inside the openDesk helmfile render (DB names, realm, Matrix server_name). Change these as a set via the render inputs, never by hand.

The tables below map where each coupling lives. Most are driven from values/global.yaml; the ones that need a manual edit — only genuinely per-site facts — are collected under Configuration values.

The cross-cutting couplings

Two couplings appear in every layer:

Coupling Ours Appears Kind
Base domain coeur.li ~160 literals across all four repos — ingress hosts, cert SANs, OIDC issuer URLs, mail, Matrix federation, TURN realm :material-cog: global.dns.baseDomain
Internal domain apps.int.coeur.li every internal HTTPRoute host, Dex issuer, OIDC redirect URIs, cookieDomain :material-cog: global.dns.appsDomain
Cluster name coeurli cert-manager coeurli-ca/coeurli-issuer, the clusters/coeurli/ overlay dir, 5 VMRule names, NetBird network name, cluster-digest subjects, each <name>-argocd UI domain :material-cog: global.cluster.name (+ derived caName/issuerName)
Cluster CA the coeurli-ca root cert ArgoCD's Dex OIDC oidc.config rootCA + headlamp's CA mount — one copy for every consumer :material-cog: global.cluster.rootCA

DNS spans nearly every chart and HTTPRoute. cluster.name drives the cert-manager CA and ClusterIssuer (the TLS trust chain), the VMRule names, and the clusters/coeurli/ overlay directory. The ops-proxy hostnames, the Dex issuer + redirect URIs, and oauth2-proxy's OIDC args are all global-driven (injected via the app-of-apps helm.values). One change isn't a value: rename the clusters/coeurli/ directory to clusters/<your-name>/ and update the app-of-apps path: accordingly.


Layer 1 — Talos

Repo: talos-cluster. Nearly everything site-specific here is a talosctl gen config input or a literal inside a patch-*.yaml. The patch bodies (CNI/proxy-off, disk encryption, kubelet-csr-approver, API-server tuning) are generic — only their embedded IPs/domains are yours.

What Ours Where Replace with
Cluster name coeurli talosctl gen config (README) :material-cog: your cluster name
API endpoint + SANs https://172.16.15.1:6443, api.int.coeur.li gen-config args + --additional-sans :material-map-marker: your VIP/endpoint
Install disk /dev/vda gen-config :material-map-marker: your disk
Control-plane IPs 172.16.15.190-192 apply-config, patch-controlplane-metrics-cp0{1,2,3}.yaml (one per CP) :material-map-marker: your CP IPs
Node subnet 172.16.15.0/24 patch-etcd.yaml, patch-kubelet.yaml, patch-kubelet-csr-approver.yaml (PROVIDER_REGEX) :material-map-marker: your subnet
Nameserver / NTP 172.16.15.1 patch-network.yaml, patch-timeserver.yaml :material-map-marker: your gateway
Dex OIDC issuer https://dex.apps.int.coeur.li patch-api-server.yaml :material-cog: your apps.int domain
Embedded CA cert coeurli-ca.crt (inline) + /var/coeurli-ca.crt path patch-api-server.yaml :material-map-marker: your cluster CA
Image-factory schematic IDs CP/worker schematic hashes README regenerate for your extension set

Reusable as-is

patch-no-cni-and-proxy.yaml and patch-disk-encryption.yaml need no edits. Cilium replaces kube-proxy, so there is no VIP and no custom pod/service CIDR — Talos defaults are used.


Layer 2 — Platform

Repo: platform-argocd. Most of the DNS and cluster-name couplings live here.

What Ours Where (representative) Replace with
Internal wildcard *.apps.int.coeur.li internal-gateway/values.yaml, every */templates/httproute.yaml :material-cog: global.dns.appsDomain
Dex redirect URIs 8 static https://<svc>.apps.int.coeur.li/... dex/values.yaml :material-cog: derive per-service from appsDomain
cert-manager CA/issuer coeurli-ca, coeurli-issuer cert-manager/templates/{ca,issuer}.yaml, internal-gateway/values.yaml, headlamp CA mount :material-cog: global.cluster.{caName,issuerName}
VMRule names coeurli-argocd, coeurli-databases, … monitoring-scrapes/templates/vmrule-*.yaml :material-cog: global.cluster.name prefix
Longhorn backup target s3://longhorn@<region>/, endpoint <s3-endpoint> longhorn Application helm.values, longhorn-s3-secret :material-cog: region ← global.backup.s3.region + :material-map-marker: endpoint (in the SealedSecret)
Admin / notification email admin@coeur.li, cluster-digest@coeur.li, talos-cluster@coeur.li cert-manager issuer, victoria-metrics-k8s-stack, cluster-digest :material-cog: global.notifications.adminEmail + baseDomain
Git owner + host your org, https://codeberg.org renovate/values.yaml, cluster-digest/values.yaml (the digest's issueRepo) :material-cog: global.git.{owner,platform,endpoint}
cluster-digest topology coeurli, "3 CP + 6 workers", IP string cluster-digest/templates/configmap.yaml (docstring, SYSTEM_PROMPT, email subject) :material-cog: global.cluster.name + a topology value

Network — the edge contract

These are physical LAN/WAN/firewall facts shared with the edge firewall, which is managed separately from the cluster. They are not extractable — you replace them with your own, and keep the NodePort numbers in lockstep with your firewall's DNAT rules.

Fact Ours Where
LAN segment 172.16.15.0/24 cilium-config/host-firewall.yaml, cilium/values.yaml, haproxy-ingress/values.yaml (proxy-protocol CIDR)
Cilium LB IP pool single /32 in the LAN cilium/templates/lb-ip-pool.yaml
HAProxy publish IPs 6 worker IPs + 2 OPNsense CARP peers haproxy-ingress/values.yaml
etcd scrape targets 3 CP IPs :2381 monitoring-scrapes/templates/etcd.yaml
Public WAN CIDR <public-WAN-CIDR> (ens3) cilium/values.yaml
NodePort map 30080/30443 (HTTP/S), 30025/30465/30587 (mail), 30993/30995 (IMAP/POP3S), 30478/30349 (TURN), 31443/31080 cilium-config/host-firewall.yaml, haproxy-ingress/values.yaml

The NodePorts are an interface, not a preference

A downstream firewall must DNAT the same NodePort numbers (or you change them in both places). Treat this table as the contract between the cluster and your edge.


Layer 3 — Addons

Repo: addons-argocd. Besides the usual DNS (netbird., vault., id., imap., smtp. + apps.int), this layer has the least portable couplings in the whole stack — because the NetBird mail-mesh pins cluster-assigned addresses by hand.

Pinned ClusterIPs + hand-rolled EndpointSlices + a Network UUID

These are values your cluster's CNI and NetBird server will assign differently — you cannot reuse ours, and each appears in several files that must stay in lockstep:

Coupling Value (yours will differ) Where (must match across all)
IMAP stub ClusterIP → backend <imap-stub-ClusterIP> → <dovecot-ClusterIP> netbird/templates/services-mail.yaml (Service + EndpointSlice), nb-resource-mail.yaml (address:)
SMTP stub ClusterIP → backend <smtp-stub-ClusterIP> → <postfix-ext-ClusterIP> same files
Health-canary ClusterIP <health-canary-ClusterIP> nb-resource-health.yaml (Service + EndpointSlice + NBResource)
NetBird Network UUID <network-UUID> (mail-router) nb-resource-mail.yaml, nb-resource-health.yaml — re-discover after you create the Network

The header comment in nb-resource-mail.yaml documents the UUID-lookup procedure. If you don't need mail-over-NetBird, delete this plumbing entirely (see Customise).

Other couplings Ours Where Replace with
OIDC clients netbird-public, netbird-management, vaultwarden netbird/values.yaml, vaultwarden/values.yaml, create-keycloak-objects.sh your Keycloak client IDs
Keycloak realm slug opendesk every id.coeur.li/realms/opendesk URL :material-cog: global.realm
Vaultwarden backup endpoint <s3-endpoint>, region <region>, bucket vaultwarden global.backup.s3.* (endpoint/region), vaultwarden/values.yaml (bucket) :material-cog: global.backup.s3.* + :material-map-marker: bucket
Backup age recipient committed public age key vaultwarden/values.yaml :material-map-marker: regenerate your own keypair
NetBird object names netbird-family, mail-router, group/policy names nb-*.yaml portable, but keep in 1:1 sync with your Keycloak groups

Layer 4 — openDesk

Repo: opendesk-argocd. Own-chart couplings are straightforward; the valuable (and hardest) category is the application content created inside the upstream helmfile render.

Own-chart couplings Ours Where Replace with
openDesk domain + host map coeur.li + 22 subdomains opendesk-certificates/values.yaml (cert SANs) :material-cog: global.dns.baseDomain + host map
Garage S3 host / region / buckets objectstore.coeur.li, us-east-1, 7 service buckets garage/values-prod.yaml :material-cog: domain + :material-map-marker: buckets
Backing-store buckets postgres-opendesk, mariadb-opendesk, garage (off-site) postgresql/, mariadb/, garage/ values-prod.yaml (buckets); global.backup.s3.endpoint :material-cog: endpoint ← global.backup.s3.endpoint + :material-map-marker: bucket names
ClusterIssuer coeurli-issuer garage/templates/Ingress.yaml :material-cog: global.cluster.issuerName
TURN realm / host coeur.li, turns.coeur.li coturn realm + certificate.dnsName (injected via the app-of-apps helm.values) :material-cog: global.dns.baseDomain

Network (Layer 4)

Same edge-contract rules as Layer 2 — all :material-map-marker: site-specific:

  • coturn pins node hostnames + node/WAN IPs (node01/node02, 172.16.15.200/201, <public-WAN>) and shared NodePorts 30478/30349 — coturn-fw{1,2}/values.yaml.
  • Mail NodePorts 30993/30995/30025/30587/30465 — generate-argocd-objects.sh, values.yaml.gotmpl; your MX + firewall depend on these being stable.
  • WOPI/Collabora allow-CIDR 172.16.0.0/12 — values.yaml.gotmpl.

Component toggles

Every optional component is gated by a flag in that repo's values/global.yaml, under global.components:

global:
  components:
    directoryImporter:
      enabled: true

The app-of-apps renders a component's ArgoCD Application (and, in platform-argocd, its namespace, NetworkPolicies, and quota) only when its flag is true. Setting a flag to false prunes the component and everything it manages. All flags default to true, so an existing cluster is unaffected — the toggles are for composing a deployment that omits a component.

Repo Flag(s) Gates
platform-argocd opendesk the whole openDesk app layer — the opendesk / opendesk-addons / opendesk-prod / opendesk-argocd namespaces. Set false for a base cluster with no openDesk.
platform-argocd postgresOperator / mariadbOperator / garageOperator the Percona Postgres, MariaDB, and Garage (S3) operators. Each is independent of opendesk (default true), so a different deployment can run any DB/storage engine for a non-openDesk component. Here postgres is shared (authentik / netbird / vaultwarden + openDesk) while mariadb + garage happen to serve only openDesk.
platform-argocd + addons-argocd authentik / netbird / vaultwarden each component's namespace + operator (platform-argocd) and its workload + authentik blueprint entries (addons-argocd)
platform-argocd + opendesk-argocd imapsync the imapsync IMAP-migration helper — its namespace (platform-argocd) and its workload (opendesk-argocd)
opendesk-argocd directoryImporter the identity-federation importer (disable if you have no upstream IdP)
opendesk-argocd mautrixWhatsapp the Matrix ↔ WhatsApp bridge

authentik, netbird, and vaultwarden span both the platform-argocd and addons-argocd instances (namespace/operator in platform-argocd, workload in addons-argocd); imapsync spans platform-argocd (namespace) and opendesk-argocd (workload). Set each spanning flag the same in both repos. The core operators (cert-manager, longhorn, sealed-secrets) are mandatory and have no toggle.

Prune safety: flipping a flag to false triggers an ArgoCD prune. For a stateful component (databases, mail, storage) that is data loss. The toggles are meant for composing a new deployment that never had the component — not for removing one from a running cluster.

Identity & operational assumptions

Cross-cutting, mostly :material-map-marker: document-only — a different operator substitutes their own:

  • Git owner alpstack on codeberg.org — already templated for the source repo (global.sourceRepo.url); sibling repo URLs + Renovate/digest config still literal.
  • Secrets vault — we use Psono; the bootstrap scripts reference it for master-password/DKIM/TURN custody. You use your own vault; only sealed secrets ever reach Git.
  • Owners / emails — admin@coeur.li, operator names (Adi, pesche), and a transient herzig.cc mailbox reference in the imapsync migration tool.

Next: Configuration values →