Both Deployments' rolling update briefly ran an old+new pod pair on the same node (node-role: storage), sharing the same config storage (qbittorrent's PVC, jdownloader's hostPath) -- both apps are effectively singletons that lock their config directory, so the new instance conflicted with the still-running old one: - qbittorrent: hit a known qbittorrent:5.2.0 image bug (linuxserver/ docker-qbittorrent#432) where a stale WebUI lockfile prevents the server from ever binding its port while a second instance is present -- surfaced as the new pod's readiness probe getting "connection refused" indefinitely. On top of that, my livenessProbe (30s/30s) was killing the container (exitCode 137) before qbittorrent had any chance to come up at all. Dropped the livenessProbe (readiness alone can never kill a container, only mark it not-ready) and loosened the readinessProbe timing. - jdownloader: the new pod's app process detected the old instance's lock in the shared /data/jdownloader hostPath and exited cleanly (exitCode 0) rather than run a duplicate copy -- nothing to do with probe timing. Both Deployments now use `strategy: Recreate` instead of the RollingUpdate default, so any future rollout fully stops the old pod before starting the new one -- this is the actual fix (matches the pattern immich.yaml already used). Applying this will briefly restart both currently-running pods to verify it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kubernetes Manifests
This directory contains raw Kubernetes resources grouped by service area. Most
subdirectories are consumed by Argo CD Applications in argocd/apps.
Directories
| Directory | Contents |
|---|---|
argocd/ |
App-of-apps, Argo CD ingress, Argo CD OIDC sealed secret |
authentik/ |
Authentik ingress, public ingress, proxy outpost, middleware, secret scripts |
cert-manager/ |
Internal and Let's Encrypt ClusterIssuers, Porkbun secret script |
core/ |
Dashy, Glances, CoreDNS custom config, CA installer |
gitea/ |
Gitea storage, backup, public ingress, runner and OIDC/admin secrets |
home-services/ |
HA gateway, AI gateway, Discord bot, service TLS, registry secret |
homeassistant/ |
Home Assistant external service and ingress |
media/ |
Jellyfin, qBittorrent, JDownloader, Immich |
monitoring/ |
Monitoring PVs, Grafana datasource, Grafana/Auth OIDC secrets |
network/ |
Pi-hole secrets, DDNS, Traefik dashboard, external host ingresses |
portfolio/ |
Portfolio deployment, ingress, registry pull secret |
Secrets
There are two patterns:
*-sealed.yamlfiles are safe to commit and are reconciled by Sealed Secrets.*.shscripts create runtime Secrets from.envdirectly in the cluster.
Use .env.example as the template for local secret names. kubeseal must point
at the in-cluster controller named sealed-secrets-controller in kube-system.
Regenerate committed sealed secrets with the matching script, then commit the resulting YAML. Runtime secret scripts should be run against the target cluster and should not produce committed plaintext.
Certificates
Internal services generally use internal-ca-issuer and home.arpa hostnames.
Public services use Let's Encrypt issuers and nik4nao.com hostnames.
The CA installer lives in core/ca-installer. Its ca-sync CronJob keeps the
served ca.crt and Apple mobileconfig in sync with the cert-manager CA secret.
DNS
Internal DNS records are configured in values/pihole.yaml and
values/pihole-debian.yaml. Add a new hostname to both files when adding a
home.arpa service.
Dashy
core/dashy.yaml defines the deployment and a placeholder ConfigMap. The real
dashboard config comes from config/dashy/conf.yaml:
bash manifests/core/apply-dashy-config.sh