Passwords, API tokens, private keys, and other sensitive values do not belong in container images, ConfigMaps, or readable configuration files in Kubernetes configurations. Use a Secret in Kubernetes or retrieve the values from an external secret manager.
In this guide, you create a Kubernetes Secret without placing the values in a YAML file or your shell history. You mount the Secret as files in a Pod, use a secure reference as an environment variable when necessary, and learn how to connect an external secret manager using External Secrets Operator.
- Kubernetes uses base64 to store binary data in a Secret, but users and workloads with sufficient permissions can read the original value because base64 is not encryption. Always restrict access with RBAC and grant access only to the Secrets that a user or workload needs.
- Use the commands for the operating system of the client on which you run kubectl. On Linux and macOS, use a POSIX shell such as Bash or Zsh. On Windows, use PowerShell. Commands that only invoke kubectl or Helm work the same in all three environments.
Choosing a storage method
Choose the storage method based on the content and how you manage the values:
- ConfigMap: use this only for non-sensitive configuration, such as a hostname, port number, or feature flag.
- Kubernetes Secret: use this for sensitive values that you manage within a single cluster. The values are stored in the Kubernetes API and can be made available as files or as environment variables through a reference.
- External secret manager: use this when you want to centrally manage, rotate, and audit Secrets, for example across multiple clusters. An operator can automatically synchronise the values with Kubernetes.
Never store a real password, token, or private key under data or stringData in a YAML file that you share or add to Git. A base64-encoded value is also easy to restore to its original value.
Requirements
For this guide, you need:
- A working Kubernetes cluster.
- kubectl, configured for the cluster.
- Optional: Helm 3 when installing External Secrets Operator.
- Optional: an external secret manager containing a stored Secret and a supported authentication method.
Creating a Kubernetes Secret
Step 1
Create a separate namespace for the examples in this guide:
kubectl create namespace secret-demo
Step 2
Create two temporary local files: one containing the database username and one containing the database password. Use the instructions for your client's operating system.
Linux and macOS (Bash or Zsh)
Use umask 077 to restrict permissions for new files to your own user. Then open the two files with nano:
umask 077
nano db-username
nano db-passwordEnter only the corresponding value in each file. Save the changes and close nano with ctrl + x > y > enter.
Create the Secret and immediately delete the local files:
kubectl create secret generic database-credentials --namespace secret-demo --from-file=username=db-username --from-file=password=db-password
rm db-username db-passwordWindows (PowerShell)
Create a unique temporary directory and grant access only to your current Windows user. PowerShell prompts for the password securely and writes both files as UTF-8 without a byte order mark (BOM):
$secretDirectory = Join-Path $env:TEMP "k8s-secret-$([guid]::NewGuid())"
New-Item -ItemType Directory -Path $secretDirectory | Out-Null
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
icacls $secretDirectory /inheritance:r /grant:r "$($currentUser):(OI)(CI)F" | Out-Null
$username = Read-Host 'Database username'
$password = Read-Host 'Database password' -AsSecureString
$credential = [PSCredential]::new('unused', $password)
$utf8 = [System.Text.UTF8Encoding]::new($false)
$usernameFile = Join-Path $secretDirectory 'db-username'
$passwordFile = Join-Path $secretDirectory 'db-password'
[System.IO.File]::WriteAllText($usernameFile, $username, $utf8)
[System.IO.File]::WriteAllText($passwordFile, $credential.GetNetworkCredential().Password, $utf8)
Remove-Variable username, password, credentialCreate the Secret and immediately remove the temporary directory and variables:
kubectl create secret generic database-credentials --namespace secret-demo --from-file="username=$usernameFile" --from-file="password=$passwordFile"
Remove-Item -LiteralPath $secretDirectory -Recurse -Force
Remove-Variable secretDirectory, currentUser, utf8, usernameFile, passwordFileThe arguments have the following functions:
- generic: creates a Secret of type Opaque for general sensitive data.
- --namespace secret-demo: creates the Secret in the namespace secret-demo.
- --from-file=username=...: stores the contents of the specified file under the key username. The second argument does the same for password.
Step 3
Check that the Secret exists without retrieving its values:
kubectl get secret database-credentials --namespace secret-demo
kubectl describe secret database-credentials --namespace secret-demoThe output of describe shows the names and sizes of the keys, but not their contents. Do not use an output format such as -o yaml in logs or shared terminal sessions: the displayed base64 values are not encrypted.
Step 4
Open the file secret-reader.yaml with your text editor:
Linux and macOS:
nano secret-reader.yamlWindows PowerShell:
notepad.exe secret-reader.yamlAdd the following configuration. The Secret is mounted in the container as read-only files:
apiVersion: v1
kind: Pod
metadata:
name: secret-reader
namespace: secret-demo
spec:
containers:
- name: app
image: busybox:1.37.0
command:
- /bin/sh
- -c
- sleep 3600
volumeMounts:
- name: database-credentials
mountPath: /run/secrets/database
readOnly: true
volumes:
- name: database-credentials
secret:
secretName: database-credentials
defaultMode: 0400Save the file and close the text editor. Then create the Pod:
kubectl apply -f secret-reader.yamlEach key in the Secret becomes a file under /run/secrets/database. readOnly: true prevents the container from modifying the files. defaultMode: 0400 grants read permission only to the owner of the files by default.
Step 5
Wait until the Pod is ready and then check that both files are readable. The command does not display their contents:
kubectl wait --for=condition=Ready pod/secret-reader --namespace secret-demo --timeout=60s
kubectl exec --namespace secret-demo secret-reader -- sh -c 'test -r /run/secrets/database/username && test -r /run/secrets/database/password && echo "Secret-bestanden zijn beschikbaar."'You see the message ‘Secret-bestanden zijn beschikbaar.’ when the mount works.
Using a Secret as an environment variable
Prefer mounted files when the application supports them. An environment variable may become visible in diagnostic output, crash reports, or a process that reads the environment. In addition, an existing container does not receive a changed Secret value through an environment variable; you must restart the Pod.
If the application supports only environment variables, reference a Secret using secretKeyRef and never place the sensitive value directly under value.
Open the file secret-env-reader.yaml with your text editor:
Linux and macOS:
nano secret-env-reader.yamlWindows PowerShell:
notepad.exe secret-env-reader.yamlAdd the following configuration:
apiVersion: v1
kind: Pod
metadata:
name: secret-env-reader
namespace: secret-demo
spec:
containers:
- name: app
image: busybox:1.37.0
command:
- /bin/sh
- -c
- test -n "$DATABASE_PASSWORD" && echo 'Secret environment variable is available.'; sleep 3600
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: database-credentials
key: passwordSave the file, close the text editor, and create the Pod:
kubectl apply -f secret-env-reader.yaml
kubectl logs secret-env-reader --namespace secret-demoThe Pod checks only whether the variable exists and does not write the sensitive value to the logs.
Connecting an external secret manager
Met External Secrets Operator synchronises values from HashiCorp Vault, Azure Key Vault, Google Secret Manager, and various other secret managers to Kubernetes Secrets. Connection and authentication configuration differs by provider.
By default, External Secrets Operator still creates a Kubernetes Secret. The external secret manager becomes the central source for management and rotation, but the synchronised value is also present in the cluster. Use a Secrets Store CSI Driver provider to mount values directly as files without synchronising them as a Kubernetes Secret.
Step 1
Add the official Helm repository and install External Secrets Operator:
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm upgrade --install external-secrets external-secrets/external-secrets --namespace external-secrets --create-namespace --waitThe optional arguments have the following functions:
- --install: installs the release if it does not yet exist, or updates an existing release.
- --namespace external-secrets: uses the namespace external-secrets for the operator.
- --create-namespace: creates the namespace automatically if it does not yet exist.
- --wait: waits until the installed resources are ready or the Helm timeout expires.
Then check the operator Pods:
kubectl get pods --namespace external-secrets
Step 2
Create a SecretStore for your provider in secret-demo. Use the External Secrets Operator provider documentation. Where possible, use a workload identity, Kubernetes ServiceAccount, or another short-lived identity instead of a static management token.
After applying the provider configuration, check that the SecretStore is ready:
kubectl get secretstore --namespace secret-demo
kubectl describe secretstore <secretstore-naam> --namespace secret-demoReplace <secretstore-naam> with the name from your SecretStore configuration. Continue only when the status shows that the connection is ready.
Step 3
Open the file database-external-secret.yaml with your text editor:
Linux and macOS:
nano database-external-secret.yamlWindows PowerShell:
notepad.exe database-external-secret.yamlAdd the following configuration and replace the placeholders with the names from your SecretStore and external secret manager:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: secret-demo
spec:
refreshInterval: 1h
secretStoreRef:
name: <secretstore-naam>
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: <secretnaam-in-provider>
property: username
- secretKey: password
remoteRef:
key: <secretnaam-in-provider>
property: passwordThe main fields have the following functions:
- refreshInterval: 1h: checks every hour whether the external value has changed.
- secretStoreRef: selects the namespaced SecretStore that the operator connects to.
- target.name: determines the name of the Kubernetes Secret created by the operator.
- remoteRef: maps a key and property from the external secret manager to a key in the Kubernetes Secret.
Save the file, close the text editor, and create the ExternalSecret:
kubectl apply -f database-external-secret.yaml
Step 4
Check the synchronisation without retrieving the values:
kubectl get externalsecret database-credentials --namespace secret-demo
kubectl get secret database-credentials --namespace secret-demoThe ExternalSecret must show the status ‘SecretSynced’ or ‘Ready’, and the Kubernetes Secret database-credentials must exist. Because the created Secret uses the same name and keys, you can mount it using the same Pod configuration from the earlier example.
After a rotation, the operator synchronises the new value according to refreshInterval. A mounted Secret volume is eventually updated, but the application must reload the file itself. An environment variable changes only after you restart the Pod.
Restricting access to Secrets
When managing Secrets, take the following measures:
- Following the Kubernetes guidelines for Secrets, grant users and ServiceAccounts only the minimum required permissions with RBAC. The list and watch permissions may also provide access to Secret contents.
- Grant access to the corresponding volume or key only to containers that need a Secret.
- Treat permissions to create Pods, Deployments, or similar workloads as sensitive permissions. A user who is allowed to create a Pod may be able to mount Secrets from the same namespace.
- Do not write Secret values to logs or display them in errors or diagnostic commands.
- Do not add files containing sensitive values, Kubernetes Secret manifests, or local .env files to Git.
- Rotate passwords, tokens, and keys regularly and immediately if you suspect that a value has been exposed.
Deleting Secrets
You can delete Secrets by deleting the namespace. Remove External Secrets Operator only when no other ExternalSecrets or SecretStores in the cluster depend on it. Delete a namespace as follows:
kubectl delete namespace secret-demoAlternatively, view the available Secrets:
kubectl get secrets -n secret-demoThen delete them:
kubectl delete secret <secretname>
You have securely made sensitive values available as a Kubernetes Secret and learned when an external secret manager is appropriate. Using Secret volumes, references, and minimum access permissions prevents passwords, tokens, and keys from ending up in configuration files or logs.