Portainer
Portainer
Web UI for managing the k3s cluster. Runs as portainer in the default namespace, deployed and kept in sync by ArgoCD.
Stack
| Item | Value |
|---|---|
| Image | portainer/portainer-ce:latest |
| Namespace | default |
| Replicas | 1 (strategy: Recreate) |
| Ports | 9000 (HTTP UI), 8000 (Edge agent) |
| Storage | PVC portainer-data, 2Gi, local-path storage class |
| ServiceAccount | portainer-sa-clusteradmin → ClusterRoleBinding to cluster-admin |
| GitOps | ArgoCD Application — repo Portainer.git, branch k3s |
| Reloader | reloader.stakater.com/auto: "true" |
RBAC
Portainer runs under a dedicated ServiceAccount bound to the built-in cluster-admin ClusterRole via portainer-crb-clusteradmin. This gives Portainer full read/write access to every resource in every namespace — required for it to manage the whole cluster through its UI, not just the default namespace it’s deployed in.
Networking
Service/portainer—ClusterIP, ports9000and8000, selectorapp=portainer.IngressRoute/portainer— Traefik, TLS vialocal-example-com-tls:www.portainer.prod-k3s.homelab.example→portainer:9000(no middleware)portainer.prod-k3s.homelab.example→portainer:9000(default-headersmiddleware)
The container runs with --http-enabled since TLS is terminated at Traefik, not by Portainer itself.
Storage
A single 2Gi local-path PVC (portainer-data) mounted at /data holds Portainer’s own database (users, endpoints, settings) — this is node-local storage, so the pod is implicitly tied to whichever node it first schedules on.
Deploy
1
kubectl apply -f serviceaccount.yaml -f pvc.yaml -f deployment.yaml -f service.yaml -f ingress.yaml
In practice this isn’t applied by hand — ArgoCD watches the k3s branch of the Portainer repo and syncs automatically.