Kubernetes Plugin
Our Kubernetes plugin enables loading values from Kubernetes Secrets and ConfigMaps using declarative instructions within your .env files.
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
It supports local kubeconfig, in-cluster service account credentials, and explicit API server + token authentication.
Features
Section titled “Features”- Zero-config local development - Automatically uses your default kubeconfig (
~/.kube/config) - In-cluster authentication - Auto-detects the mounted service account when running inside a pod
- Explicit auth - Provide a cluster API URL and bearer token directly
- Fetch Secret keys with
k8sSecret()(values are automatically base64-decoded) - Fetch ConfigMap keys with
k8sConfigMap()(includingbinaryData) - Bulk-load whole Secrets or ConfigMaps with
k8sSecretBulk()/k8sConfigMapBulk() - Auto-infer keys from environment variable names
- Multiple instances for different namespaces or clusters
- Read-only — the plugin never mutates cluster state
Installation and setup
Section titled “Installation and setup”In a JS/TS project, you may install the @varlock/kubernetes-plugin package as a normal dependency.
Otherwise you can just load it directly from your .env.schema file, as long as you add a version specifier.
See the plugins guide for more instructions on installing plugins.
# 1. Load the plugin# @plugin(@varlock/kubernetes-plugin)## 2. Initialize the plugin - see below for more details on options# @initKubernetes(namespace=default)Authentication options
Section titled “Authentication options”The plugin tries authentication methods in this priority order:
- Explicit cluster server + token - If
clusterServeris provided in@initKubernetes() - Explicit kubeconfig - If
kubeconfigis provided (file path or raw YAML/JSON string) - In-cluster service account - Auto-detected via
KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORTenv vars (set automatically inside pods) - Default kubeconfig - Loads from
$KUBECONFIGor~/.kube/config
Automatic authentication (Recommended for local dev)
Section titled “Automatic authentication (Recommended for local dev)”For local development, just initialize the plugin and the plugin will pick up your default kubeconfig automatically:
# @plugin(@varlock/kubernetes-plugin)# @initKubernetes(namespace=default)How this works:
- Local development: Uses your active
kubectlcontext from~/.kube/config(or$KUBECONFIG) - Inside a pod: Uses the pod’s mounted service account credentials at
/var/run/secrets/kubernetes.io/serviceaccount/
If your kubeconfig has multiple contexts, you can select one explicitly:
# @initKubernetes(namespace=default, context=my-dev-cluster)Explicit cluster server and token
Section titled “Explicit cluster server and token”If you don’t want to rely on a kubeconfig file — for example, in CI/CD or other deployed environments — provide the API server URL and a bearer token directly:
-
Create a service account and RBAC in your cluster (see Kubernetes Setup section below)
-
Wire up the credentials in your config. Add a config item for the token and reference it when initializing the plugin.
.env.schema # @plugin(@varlock/kubernetes-plugin)# @initKubernetes(# namespace=default,# clusterServer="https://kubernetes.example.com:6443",# token=$KUBERNETES_TOKEN# )# ---# @type=kubernetesBearerToken @sensitiveKUBERNETES_TOKEN= -
Set your credentials in deployed environments. Use your platform’s env var management UI to securely inject the bearer token.
For clusters that present self-signed or custom-CA certificates, you can disable TLS verification by adding skipTlsVerify=true to @initKubernetes(). This should generally be avoided outside of development.
Raw kubeconfig
Section titled “Raw kubeconfig”You can also pass an entire kubeconfig as a string (YAML or JSON) — useful when injecting credentials from a secret manager or platform env var:
# @initKubernetes(kubeconfig=$KUBECONFIG_DATA)# ---
# @sensitiveKUBECONFIG_DATA=The plugin auto-detects whether kubeconfig is a file path or raw content by looking for YAML/JSON markers.
Multiple instances
Section titled “Multiple instances”If you need to read from multiple namespaces or clusters, register multiple 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)Loading values
Section titled “Loading values”Once the plugin is installed and initialized, you can start adding config items that load values using the k8sSecret() and k8sConfigMap() resolver functions.
How Secrets and ConfigMaps are structured
Section titled “How Secrets and ConfigMaps are structured”A Kubernetes Secret or ConfigMap is a named resource that holds a map of key/value pairs:
apiVersion: v1kind: Secretmetadata: name: app-secrets # ← the resource namedata: DATABASE_URL: cG9zdGdyZXM6... # ← keys inside the resource API_KEY: c2VjcmV0LWtleQ==So fetching a value is always a two-level lookup: which Secret/ConfigMap (name), and which key inside it (key). The resolvers reflect this directly:
k8sSecret(name, key)— readname.data.keyk8sConfigMap(name, key)— readname.data.key(orname.binaryData.key)
When key is omitted, the plugin uses the config item’s name as the key — this works well when your Secret keys already match your env var names.
Secret keys
Section titled “Secret keys”The k8sSecret() function fetches a key from a Kubernetes Secret. Values stored in Secret data are base64-encoded; the plugin decodes them automatically before returning.
# Auto-infer key from item name (fetches "DATABASE_URL" from "app-secrets")DATABASE_URL=k8sSecret(app-secrets)
# Explicit key nameDB_URL=k8sSecret(app-secrets, DATABASE_URL)
# Named args also workDB_URL=k8sSecret(name=app-secrets, key=DATABASE_URL)
# With named instancePROD_DB_URL=k8sSecret(prod, app-secrets, DATABASE_URL)ConfigMap keys
Section titled “ConfigMap keys”The k8sConfigMap() function fetches a key from a Kubernetes ConfigMap. Both data (string) and binaryData (base64-encoded) fields are supported.
# Auto-infer key from item namePUBLIC_API_HOST=k8sConfigMap(app-config)
# Explicit key nameAPI_HOST=k8sConfigMap(app-config, PUBLIC_API_HOST)Default Secret/ConfigMap (Recommended for the common case)
Section titled “Default Secret/ConfigMap (Recommended for the common case)”The idiomatic Kubernetes deployment pattern is one Secret + one ConfigMap per app, mounted into the pod via envFrom. If your app follows this pattern, set defaultSecret and defaultConfigMap once on the init decorator and skip the name argument on every call:
# @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 nameDATABASE_URL=k8sSecret()API_KEY=k8sSecret()JWT_SECRET=k8sSecret()PUBLIC_API_HOST=k8sConfigMap()
# Override just the key while still using the default SecretSTRIPE_KEY=k8sSecret(key=stripe_api_key)
# Override the resource name to read from a different SecretSHARED_TOKEN=k8sSecret(shared-secrets, AUTH_TOKEN)You can mix positional and named arguments, but you can’t provide the same field twice — e.g., k8sSecret(app-secrets, name=other-secrets) is a schema error.
Bulk loading
Section titled “Bulk loading”Use bulk loading when a single Secret or ConfigMap contains several environment variables you want to map into your config. The bulk resolvers return all keys as a JSON object, which pairs naturally 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 — any extra keys in the Secret or ConfigMap are ignored.
Bulk resolvers also pick up defaultSecret/defaultConfigMap, so the names can be omitted entirely:
# @initKubernetes(defaultSecret=app-secrets, defaultConfigMap=app-config)# @setValuesBulk(k8sSecretBulk(), format=json)# @setValuesBulk(k8sConfigMapBulk(), format=json)Optional values
Section titled “Optional values”By default, fetching a key from a missing Secret/ConfigMap throws an error. If you want missing resources or keys to resolve to undefined instead, set allowMissing=true:
# @initKubernetes(namespace=default, allowMissing=true)# ---
# @required=falseOPTIONAL_FLAG=k8sConfigMap(feature-flags, NEW_UI)When allowMissing=true, also mark the corresponding items with @required=false (or wrap the resolver with fallback()) so validation does not fail.
Kubernetes Setup
Section titled “Kubernetes Setup”Required RBAC permissions
Section titled “Required RBAC permissions”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:
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: varlock-reader namespace: defaultrules: - apiGroups: [""] resources: ["secrets", "configmaps"] verbs: ["get"]Apply it with:
kubectl apply -f varlock-rbac.yamlService account for in-cluster use
Section titled “Service account for in-cluster use”When running inside a pod, the plugin uses the pod’s mounted service account. Create a dedicated service account and bind it to the role above:
-
Create a service account
Terminal window kubectl create serviceaccount varlock-reader -n default -
Bind the role to the service account
varlock-rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: varlock-reader-bindingnamespace: defaultsubjects:- kind: ServiceAccountname: varlock-readernamespace: defaultroleRef:kind: Rolename: varlock-readerapiGroup: rbac.authorization.k8s.ioTerminal window kubectl apply -f varlock-rolebinding.yaml -
Use the service account in your pod spec
deployment.yaml spec:serviceAccountName: varlock-readercontainers:- name: appimage: my-app:latest
Generate a bearer token for explicit auth
Section titled “Generate a bearer token for explicit auth”For CI/CD or other external use cases that need an explicit token, create a long-lived service account token:
# Create a token secret bound to the service accountkubectl apply -f - <<EOFapiVersion: v1kind: Secretmetadata: name: varlock-reader-token namespace: default annotations: kubernetes.io/service-account.name: varlock-readertype: kubernetes.io/service-account-tokenEOF
# Read the tokenkubectl get secret varlock-reader-token -n default -o jsonpath='{.data.token}' | base64 -dInject the resulting token into your deployment environment as KUBERNETES_TOKEN (or whatever name you wire up in @initKubernetes()).
Verify access
Section titled “Verify access”You can test that the configured identity can read the resources you expect:
# As your current kubeconfig userkubectl auth can-i get secrets -n defaultkubectl auth can-i get configmaps -n default
# As a specific service accountkubectl auth can-i get secrets -n default --as=system:serviceaccount:default:varlock-readerReference
Section titled “Reference”Root decorators
Section titled “Root decorators”@initKubernetes()
Section titled “@initKubernetes()”Initialize a Kubernetes plugin instance for k8sSecret(), k8sConfigMap(), k8sSecretBulk(), and k8sConfigMapBulk() resolvers.
Key/value args:
id(optional, static): Instance identifier for multiple instances, defaults to_defaultnamespace(optional): Kubernetes namespace to read from. Defaults to the kubeconfig context namespace, the pod’s mounted service account namespace (when in-cluster), ordefaultcontext(optional): Kubeconfig context name to use (overrides the current context)kubeconfig(optional): Path to a kubeconfig file, or raw kubeconfig YAML/JSON contentclusterServer(optional): Kubernetes API server URL for explicit auth (e.g.,https://kubernetes.example.com:6443)token(optional): Bearer token for explicit authskipTlsVerify(optional): Set totrueto skip TLS verification on the cluster certificate. Only applies when usingclusterServer+tokenallowMissing(optional): Iftrue, missing resources or keys returnundefinedinstead of throwingdefaultSecret(optional): Default Secret name used byk8sSecret()/k8sSecretBulk()when no name argument is provideddefaultConfigMap(optional): Default ConfigMap name used byk8sConfigMap()/k8sConfigMapBulk()when no name argument is provided
# @initKubernetes(namespace=default, defaultSecret=app-secrets, defaultConfigMap=app-config)Data types
Section titled “Data types”kubernetesBearerToken
Section titled “kubernetesBearerToken”Represents a Kubernetes bearer token used for API server authentication (typically a service account token). This type is marked as @sensitive.
# @type=kubernetesBearerTokenKUBERNETES_TOKEN=Resolver functions
Section titled “Resolver functions”k8sSecret()
Section titled “k8sSecret()”Fetch a single key from a Kubernetes Secret. Secret data values are base64-decoded automatically.
Arguments can be provided positionally or as named arguments, but the same field cannot be provided both ways.
Array args (positional):
instanceId(optional, static): Instance identifier to use when multiple plugin instances are initializedname(optional): Name of the Secret resource. Required unlessdefaultSecretis set on@initKubernetes()key(optional): Key inside the Secret’sdatato fetch. If omitted, uses the item key (variable name)
Named args:
id(optional, static): same as positionalinstanceIdname(optional): same as positionalnamekey(optional): same as positionalkey
# Auto-infer key from item nameDATABASE_URL=k8sSecret(app-secrets)
# Explicit key name (positional)DB_URL=k8sSecret(app-secrets, DATABASE_URL)
# Override just the key (uses defaultSecret from @initKubernetes)STRIPE_KEY=k8sSecret(key=stripe_api_key)
# Both positional and namedDB_URL=k8sSecret(name=app-secrets, key=DATABASE_URL)
# With instance IDPROD_DB=k8sSecret(prod, app-secrets, DATABASE_URL)k8sConfigMap()
Section titled “k8sConfigMap()”Fetch a single key from a Kubernetes ConfigMap. Both data and binaryData fields are supported.
Arguments can be provided positionally or as named arguments, but the same field cannot be provided both ways.
Array args (positional):
instanceId(optional, static): Instance identifier to use when multiple plugin instances are initializedname(optional): Name of the ConfigMap resource. Required unlessdefaultConfigMapis set on@initKubernetes()key(optional): Key inside the ConfigMap to fetch. If omitted, uses the item key (variable name)
Named args:
id(optional, static): same as positionalinstanceIdname(optional): same as positionalnamekey(optional): same as positionalkey
# Auto-infer key from item namePUBLIC_API_HOST=k8sConfigMap(app-config)
# Explicit key nameAPI_HOST=k8sConfigMap(app-config, PUBLIC_API_HOST)
# Named argsAPI_HOST=k8sConfigMap(name=app-config, key=PUBLIC_API_HOST)
# With instance IDDEV_HOST=k8sConfigMap(dev, app-config, PUBLIC_API_HOST)k8sSecretBulk()
Section titled “k8sSecretBulk()”Fetch all keys from a Kubernetes Secret as a JSON object string. Designed to be used with @setValuesBulk(..., format=json).
Array args (positional):
instanceId(optional, static): Instance identifier to use when multiple plugin instances are initializedname(optional): Name of the Secret resource. Required unlessdefaultSecretis set on@initKubernetes()
Named args:
id(optional, static): same as positionalinstanceIdname(optional): same as positionalname
# Uses defaultSecret from @initKubernetes# @setValuesBulk(k8sSecretBulk(), format=json)
# Explicit name# @setValuesBulk(k8sSecretBulk(app-secrets), format=json)
# With instance ID# @setValuesBulk(k8sSecretBulk(prod, app-secrets), format=json)k8sConfigMapBulk()
Section titled “k8sConfigMapBulk()”Fetch all keys from a Kubernetes ConfigMap as a JSON object string. Designed to be used with @setValuesBulk(..., format=json).
Array args (positional):
instanceId(optional, static): Instance identifier to use when multiple plugin instances are initializedname(optional): Name of the ConfigMap resource. Required unlessdefaultConfigMapis set on@initKubernetes()
Named args:
id(optional, static): same as positionalinstanceIdname(optional): same as positionalname
# @setValuesBulk(k8sConfigMapBulk(app-config), format=json)
# @setValuesBulk(k8sConfigMapBulk(prod, app-config), format=json)Troubleshooting
Section titled “Troubleshooting”Secret or ConfigMap not found (404)
Section titled “Secret or ConfigMap not found (404)”- Verify the resource exists:
kubectl get secret <name> -n <namespace>orkubectl get configmap <name> -n <namespace> - Double-check the namespace — the plugin reads only 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
Permission denied (403)
Section titled “Permission denied (403)”- 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
Authentication failed (401)
Section titled “Authentication failed (401)”- Local dev: Run
kubectl config current-contextand verify it points to the right cluster; runkubectl get secretsto confirm your kubeconfig works - Explicit token: Verify the token isn’t expired or revoked. Service account tokens created from
kubernetes.io/service-account-tokenSecrets are long-lived, but TokenRequest-issued tokens have shorter TTLs - In-cluster: Check the pod’s mounted service account token at
/var/run/secrets/kubernetes.io/serviceaccount/token
Connection refused or TLS errors
Section titled “Connection refused or TLS errors”- 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 using
kubectlworks but the plugin doesn’t, your kubeconfig may rely on an exec credential plugin (e.g.,aws eks get-token,gke-gcloud-auth-plugin,kubelogin) — ensure the helper binary is on your$PATH
Wrong namespace
Section titled “Wrong namespace”- The plugin uses (in order): the explicit
namespaceargument, the current kubeconfig context’s namespace, the pod’s mounted SA namespace, ordefault - To force a specific namespace, pass it explicitly:
@initKubernetes(namespace=my-ns)