Move Hard-Coded Credentials into a Kubernetes Secret
In namespace default, Deployment api-server exists with hard-coded environment variables: DB_USER=admin DB_PASS=Secret123!
Live lab access
This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.
Create an account to continue →
On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Task
In namespace default, Deployment api-server exists with hard-coded environment variables:
DB_USER=adminDB_PASS=Secret123!
Requirements
- Create a Secret named
db-credentialsin namespacedefaultcontaining these credentials - Update Deployment
api-serverto use the Secret viavalueFrom.secretKeyRef - Do not change the Deployment name or namespace
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster.
Move credentials out of a manifest and into a Secret, three ways
A Secret is a namespaced object that holds small pieces of confidential data as key/value pairs. Pods consume it in three ways: one key per environment variable (valueFrom.secretKeyRef), every key at once (envFrom), or as files in a volume. Picking the right one, and proving that no plain-text value is left in the workload spec, is the core CKAD skill here. Keep one fact in mind throughout: the values are only base64-encoded, which is an encoding anyone can reverse, not encryption.
Secret quote-api-auth {client-id, client-secret}
├─ env[].valueFrom.secretKeyRef ──→ one variable per key, any name you choose
├─ envFrom[].secretRef ──→ all keys, key names become variable names
└─ volumes[].secret ──→ one file per key under the mountPath
1. Create the Secret and see what "encoded" means
kubectl create namespace prepium-tutorial
kubectl -n prepium-tutorial create secret generic quote-api-auth \
--from-literal=client-id='reports' \
--from-literal=client-secret='Qx7$pl!m'
kubectl -n prepium-tutorial get secret quote-api-auth -o yaml
kubectl -n prepium-tutorial get secret quote-api-auth \
-o jsonpath='{.data.client-secret}' | base64 -d
The single quotes stop your shell from expanding $ and !. Without them the stored value silently differs from what you typed. generic creates the default type, Opaque; other built-in types such as kubernetes.io/tls or kubernetes.io/dockerconfigjson enforce specific keys. The last command prints the clear value, which shows why Secrets are protected by RBAC and, on real clusters, encryption at rest: by default they are stored unencrypted in etcd.
2. Reference single keys as environment variables
Save this as ckad-secret-deploy.yaml. The env entries use secretKeyRef, the pattern to choose when the application expects particular variable names or needs only some keys. It also lets you rename: the key client-secret becomes QUOTE_CLIENT_SECRET.
apiVersion: apps/v1
kind: Deployment
metadata:
name: quote-api
namespace: prepium-tutorial
spec:
replicas: 1
selector:
matchLabels:
app: quote-api
template:
metadata:
labels:
app: quote-api
spec:
containers:
- name: api
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
env:
- name: QUOTE_CLIENT_ID # renamed: the key is client-id
valueFrom:
secretKeyRef:
name: quote-api-auth
key: client-id
- name: QUOTE_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: quote-api-auth
key: client-secret
volumeMounts:
- name: auth
mountPath: /etc/quote-auth
readOnly: true
volumes:
- name: auth
secret:
secretName: quote-api-auth
kubectl apply -f ckad-secret-deploy.yaml
kubectl -n prepium-tutorial rollout status deployment/quote-api
The alternative, envFrom with a secretRef, injects every key under its own key name (an optional prefix is prepended). It is shorter, but you lose control over names and pull in keys the app may not need.
3. Mount the same Secret as files
The volumes and volumeMounts part of the same manifest exposes each key as a file named after the key under /etc/quote-auth, mounted read-only. Mounted files are refreshed in place, eventually, when the Secret changes (unless mounted with subPath). Environment variables are not: a container sees new values only after it restarts.
4. Verify from inside and from the spec
kubectl -n prepium-tutorial exec deploy/quote-api -- printenv QUOTE_CLIENT_ID
kubectl -n prepium-tutorial exec deploy/quote-api -- ls /etc/quote-auth
kubectl -n prepium-tutorial get deploy quote-api \
-o jsonpath='{.spec.template.spec.containers[0].env}'
The first two prove the data arrives. The third proves the template contains only references and no value: fields holding the secret itself, which is the point of the exercise.
Common mistakes
- A wrong key name in
secretKeyRef. If the Secret exists but lacks that key, the Pod fails during startup; check its events. - Creating the Secret in another namespace. The reference is resolved in the Pod's own namespace, so the containers do not start until a Secret with that name exists there.
- Base64-encoding a value yourself and then passing it to
--from-literalorstringData. Those take plain text, so you end up double-encoded. - Leaving the old literal
value:next to the new reference. Remove it, then confirm with the jsonpath check.
| Example check | Evidence |
|---|---|
| Secret holds the right data | Decoded keys match the intended values |
| Workload references, not copies | Env entries use valueFrom.secretKeyRef with correct name and key |
| Values reach the process | printenv inside the new Pod |
| Rollout finished | New Pods are Ready after the template change |
Cleanup: kubectl delete namespace prepium-tutorial. References: Secrets, Distribute Credentials Securely Using Secrets, kubectl create secret generic. Next, try TLS Secrets or security contexts.
What this tests
Wire up configmaps, secrets, service accounts, RBAC, securityContext, and resource limits. On the CKAD exam, Application Environment, Configuration and Security tasks are graded purely on what you build in the cluster - not multiple choice - so the only way to get faster is to do them on a real cluster against a clock.
Practice it for real
The public description lets you study the task before signing up. Full access includes this live lab; the selected Task of the Week is free with an account. You solve the task in a real terminal, hit validate, and a programmatic checker scores exactly what you got right and wrong (with partial credit). You can open the solution while practising, then retry and use the failed checks to improve your score.