==============================================================================
Source: https://docs.snapenv.io/integrations/kubernetes
==============================================================================
Kubernetes Operator
The SnapEnv operator automatically syncs variables into native Kubernetes Secrets. No init containers, no custom scripts — just a CRD and a controller.
Install
kubectl apply -f https://get.snapenv.io/operator/install.yamlInstalls the CRD, RBAC, and operator Deployment into the snapenv-operator namespace.
Quick start
1. Create a token Secret:
kubectl create secret generic snapenv-token \
--from-literal=token=snp_live_xxxxxxxxxxxx2. Create a SnapEnvSecret resource:
apiVersion: snapenv.io/v1alpha1
kind: SnapEnvSecret
metadata:
name: api-prod
namespace: default
spec:
tokenSecret: snapenv-token # K8s Secret in same namespace
project: <your-project-uuid>
env: prod
target: api-prod-env # name of the K8s Secret to create
syncInterval: 30m # fallback poll interval (default 30m)kubectl apply -f snapenvsecret.yaml
kubectl get ses api-prod # watch the Ready column3. Mount in your Deployment:
envFrom:
- secretRef:
name: api-prod-envAuto-restart deployments on change
Add the snapenv.io/sync-secret annotation to any Deployment or StatefulSet. When the operator detects that the variable content has changed (via content hash), it automatically triggers a rolling restart — no manual kubectl rollout restart needed.
The annotation value must match the spec.target name in your SnapEnvSecret.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
snapenv.io/sync-secret: "api-prod-env" # matches spec.target above
spec:
template:
spec:
containers:
- name: app
envFrom:
- secretRef:
name: api-prod-envThe operator computes a SHA-256 hash of all variable values after every sync. If the hash matches the previous sync, no restart is triggered. Both Deployment and StatefulSet are supported.
Helm chart integration
If your app deploys via a Helm chart, wire SnapEnv in as chart templates instead of hand-applying CRDs per environment. This pattern is in production use — gate everything behind one snapenv.enabled flag so a chart can fall back to plain Secret values when SnapEnv isn't used.
values.yaml:
snapenv:
enabled: false
tokenSecret:
create: true # false if you manage the token Secret separately
name: "" # defaults to "<release-name>-snapenv-token"
value: "" # snp_live_xxxxxxxxxxxx — generate from
# dash.snapenv.io → Integrations → Manage tokens
managedSecret:
name: "" # K8s Secret the operator creates — defaults to "<release-name>-secret"
project: "" # SnapEnv project UUID
env: prod
apiUrl: "" # leave empty for the default https://api.snapenv.io
syncInterval: 30m
autoReload: true # adds the snapenv.io/sync-secret annotation (see above)templates/snapenvsecret.yaml:
{{- if .Values.snapenv.enabled }}
{{- if .Values.snapenv.tokenSecret.create }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.snapenv.tokenSecret.name | default (printf "%s-snapenv-token" .Release.Name) }}
namespace: {{ .Release.Namespace }}
type: Opaque
stringData:
token: {{ .Values.snapenv.tokenSecret.value | quote }}
---
{{- end }}
apiVersion: snapenv.io/v1alpha1
kind: SnapEnvSecret
metadata:
name: {{ .Release.Name }}-snapenv
namespace: {{ .Release.Namespace }}
spec:
tokenSecret: {{ .Values.snapenv.tokenSecret.name | default (printf "%s-snapenv-token" .Release.Name) }}
project: {{ .Values.snapenv.project | quote }}
env: {{ .Values.snapenv.env | quote }}
target: {{ .Values.snapenv.managedSecret.name | default (printf "%s-secret" .Release.Name) }}
{{- if .Values.snapenv.syncInterval }}
syncInterval: {{ .Values.snapenv.syncInterval | quote }}
{{- end }}
{{- if .Values.snapenv.apiUrl }}
apiUrl: {{ .Values.snapenv.apiUrl | quote }}
{{- end }}
{{- end }}In your Deployment template, mount the managed Secret and — if autoReload is on — add the restart annotation to the pod template (not the Deployment's own metadata; it has to be on spec.template.metadata.annotations to trigger a rollout):
spec:
template:
metadata:
annotations:
{{- if and .Values.snapenv.enabled .Values.snapenv.autoReload }}
snapenv.io/sync-secret: {{ .Values.snapenv.managedSecret.name | default (printf "%s-secret" .Release.Name) | quote }}
{{- end }}
spec:
containers:
- envFrom:
- secretRef:
name: {{ .Values.snapenv.managedSecret.name | default (printf "%s-secret" .Release.Name) }}The CRD and operator are cluster-scoped, installed once (see Install above) — they aren't part of the app chart itself. Everything in this section is namespace-scoped and safe to template per-release.
Smart polling — only syncs when something changed
The operator uses HTTP conditional requests so polls are nearly free when nothing has changed.
How it works:
- On the first pull, the API returns the dotenv body plus an
ETagheader — a short hash of the variable content. - The operator stores that hash in
status.dataHash. - On every subsequent poll, the operator sends
If-None-Match: "<hash>"with the request. - If nothing changed, the API returns
304 Not Modifiedwith no body. The operator skips processing, skips the audit log, and re-schedules — the round trip costs almost nothing. - Only when variables actually change does the API return
200with the new content, and only then does the operator update the Secret (and restart linked Deployments).
This means you can poll frequently without hammering the API or cluttering the audit log. The default syncInterval of 30m is conservative — feel free to lower it:
spec:
syncInterval: 5m # cheap — only does real work when variables changeCRD reference
| Field | Required | Description |
|---|---|---|
tokenSecret | yes | K8s Secret name containing the snp_live_ token |
tokenKey | no | Data key inside tokenSecret (default: token) |
project | yes | SnapEnv project UUID |
env | yes | SnapEnv environment name |
target | yes | Name of the K8s Secret to create/update |
apiUrl | no | Override API URL (default: https://api.snapenv.io) |
syncInterval | no | Fallback poll interval, min 1m (default: 30m) |
Status fields
kubectl describe ses api-prod| Field | Description |
|---|---|
ready | true when last sync succeeded |
lastSyncTime | Time of last successful sync |
lastSyncError | Error message from last failed sync |
variableCount | Number of variables written |
dataHash | Content hash of the last synced variables — sent as If-None-Match on the next poll to get a cheap 304 when nothing changed |
Dashboard connection status
When the operator syncs, it sends X-SnapEnv-Client: operator with each pull request. The dashboard's Integrations page shows Connected · last sync X ago based on this.
Troubleshooting
kubectl logs -n snapenv-operator deploy/snapenv-operator -f
kubectl get ses -A
kubectl describe ses <name>| Error | Cause |
|---|---|
tokenSecret not found | The named K8s Secret doesn't exist in the namespace |
HTTP 401 | Token is invalid or revoked |
HTTP 403 | Token lacks access to the project or environment |
HTTP 404 | Wrong project UUID or environment name |
Update the operator
kubectl apply -f https://get.snapenv.io/operator/install.yaml
kubectl rollout restart deploy/snapenv-operator -n snapenv-operator