This repo contains helm and YAML for deploying Portainer into a Kubernetes environment. Chart versions follow a Portainer-aligned versioning scheme — see VERSIONING.md for details. Follow the applicable instructions for your edition / deployment methodology below:
Ensure you’re using at least helm v3.2, which includes support for the --create-namespace argument.
Install the repository:
helm repo add portainer https://portainer.github.io/k8s/
helm repo update
Install the helm chart:
helm install --create-namespace -n portainer portainer portainer/portainer
helm install --create-namespace -n portainer portainer portainer/portainer \
--set service.type=LoadBalancer
helm install --create-namespace -n portainer portainer portainer/portainer \
--set service.type=ClusterIP
For advanced helm customization, see the chart README
helm install --create-namespace -n portainer portainer portainer/portainer \
--set enterpriseEdition.enabled=true
helm install --create-namespace -n portainer portainer portainer/portainer \
--set enterpriseEdition.enabled=true \
--set service.type=LoadBalancer
helm install --create-namespace -n portainer portainer portainer/portainer \
--set enterpriseEdition.enabled=true \
--set service.type=ClusterIP
For advanced helm customization, see the chart README
If you’re not using helm, you can install Portainer using manifests directly, as follows
kubectl apply -f https://raw.githubusercontent.com/portainer/k8s/master/deploy/manifests/portainer/portainer.yaml
kubectl apply -f https://raw.githubusercontent.com/portainer/k8s/master/deploy/manifests/portainer/portainer-lb.yaml
kubectl apply- f https://raw.githubusercontent.com/portainer/k8s/master/deploy/manifests/portainer/portainer-ee.yaml
kubectl apply -f https://raw.githubusercontent.com/portainer/k8s/master/deploy/manifests/portainer/portainer-lb-ee.yaml
Portainer supports encrypting its internal database at rest using a key you provide. This feature requires chart version 2.39.0 or later.
⚠️ This is a non-reversible change. Once Portainer starts with encryption enabled the database will be encrypted and cannot be decrypted without the original key. Rolling back to a chart version older than 2.39.0 is not supported after encryption has been enabled.
⚠️ Back up your encryption key externally. A Kubernetes secret is not a sufficient sole backup. Store the key in a secure external system (e.g. HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) before enabling this feature. If the key is lost, the Portainer database cannot be recovered.
Create the secret in the same namespace as Portainer:
kubectl create secret generic portainer-key \
--from-literal=secret=<your-encryption-key> \
-n portainer
Then enable it via Helm:
helm upgrade -i -n portainer portainer portainer/portainer \
--set dbEncryption.existingSecret=portainer-key
For full details see the chart README.
The charts/manifests will create a persistent volume for storing Portainer data, using the default StorageClass.
In some Kubernetes clusters (microk8s), the default Storage Class simply creates hostPath volumes, which are not explicitly tied to a particular node. In a multi-node cluster, this can create an issue when the pod is terminated and rescheduled on a different node, “leaving” all the persistent data behind and starting the pod with an “empty” volume.
While this behaviour is inherently a limitation of using hostPath volumes, a suitable workaround is to use add a nodeSelector to the deployment, which effectively “pins” the portainer pod to a particular node.
The nodeSelector can be added in the following ways:
nodeSelector:
kubernetes.io/hostname: <YOUR NODE NAME>
Explicictly set the target node when deploying/updating the helm chart on the CLI, by including --set nodeSelector.kubernetes.io/hostname=<YOUR NODE NAME>
If you’ve deployed Portainer via manifests, without Helm, run the following one-liner to “patch” the deployment, forcing the pod to always be scheduled on the node it’s currently running on:
kubectl patch deployments -n portainer portainer -p '{"spec": {"template": {"spec": {"nodeSelector": {"kubernetes.io/hostname": "'$(kubectl get pods -n portainer -o jsonpath='{ ..nodeName }')'"}}}}}' || (echo Failed to identify current node of portainer pod; exit 1)