No description
Find a file
2026-09-05 15:45:07 +00:00
.forgejo remove ntfy: app, relay, bot notifications, grafana secrets 2026-08-25 08:14:00 +02:00
apps chore: bump lexsteen-site to 60cbab6eaa787f5acabe95b8ff847ccea37cccb6 2026-09-05 15:45:07 +00:00
argo-cd chore: alerting to Grafana UI; disable dex 2026-08-12 08:59:30 +02:00
docs Initialize GitOps repo: app-of-apps for MetalLB, registry, local-path 2026-08-10 10:59:36 +02:00
talos fix: RegistryMirrorConfig document format for Talos v1.13 multi-doc config 2026-08-10 12:31:40 +02:00
.gitignore chore: drop pycache, ignore it 2026-08-10 21:20:31 +02:00
README.md feat: deploy Alfredo Sasso portfolio 2026-09-03 22:04:05 +02:00

talos-infra

GitOps-driven infrastructure for a 3-node Kubernetes cluster on Talos Linux, virtualized on a Proxmox VE host.

Architecture

  • 3-node Talos Linux cluster (1 control plane + 2 workers), KVM VMs on Proxmox
  • Static IPs: control plane 10.120.1.150, workers 10.120.1.151 / 10.120.1.152
  • MetalLB (layer 2) hands out 10.120.1.153-253 to LoadBalancer services
  • No ingress controller: every exposed app gets its own MetalLB IP
  • External access via NetBird reverse proxy on a VPS, TLS terminated there
  • ArgoCD (app-of-apps) watches this repository and syncs the cluster
  • CI/CD: Forgejo Actions (act_runner inside the cluster) builds images and pushes to the in-cluster registry
push -> Forgejo (10.120.1.109) -> act_runner builds image
     -> in-cluster registry -> bump image tag in this repo
     -> ArgoCD syncs -> rollout

Repo layout

apps/
  root.yaml                 # app-of-apps entry point (applies everything here)
  metal-lb.yaml             # MetalLB controller (helm chart)
  metal-lb-config.yaml      # IP pool + L2 advertisement
  local-path-provisioner.yaml
  registry.yaml             # private container registry
  metal-lb-config/          # IPAddressPool + L2Advertisement
  registry/                 # registry:2 deployment, ClusterIP service, PVC
argo-cd/                    # bootstrap values for the ArgoCD helm install

Bootstrap (one-time, documented in docs/argo-cd-bootstrap.md)

  1. Install ArgoCD via helm with argo-cd/values.yaml
  2. kubectl apply -f apps/root.yaml
  3. ArgoCD syncs every Application in this repo from then on

Adding a new app

  1. Add apps/<app>.yaml (an ArgoCD Application) + an apps/<app>/ manifest dir
  2. Push — ArgoCD creates it; give the app a LoadBalancer Service for a MetalLB IP and point a NetBird reverse proxy entry at it

Rollbacks

Every CI run produces one image-tag commit in this repo (chained on the previous one), so rolling back to the previous release is a single revert:

git revert <image-bump-commit>
git push

ArgoCD picks the change up (webhook or the 60-second reconciliation window) and rolls the deployment back to the previously pinned image, which still exists in the registry. Reverting only the latest bump commit is safe; older reverts may restore an image tag that no longer exists in the registry.

Escape hatch: ArgoCD UI -> application -> Sync to a previous revision.

Update bot

A nightly workflow (.forgejo/workflows/update-bot.yaml) checks the image tags in apps/, reads release notes, and opens a pull request for each update. It merges automatically only when the update is a patch or minor bump on an already pinned tag and the risk check approves; first pins from floating tags (e.g. latest, 2) and major bumps always wait for a human.

The bot's config lives in .forgejo/update-bot/apps.json — adding an app to the bot is one entry there:

{
  "app": "name",
  "image": "owner/image",
  "manifest": "apps/name/deployment.yaml",
  "notes": "https://api.github.com/repos/owner/repo/releases",
  "floating": "latest"
}

Secrets used (Forgejo repo secrets): LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, INFRA_TOKEN. The risk check fails safe: no verdict means the pull request waits for a human.

Observability

  • kube-prometheus-stack (Prometheus + Grafana), trimmed for 2-core nodes
  • Grafana: http://10.120.1.156, admin password in the grafana-admin-credentials secret (created via kubectl, not in git)
  • Prometheus: 7d retention, 10Gi local-path volume
  • Quirk: the operator mounts the DB volume with subPath: prometheus-db, and the kubelet recreates that dir as root:0755 at pod start. A root init container (fix-db-perms) chmods it to 0777 on every start.

Secrets policy

Nothing secret is committed. Registry htpasswd, webhook tokens, and runner registration tokens are created directly as Kubernetes Secrets via kubectl.

Services

  • linkding — bookmark manager (10.120.1.157)
  • freshrss — RSS reader (10.120.1.158)
  • registry — container registry (10.120.1.154)
  • site — melonsoda.org (10.120.1.155)
  • lexsteen-site — Alfredo Sasso portfolio (10.120.1.159)

The update bot opens pull requests for each update; safe patch/minor bumps on pinned tags are merged automatically after the LLM risk check approves.