CI-deployed Nextcloud stack for the 3-node k3s-over-Cloudflare-WARP-Mesh cluster. Push to main triggers .github/workflows/deploy.yml on the self-hosted ARC runner arc-itguys-ro-nextcloud.
Cluster operational runbook (outside repo): ~/cloudflare-mesh-k3s-state.md.
Design: docs/superpowers/specs/2026-05-25-nextcloud-ci-deployment-design.md.
.github/workflows/ deploy.yml (push:main) + pr-checks.yml (PR dry-run)
docs/ design specs
helm/ Helm values (committed, secret-free): cert-manager + nextcloud
manifests/ raw k8s YAML (CI-applied: certificate, MariaDB, Valkey, PVCs, backup CronJob)
manifests/bootstrap/ one-time, applied manually by cluster-admin (namespace, ClusterIssuer, deployer RBAC)
secrets/ *.example templates only. Real values live in GitHub Actions Secrets.
manifests/60-nginx-tls-proxy.yaml is not CI-applied — it's a historical baseline of the nextcloud-only vhost. The live proxy is a shared :443 ingress serving 4 other apps; edit it out-of-band.
Plaintext never committed (.gitignore enforces).
Workflow on rotation:
- Update
secrets/<name>.yamllocally (gitignored). gh secret set <NAME>for each affected key (see.github/workflows/deploy.ymlfor the GH secret names).- Re-run deploy (
gh workflow run deploy.yml -f reconcile_all=true).
GitHub Actions Secrets currently set:
CF_API_TOKEN, NEXTCLOUD_ADMIN_USERNAME, NEXTCLOUD_ADMIN_PASSWORD, MARIADB_ROOT_PASSWORD, MARIADB_USERNAME, MARIADB_PASSWORD, VALKEY_PASSWORD, BACKUP_SSH_PRIVATE_KEY.
The CF API token must be scoped Zone:DNS:Edit + Zone:Zone:Read on itguys.ro only.
Cluster-admin runs these once. CI cannot grant itself rights.
kubectl apply -f manifests/bootstrap/00-namespace.yamlhelm install cert-manager jetstack/cert-manager -n cert-manager --create-namespace --version v1.20.2 -f helm/cert-manager-values.yaml- Create initial
cloudflare-api-tokenSecret in nscert-manager(CI's narrow Role canupdatebut notcreate):kubectl -n cert-manager create secret generic cloudflare-api-token --from-literal=api-token=<token> kubectl apply -f manifests/bootstrap/10-clusterissuer-letsencrypt.yamlkubectl apply -f manifests/bootstrap/11-ci-deployer-rbac.yaml- Install ARC scale set (see design doc §4.2 for the helm install command).
gh secret setfor all 8 secrets (see above).- Live-edit the shared nginx-tls-proxy ConfigMap to add the
nextcloud.itguys.rovhost (if not already present). - DNS
nextcloud.itguys.roA →100.96.0.2(DNS-only). - Cloudflare Gateway "Do Not Inspect" rule for the hostname (see Operational dependencies).
- Push to
main→ CI takes over.
- Routine change:
git push origin main→ CI applies path-relevant jobs. No further action needed; secrets job always runs, manifests/helm jobs run only if their paths changed. - Rollback:
gh workflow disable deploy.yml, thenhelm rollback nextcloud -n nextcloudmanually.
gh workflow run deploy.yml -f reconcile_all=true
When to use:
- Drift suspected (someone ran
kubectl edit/helm upgradeagainst the cluster out-of-band). - A GH Actions Secret was rotated but no repo file changed (path filter wouldn't trigger helm/manifests).
- Workflow was disabled and re-enabled; first run after re-enable.
Not needed after a normal merge — the merge push already triggers all relevant jobs via paths-filter (helm filter includes .github/workflows/deploy.yml, so a workflow edit forces helm reconcile too).
Access: https://nextcloud.itguys.ro (Mesh participants only).
- Cloudflare Gateway "Do Not Inspect" rule —
itguysorg has Gatewaytls_decryptenabled. Rule iddf440536-0b50-483d-b5d7-70cd7cbe6230(action: off,http.conn.hostname == "nextcloud.itguys.ro") exempts this host. If deleted: clients get the Gateway CA, Nextcloud Android app breaks, file traffic decrypted at Cloudflare's edge. Verify:echo | openssl s_client -connect 100.96.0.2:443 -servername nextcloud.itguys.ro 2>/dev/null | openssl x509 -noout -issuermust showO = Let's Encrypt. Any new host on :443 needs its own rule. Full context: design doc §5 amendment 2026-05-18. - PV reclaim policy = Retain — all three claims (
nextcloud-data,nextcloud-db,nextcloud-backups) patched topersistentVolumeReclaimPolicy: Retain(local-path defaults toDelete). An accidentalkubectl delete pvcdoes not wipe the hostPath. Real disk-loss recovery is the nightly acer rsync.