Load values from Kubernetes Secrets and ConfigMaps into your Varlock configuration.
This plugin is read-only. It performs get requests on Secrets and ConfigMaps in a configured namespace and surfaces the values to your .env schema — nothing more. It does not create, update, or delete cluster resources, generate or template manifests, watch for changes, or manage deployments.
Typical use cases:
- Local development — pull dev/staging Secrets and ConfigMaps from a cluster into your local app without copying values by hand
- In-cluster runtime — read additional Secrets/ConfigMaps at runtime that aren't already mounted into the pod via
envFrom/valueFrom - CI/CD — read Secrets/ConfigMaps from a cluster using an explicit service account token
Deeper Kubernetes integration is on our radar. If you have ideas, a use case this plugin doesn't cover, or feedback from running it in production, come chat on Discord — we'd love to hear from you.
- Zero-config local development using your default kubeconfig
- In-cluster authentication using the pod's mounted service account
- Explicit auth via cluster API URL + bearer token (CI/CD, deployed apps)
- Fetch individual keys from Secrets and ConfigMaps
- Bulk-load whole Secrets or ConfigMaps with
@setValuesBulk - Configurable default Secret / ConfigMap so common cases don't repeat themselves
- Multiple plugin instances for different namespaces or clusters
- Auto-decode base64 Secret values and ConfigMap
binaryData
npm install @varlock/kubernetes-pluginThen load it from your .env.schema:
# @plugin(@varlock/kubernetes-plugin)
For local development, just initialize the plugin and it will use your default kubeconfig ($KUBECONFIG or ~/.kube/config):
# @plugin(@varlock/kubernetes-plugin)
# @initKubernetes(namespace=default)
Inside a pod, omit kubeconfig-related settings and the plugin will use the mounted service account credentials at /var/run/secrets/kubernetes.io/serviceaccount/.
For deployments without a kubeconfig, provide the API server URL and a bearer token directly:
# @plugin(@varlock/kubernetes-plugin)
# @initKubernetes(
# namespace=default,
# clusterServer="https://kubernetes.example.com:6443",
# token=$KUBERNETES_TOKEN
# )
# ---
# @type=kubernetesBearerToken @sensitive
KUBERNETES_TOKEN=
See Kubernetes Setup below for how to mint a long-lived service account token.
You can also pass a full kubeconfig as a string (YAML or JSON) — useful when injecting credentials from a secret manager:
# @initKubernetes(kubeconfig=$KUBECONFIG_DATA)
# ---
# @sensitive
KUBECONFIG_DATA=
The plugin tries authentication methods in this order:
- Explicit cluster server + token — if
clusterServeris provided - Explicit kubeconfig — if
kubeconfigis provided (file path or raw YAML/JSON) - In-cluster service account — auto-detected via
KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT - Default kubeconfig —
$KUBECONFIGor~/.kube/config
You can override the active kubeconfig context with context=....
To read from multiple namespaces or clusters, register named instances:
# @initKubernetes(id=dev, namespace=dev)
# @initKubernetes(id=prod, namespace=prod, context=prod-cluster)
# ---
DEV_DATABASE_URL=k8sSecret(dev, app-secrets, DATABASE_URL)
PROD_DATABASE_URL=k8sSecret(prod, app-secrets, DATABASE_URL)
A Kubernetes Secret/ConfigMap is a named resource that holds a map of key/value pairs:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets # ← the resource name
data:
DATABASE_URL: cG9zdGdyZXM6... # ← keys inside the resource
API_KEY: c2VjcmV0LWtleQ==So fetching a value is always a two-level lookup: which resource (name), and which key inside it (key).
Secret data values are base64-decoded automatically.
# Auto-infer key from item name (fetches app-secrets.data.DATABASE_URL)
DATABASE_URL=k8sSecret(app-secrets)
# Explicit key name
DB_URL=k8sSecret(app-secrets, DATABASE_URL)
# Named args also work
DB_URL=k8sSecret(name=app-secrets, key=DATABASE_URL)
Both data (strings) and binaryData (base64-decoded) fields are supported.
PUBLIC_API_HOST=k8sConfigMap(app-config)
API_HOST=k8sConfigMap(app-config, PUBLIC_API_HOST)
The idiomatic k8s pattern is one Secret + one ConfigMap per app. Set defaults on the init decorator and skip the name argument:
# @plugin(@varlock/kubernetes-plugin)
# @initKubernetes(
# namespace=default,
# defaultSecret=app-secrets,
# defaultConfigMap=app-config,
# )
# ---
# Both default to app-secrets / app-config and infer the key from the item name
DATABASE_URL=k8sSecret()
API_KEY=k8sSecret()
PUBLIC_API_HOST=k8sConfigMap()
# Override just the key while still using the default Secret
STRIPE_KEY=k8sSecret(key=stripe_api_key)
# Override the resource name to read from a different Secret
SHARED_TOKEN=k8sSecret(shared-secrets, AUTH_TOKEN)
You can mix positional and named arguments, but providing the same field both ways (e.g. k8sSecret(app-secrets, name=other)) is a schema error.
Use bulk loading when one Secret or ConfigMap contains several env vars. The bulk resolvers return a JSON object, which pairs with @setValuesBulk:
# @plugin(@varlock/kubernetes-plugin)
# @initKubernetes(namespace=default)
# @setValuesBulk(k8sSecretBulk(app-secrets), format=json)
# @setValuesBulk(k8sConfigMapBulk(app-config), format=json)
# ---
DATABASE_URL=
API_KEY=
PUBLIC_API_HOST=
Only items declared in your schema will be populated — extra keys in the resource are ignored. Bulk resolvers also pick up defaultSecret / defaultConfigMap, so the names can be omitted.
By default, fetching from a missing Secret/ConfigMap throws. To resolve missing resources or keys to undefined, set allowMissing=true:
# @initKubernetes(namespace=default, allowMissing=true)
# ---
# @required=false
OPTIONAL_FLAG=k8sConfigMap(feature-flags, NEW_UI)
Mark optional items with @required=false (or wrap with fallback()) so validation doesn't fail.
Initialize a Kubernetes plugin instance for the resolvers below.
Parameters:
id?: string(static) - Instance identifier for multiple instances (defaults to_default)namespace?: string- Kubernetes namespace. Defaults to the kubeconfig context namespace, the in-cluster service account namespace, ordefaultcontext?: string- Kubeconfig context name (overrides the current context)kubeconfig?: string- Path to a kubeconfig file, or raw kubeconfig YAML/JSON contentclusterServer?: string- Kubernetes API server URL for explicit auth (e.g.,https://kubernetes.example.com:6443)token?: string- Bearer token for explicit authskipTlsVerify?: boolean- Skip TLS verification (only applies to explicitclusterServer+tokenauth)allowMissing?: boolean- Missing resources or keys resolve toundefinedinstead of throwingdefaultSecret?: string- Default Secret name fork8sSecret()/k8sSecretBulk()defaultConfigMap?: string- Default ConfigMap name fork8sConfigMap()/k8sConfigMapBulk()
All resolvers accept positional and named arguments. The same field cannot be provided both ways.
Fetch a single key from a Secret. Values are base64-decoded automatically.
Signatures:
k8sSecret()- UsesdefaultSecret, infers key from item namek8sSecret(name)- Uses given Secret, infers key from item namek8sSecret(name, key)- Explicit name and keyk8sSecret(instanceId, name, key)- With explicit instancek8sSecret(name=..., key=..., id=...)- Same with named args
Fetch a single key from a ConfigMap. Both data and binaryData are supported.
Signatures: same shape as k8sSecret() — substitute k8sConfigMap and defaultConfigMap.
Fetch all keys from a Secret as a JSON object. Designed for @setValuesBulk(..., format=json).
Signatures:
k8sSecretBulk()- UsesdefaultSecretk8sSecretBulk(name)- Explicit Secret namek8sSecretBulk(instanceId, name)- With explicit instancek8sSecretBulk(name=..., id=...)- Same with named args
Same shape as k8sSecretBulk() — substitute k8sConfigMapBulk and defaultConfigMap.
kubernetesBearerToken- Kubernetes service account / API server bearer token (sensitive)
The identity used by the plugin (your kubeconfig user, an in-cluster service account, or an explicit token) needs read access to Secrets and/or ConfigMaps in the target namespace.
The minimum permissions are get on secrets and configmaps:
# varlock-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: varlock-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["get"]kubectl apply -f varlock-rbac.yamlFor least privilege, omit "secrets" if you only need ConfigMaps. Prefer namespaced Roles over ClusterRoles whenever possible.
kubectl create serviceaccount varlock-reader -n defaultBind the role to the service account:
# varlock-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: varlock-reader-binding
namespace: default
subjects:
- kind: ServiceAccount
name: varlock-reader
namespace: default
roleRef:
kind: Role
name: varlock-reader
apiGroup: rbac.authorization.k8s.ioThen use it in your pod spec:
spec:
serviceAccountName: varlock-reader
containers:
- name: app
image: my-app:latestFor CI/CD or other external use cases, create a long-lived service account token:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: varlock-reader-token
namespace: default
annotations:
kubernetes.io/service-account.name: varlock-reader
type: kubernetes.io/service-account-token
EOF
kubectl get secret varlock-reader-token -n default -o jsonpath='{.data.token}' | base64 -dkubectl auth can-i get secrets -n default
kubectl auth can-i get configmaps -n default --as=system:serviceaccount:default:varlock-reader- Verify the resource exists:
kubectl get secret <name> -n <namespace> - Double-check the namespace — the plugin only reads from the configured namespace
- Resource names are case-sensitive and namespace-scoped
- If the resource is genuinely optional, set
allowMissing=trueon@initKubernetes()and@required=falseon the item
- Check that the active identity has the required RBAC:
kubectl auth can-i get secrets -n <namespace> - For in-cluster use, verify the pod's
serviceAccountNameis set and bound to aRole/ClusterRolethat grantsgetonsecrets/configmaps - The error message includes the exact
Rolesnippet you need to grant
- Local dev: Run
kubectl config current-contextandkubectl get secretsto confirm your kubeconfig works - Explicit token: Verify the token isn't expired or revoked
- In-cluster: Check the pod's mounted service account token at
/var/run/secrets/kubernetes.io/serviceaccount/token
- Verify the cluster API URL is reachable from your machine/pod
- For clusters with self-signed certificates and explicit auth, set
skipTlsVerify=true(development only) - If
kubectlworks but the plugin doesn't, your kubeconfig may rely on an exec credential plugin (aws eks get-token,gke-gcloud-auth-plugin,kubelogin) — ensure the helper binary is on your$PATH
- The plugin uses (in order): explicit
namespaceargument, kubeconfig context namespace, pod's mounted SA namespace, ordefault - Force a specific namespace with
@initKubernetes(namespace=my-ns)