Kustomize generators are one of the most practical features for managing environment-specific Kubernetes configuration. In the previous article, we looked at
configMapGenerator. In this article, we will focus on secretGenerator.The behavior is very similar: Kustomize reads input values from files, .env files, or literals and generates a Kubernetes object during the build step. The difference is that secretGenerator creates a Secret instead of a ConfigMap..
This is useful when the generated data contains sensitive values such as database usernames, database passwords, API tokens, or private keys.
Kubernetes Secrets are intended for confidential data, while ConfigMaps are intended for non-confidential configuration.
What secretGenerator Does
A secretGenerator entry tells Kustomize to generate a Kubernetes Secret as part of the final manifest output.
Instead of manually writing a Secret manifest like this:
apiVersion: v1
kind: Secret
metadata:
name: local-config
type: Opaque
data:
DB_USERNAME: bG9jYWxfZGI=
DB_PASSWORD: bG9jYWxfcGFzc3dvcmQ=
you can keep local values in a file and let Kustomize generate the final Secret:
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
Kustomize will read .env.local, create a Kubernetes Secret, base64-encode the values, and optionally append a hash suffix to the generated Secret name.
Example Project Structure
For a development overlay, the structure can look like this:
k8s/
├── base/
│ ├── deployment.yaml
│ └── kustomization.yaml
└── overlays/
└── dev/
├── .env.local
├── db-init.js
├── nginx-deployment.yaml
└── kustomization.yaml
The .env.local file is intentionally placed inside the local development overlay because it contains values that are only needed for local development.
Local Secret Values in an .env File
A simple .env.local file can contain the database host, username, and password:
DB_HOST=postgres.dev.svc.cluster.local
DB_USERNAME=local_db
DB_PASSWORD=local_password
This file should normally not be committed to the repository when it contains real credentials.
Add it to .gitignore:
.env.local
.env.*.local
For a course demo, local placeholder values are fine. For real environments, especially staging and production, avoid committing secret material to Git.
Creating a Secret from an .env File
Inside overlays/dev/kustomization.yaml, we can define the generator:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
namePrefix: dev-
nameSuffix: -api
resources:
- nginx-deployment.yaml
labels:
- includeSelectors: true
pairs:
app.kubernetes.io/part-of: kustomize-demo
app.kubernetes.io/environment: dev
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
The important parts are:
| Field | Purpose |
|---|---|
secretGenerator | Defines one or more Secrets that Kustomize should generate. |
name | The logical Secret name used inside the kustomization. |
type | The Kubernetes Secret type. Opaque is used for generic key-value secrets. |
envs | Reads key-value pairs from one or more .env files. |
When Kustomize builds the overlay, it generates a Secret similar to this:
apiVersion: v1
data:
DB_HOST: cG9zdGdyZXMuZGV2LnN2Yy5jbHVzdGVyLmxvY2Fs
DB_PASSWORD: bG9jYWxfcGFzc3dvcmQ=
DB_USERNAME: bG9jYWxfZGI=
kind: Secret
metadata:
name: dev-local-config-api-7c9k8m6h5f
namespace: dev
type: Opaque
The exact hash suffix will depend on the generated data.
You Do Not Need to Base64-Encode Values Yourself
When you use secretGenerator, you provide the values as normal text in the source file:
DB_USERNAME=local_db
DB_PASSWORD=local_password
Kustomize generates the Secret and encodes the values into the data field of the Secret manifest.
This is easier and less error-prone than manually encoding each value yourself.
However, base64 encoding is not encryption. Anyone who can read the Secret manifest or retrieve the Secret from the Kubernetes API can decode the value.
For example:
echo 'bG9jYWxfcGFzc3dvcmQ=' | base64 --decode
Output:
local_password
So, while Secret is the correct Kubernetes object for sensitive data, you still need proper security controls around it.
The Default Hash Suffix
By default, generated Secrets and ConfigMaps receive a hash suffix:
local-config-7c9k8m6h5f
When namePrefix and nameSuffix are also configured, the final name can look like this:
dev-local-config-api-7c9k8m6h5f
The hash is based on the generated data. When the Secret content changes, Kustomize creates a Secret with a new name.
This behavior is important because it helps Kubernetes workloads notice that the configuration changed. When a Deployment references a generated Secret and Kustomize updates that reference to a new name, the Pod template changes. That Pod template change can trigger a rollout.
Disabling the Name Suffix Hash
You can disable the generated hash globally for generators in the current kustomization:
generatorOptions:
disableNameSuffixHash: true
With this option enabled, the generated Secret name remains stable:
dev-local-config-api
This can be convenient during local development or when another system expects a fixed Secret name.
However, be careful. Stable names also mean that a Secret content change might not automatically trigger a Deployment rollout. If your application reads values only at startup, existing Pods may continue running with the old values until they are restarted.
Use disableNameSuffixHash: true only when you have a clear reason.
Referencing Generated Secrets in a Deployment
A very useful part of Kustomize is that you usually reference the original generator name, not the final generated name.
For example, the generator name is:
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
In the Deployment, reference local-config:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-inside-dev
spec:
replicas: 1
selector:
matchLabels:
app: nginx-inside-dev
template:
metadata:
labels:
app: nginx-inside-dev
spec:
containers:
- name: nginx
image: nginx:1.27
envFrom:
- secretRef:
name: local-config
After running Kustomize, the reference is updated automatically:
envFrom:
- secretRef:
name: dev-local-config-api-7c9k8m6h5f
This means you should not hardcode the generated hash in your Deployment. Let Kustomize calculate the final name.
Mounting a Generated Secret as a Volume
Secrets can also be mounted as files inside a container.
Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-inside-dev
spec:
replicas: 1
selector:
matchLabels:
app: nginx-inside-dev
template:
metadata:
labels:
app: nginx-inside-dev
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: app-secrets
mountPath: /app/secrets
readOnly: true
volumes:
- name: app-secrets
secret:
secretName: local-config
Kustomize understands this standard Kubernetes Secret reference:
secretName: local-config
and transforms it into the generated name:
secretName: dev-local-config-api-7c9k8m6h5f
Referencing Generated ConfigMaps Works the Same Way
The same behavior applies to generated ConfigMaps.
For example, create a ConfigMap from a local JavaScript initialization file:
configMapGenerator:
- name: db-init-config
files:
- db-init.js
Then reference the original generator name in the Deployment:
volumes:
- name: db-config
configMap:
name: db-init-config
After Kustomize builds the overlay, the reference can become:
volumes:
- name: db-config
configMap:
name: dev-db-init-config-api-fbf87h9k6m
This is the key idea: the reference in your source YAML must match the generator name. Kustomize then updates the reference to include the prefix, suffix, and hash.
Why the Resource Must Be Included in resources
Kustomize can only transform resources that are part of the kustomization.
If the Deployment references a generated Secret but the Deployment file is not listed under resources, Kustomize will not process it.
Correct:
resources:
- nginx-deployment.yaml
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
Incorrect:
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
In the incorrect example, Kustomize generates the Secret, but it never sees the Deployment that references it. Therefore, it cannot update the reference inside the Deployment.
Running the Overlay
To preview the generated YAML:
kubectl kustomize overlays/dev
or:
kustomize build overlays/dev
To apply it to the cluster:
kubectl apply -k overlays/dev
To inspect the generated Secret:
kubectl get secret -n dev
To view the encoded values:
kubectl get secret dev-local-config-api-7c9k8m6h5f -n dev -o yaml
To decode one value:
kubectl get secret dev-local-config-api-7c9k8m6h5f \
-n dev \
-o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
Using literals, files, and envs
secretGenerator supports different input styles.
Literals
Use literals for small demo values:
secretGenerator:
- name: database-creds
type: Opaque
literals:
- DB_USERNAME=admin
- DB_PASSWORD=local-password
This is convenient for examples, but it is not a good idea for real credentials if the kustomization file is committed to Git.
Files
Use files when each file should become a Secret key:
secretGenerator:
- name: ssh-key
type: Opaque
files:
- id_rsa
The filename becomes the key, and the file content becomes the value.
Environment Files
Use envs when you already have simple key-value pairs:
secretGenerator:
- name: local-config
type: Opaque
envs:
- .env.local
This is often the cleanest option for local development.
Opaque Secret Type
Opaque is the default generic Secret type in Kubernetes. It is suitable for arbitrary application credentials such as:
- database usernames
- database passwords
- API tokens
- local development credentials
- internal application keys
Kubernetes also supports specialized Secret types, such as TLS Secrets and Docker registry Secrets. Use those specialized types when they match the use case. For ordinary application key-value pairs, Opaque is normally the correct choice.
Security Considerations
Using secretGenerator improves the workflow, but it does not remove the need for Secret management discipline.
Do Not Treat Base64 as Encryption
Kubernetes Secret values are base64-encoded in the manifest. That encoding is reversible and should not be treated as a security boundary.
Enable Encryption at Rest
By default, Secret data can be stored unencrypted in the Kubernetes API server backing store unless encryption at rest is configured.
For production clusters, enable encryption at rest for Secrets.
Use Least-Privilege RBAC
Be careful with permissions such as:
get secrets
list secrets
watch secrets
A user or service account that can list Secrets in a namespace can effectively retrieve sensitive data from that namespace.
Avoid Committing Real Secrets to Git
For local demos, .env.local can be useful. For production, prefer a proper Secret management workflow.
Common production approaches include:
- external Secret stores
- the Secrets Store CSI Driver
- External Secrets Operator
- sealed or encrypted GitOps workflows
- cloud-native secret managers
Be Careful with Logs and CI Output
Avoid commands that print Secret values in CI logs. Even base64-encoded values can be decoded later.
Common Mistakes
| Mistake | Why it is a problem | Better approach |
|---|---|---|
| Hardcoding the generated Secret name with the hash | The hash changes when the Secret data changes. | Reference the generator name, such as local-config. |
Forgetting to list the Deployment under resources | Kustomize cannot update a resource it does not process. | Add the Deployment YAML file to resources. |
| Disabling the hash without a rollout strategy | Existing Pods may continue using old values. | Keep the hash enabled or restart Pods explicitly. |
| Treating base64 as secure encryption | Base64 is reversible encoding. | Use RBAC, encryption at rest, and external Secret stores where appropriate. |
Using secretGenerator literals for production credentials in Git | The credentials become part of the repository history. | Use external Secret management or encrypted GitOps tooling. |
| Expecting custom resources to always update automatically | Kustomize knows common built-in Kubernetes references, but not every custom field. | Add transformer configuration or use replacements for custom references. |
Important Detail: Built-In Name References
Kustomize has built-in knowledge of many standard Kubernetes reference fields.
For ConfigMaps, this includes fields such as:
configMap:
name: db-init-config
and:
envFrom:
- configMapRef:
name: app-config
For Secrets, this includes fields such as:
secret:
secretName: local-config
and:
envFrom:
- secretRef:
name: local-config
This is why Kustomize can update these references when it adds a prefix, suffix, or hash.
For custom resources, Kustomize may not automatically know which fields are references. In that case, you may need a custom transformer configuration or another mechanism such as replacements.
Final Thoughts
secretGenerator is a clean way to generate Kubernetes Secrets without manually writing base64-encoded Secret manifests.
For local development, it works especially well with .env files that are not committed to the repository. For production, it can still be useful, but it should be combined with a secure Secret management strategy.
The most important practical rule is simple:
Reference the generator name in your manifests, and let Kustomize produce the final name.
That allows Kustomize to correctly apply namePrefix, nameSuffix, and the generated hash while keeping your source YAML readable and stable.
References
- Kubernetes: Declarative Management of Kubernetes Objects Using Kustomize - kubernetes[.]io/docs/tasks/manage-kubernetes-objects/kustomization/
- Kubernetes: Managing Secrets using Kustomize - kubernetes[.]io/docs/tasks/configmap-secret/managing-secret-using-kustomize/
- Kubernetes: Secrets - kubernetes[.]io/docs/concepts/configuration/secret/
- Kubernetes: Good practices for Kubernetes Secrets - kubernetes[.]io/docs/concepts/security/secrets-good-practices/
- Kustomize: Generator Options - github[.]com/kubernetes-sigs/kustomize/blob/master/examples/generatorOptions.md
- Kustomize: Transformer Configurations and Name References - github[.]com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md