Every app in this cluster follows the same pattern: GitOps-managed by ArgoCD, credentials injected at runtime by ESO from Vault, HTTPS provided by the shared wildcard cert. The 14 Applications break down into two groups — platform infrastructure (deployed at wave -1 to 1) and user-facing services (wave 2+).
01 Platform infrastructure
These Applications form the foundation the user-facing apps depend on. They deploy first, in sync-wave order, before any user workloads start.
| Application | Namespace | Wave | Role |
|---|---|---|---|
| vault | security | −1 | Secrets backend — must be unsealed before ESO can sync. |
| argocd | argocd | 0 | Self-managing GitOps controller. Adopts imperatively-bootstrapped install. |
| longhorn | longhorn-system | 0 | Block storage for stateful workloads. Self-managing after bootstrap. |
| ingress-nginx | networking | 1 | Cluster-wide ingress controller. Serves wildcard TLS on port 443. |
| cert-manager | networking | 1 | Automated TLS — issues and renews the wildcard cert via DNS-01. |
| external-secrets | security | 1 | Bridges Vault to Kubernetes Secrets. ClusterSecretStore k8s-secrets. |
| monitor | monitor | 2 | kube-prometheus-stack: Prometheus, Grafana, Alertmanager. |
02 User-facing services
These Applications deploy at wave 2 or later, after the platform is healthy. All are exposed via ingress-nginx with the wildcard cert.
| Application | Namespace | URL | Description |
|---|---|---|---|
| auth | security | auth.in.alybadawy.com | Authentik v2026.5.2 — SSO, OAuth2, LDAP proxy. |
| db | db | pgadmin.in.alybadawy.com | PostgreSQL + Redis + pgAdmin + pg-dump CronJob. |
| cloud | cloud | cloud.in.alybadawy.com | Nextcloud — self-hosted cloud storage and collaboration. |
| immich | immich | photos.in.alybadawy.com | Immich — photo and video backup (Google Photos alternative). |
| whoami | whoami | whoami.in.alybadawy.com | HTTP echo service for testing ingress and TLS. |
| aly | websites | aly.in.alybadawy.com | Personal website (ghcr.io/alybadawy/aly:latest). |
| root | argocd | — | Root app-of-apps. Watches k8s/apps/ and manages all other Applications. |
03 Database stack
PostgreSQL and Redis run in the db namespace as raw Deployments (not operators). A pg-dump CronJob runs periodic dumps of all databases to the NAS. pgAdmin provides a web UI for database management.
POSTGRES_DB. This simplifies operations: one set of backups, one PVC to restore on rebuild.# check all db namespace resources
$ kubectl get all -n db
NAME READY STATUS RESTARTS AGE
pod/postgres-xxx 1/1 Running 0 5d
pod/pgadmin-xxx 1/1 Running 0 5d
pod/redis-xxx 1/1 Running 0 5d
# check the pg-dump cronjob (dumps to NAS)
$ kubectl get cronjob -n db
$ kubectl get jobs -n db --sort-by=.metadata.creationTimestamp
04 Immich
Immich runs two containers in the immich namespace: immich-server (main API + web UI) and immich-ml (machine learning for face recognition and CLIP image search). Photos are stored on a Longhorn-backed PVC that is included in the NAS backup schedule.
client-max-body-size: 100m setting is required — without it, nginx rejects uploads over 1 MiB with a 413 error. For very large Nextcloud uploads (>100 MB), the client must use chunked upload.05 Adding a new app
The pattern is the same for every app:
- Create
k8s/components/<name>/with namespace, deployment, service, ingress, and optionally ExternalSecret + PVC. - Create
k8s/apps/<name>.yaml— copy any existing Application manifest and updatepath,destination.namespace, andname. - Store credentials in Vault:
vault kv put secret/<name>-secret KEY=value ... - Commit and push. ArgoCD detects the new Application manifest (root app watches
k8s/apps/) and syncs automatically.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "3" # after db and platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/AlyBadawy/hl-beta
targetRevision: main
path: k8s/components/myapp
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true