Skip to content

==============================================================================

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 ​

bash
kubectl apply -f https://get.snapenv.io/operator/install.yaml

Installs the CRD, RBAC, and operator Deployment into the snapenv-operator namespace.

Quick start ​

1. Create a token Secret:

bash
kubectl create secret generic snapenv-token \
  --from-literal=token=snp_live_xxxxxxxxxxxx

2. Create a SnapEnvSecret resource:

yaml
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)
bash
kubectl apply -f snapenvsecret.yaml
kubectl get ses api-prod   # watch the Ready column

3. Mount in your Deployment:

yaml
envFrom:
  - secretRef:
      name: api-prod-env

Auto-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.

yaml
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-env

The 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:

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:

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):

yaml
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:

  1. On the first pull, the API returns the dotenv body plus an ETag header — a short hash of the variable content.
  2. The operator stores that hash in status.dataHash.
  3. On every subsequent poll, the operator sends If-None-Match: "<hash>" with the request.
  4. If nothing changed, the API returns 304 Not Modified with no body. The operator skips processing, skips the audit log, and re-schedules — the round trip costs almost nothing.
  5. Only when variables actually change does the API return 200 with 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:

yaml
spec:
  syncInterval: 5m   # cheap — only does real work when variables change

CRD reference ​

FieldRequiredDescription
tokenSecretyesK8s Secret name containing the snp_live_ token
tokenKeynoData key inside tokenSecret (default: token)
projectyesSnapEnv project UUID
envyesSnapEnv environment name
targetyesName of the K8s Secret to create/update
apiUrlnoOverride API URL (default: https://api.snapenv.io)
syncIntervalnoFallback poll interval, min 1m (default: 30m)

Status fields ​

bash
kubectl describe ses api-prod
FieldDescription
readytrue when last sync succeeded
lastSyncTimeTime of last successful sync
lastSyncErrorError message from last failed sync
variableCountNumber of variables written
dataHashContent 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 ​

bash
kubectl logs -n snapenv-operator deploy/snapenv-operator -f
kubectl get ses -A
kubectl describe ses <name>
ErrorCause
tokenSecret not foundThe named K8s Secret doesn't exist in the namespace
HTTP 401Token is invalid or revoked
HTTP 403Token lacks access to the project or environment
HTTP 404Wrong project UUID or environment name

Update the operator ​

bash
kubectl apply -f https://get.snapenv.io/operator/install.yaml
kubectl rollout restart deploy/snapenv-operator -n snapenv-operator

Built with SnapEnv