Skip to main content

Managing Kubernetes Secrets with Vault and ConfigMaps

Securely manage Kubernetes Secrets and ConfigMaps in production using HashiCorp Vault, secret injection, encryption at rest, and RBAC access controls.

52 Terms

Overview and What You Will Learn

Hardcoding credentials inside Docker images or environment variables is one of the most dangerous and common security mistakes in Kubernetes deployments. This lab walks you through the correct production approach — managing sensitive credentials using Kubernetes Secrets with encryption at rest, injecting secrets dynamically using HashiCorp Vault, and separating non-sensitive configuration using ConfigMaps with proper RBAC controls.

By the end of this guide you will be able to:

  • Create and consume Kubernetes Secrets safely as environment variables and mounted files
  • Enable encryption at rest for Secrets stored in etcd
  • Deploy HashiCorp Vault on Kubernetes and inject secrets dynamically into pods
  • Use ConfigMaps for non-sensitive application configuration with automatic reload
  • Apply RBAC policies to restrict which pods and service accounts can access which secrets

Why This Matters in Production

In 2023, a major Indian fintech platform suffered a credential leak because database passwords were stored as plain environment variables in their Kubernetes Deployment YAML files — which were committed to a public GitHub repository. The blast radius included customer PII exposure and regulatory penalties.

At Zerodha, Razorpay, and PhonePe — platforms handling real financial transactions — secrets management is a compliance requirement, not just a best practice. A single leaked database credential or API key can compromise millions of user accounts. Every engineer deploying to Kubernetes must understand how to handle credentials correctly from day one.

Core Principles

The secrets management hierarchy in a production Kubernetes cluster:

◈ DIAGRAM
Application needs a credential (e.g., DB password, API key)
│
├─► Non-sensitive config (LOG_LEVEL, APP_ENV, PORT)
│ └─► ConfigMap → injected as env vars or mounted files
│
└─► Sensitive credentials (DB_PASSWORD, API_KEY, JWT_SECRET)
│
├─► Basic: Kubernetes Secret (base64, encrypted at rest)
│ └─► Acceptable for small teams with RBAC controls
│
└─► Advanced: HashiCorp Vault (dynamic secrets, audit logs)
└─► Required for compliance-grade production systems

Key rules every engineer must follow:

  • Never store sensitive values in ConfigMaps — they are plain text in etcd
  • Never commit Secret YAML files to Git — use Sealed Secrets or Vault instead
  • Always enable encryption at rest for the Secrets API group in etcd
  • Always use RBAC to restrict which service accounts can read which secrets
  • Rotate secrets regularly — Vault enables automatic rotation without pod restarts

Detailed Step-by-Step Practical Lab

Step 1 — Create and Use Basic Kubernetes Secrets
Bash
kubectl create secret generic db-credentials \
--from-literal=DB_HOST=10.0.1.50 \
--from-literal=DB_NAME=zerodha_trading \
--from-literal=DB_USER=rahul \
--from-literal=DB_PASSWORD=Tr@d3Secure#9182 \
-n production
# Verify the secret was created
kubectl get secret db-credentials -n production
# Inspect what keys are stored (values are base64 encoded)
kubectl describe secret db-credentials -n production
# Decode a specific value to verify
kubectl get secret db-credentials -n production \
-o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
Step 2 — Inject Secrets into Pods as Environment Variables
YAML
# deployment.yaml — inject secrets as env vars into the trading API
apiVersion: apps/v1
kind: Deployment
metadata:
name: trading-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: trading-api
template:
metadata:
labels:
app: trading-api
spec:
serviceAccountName: trading-api-sa # Dedicated service account with RBAC
containers:
- name: trading-api
image: registry.zerodha.in/trading-api:v4.2.1
envFrom:
- configMapRef:
name: app-config # Non-sensitive config
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_HOST
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: jwt-credentials
key: JWT_SECRET
Step 3 — Mount Secrets as Files for Certificate and Key Management
YAML
# Mount TLS certificate and private key as files inside the container
spec:
containers:
- name: trading-api
image: registry.zerodha.in/trading-api:v4.2.1
volumeMounts:
- name: tls-certs
mountPath: /etc/ssl/certs/app # Application reads certs from here
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: zerodha-tls-secret # Contains tls.crt and tls.key
defaultMode: 0400 # Read-only for owner only — critical
Security

Always set defaultMode: 0400 on secret volume mounts. The default mode 0644 makes private keys readable by all users on the container — a significant security risk.

Step 4 — Enable Encryption at Rest for Secrets in etcd

By default, Kubernetes Secrets are stored as base64 in etcd — not encrypted. Anyone with etcd access can read all secrets. Fix this:

YAML
# encryption-config.yaml — place on the control plane node
# Path: /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc: # AES-CBC encryption
keys:
- name: key1
secret: c2VjcmV0LWtleS0zMi1ieXRlcy1sb25nLWhlcmU= # base64 of 32-byte key
- identity: {} # Fallback for reading unencrypted secrets
Bash
# Generate a proper 32-byte encryption key
head -c 32 /dev/urandom | base64
# Apply to kube-apiserver by adding this flag to the apiserver manifest
# /etc/kubernetes/manifests/kube-apiserver.yaml
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# Verify all existing secrets are now encrypted by rewriting them
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Remember

Encryption at rest only protects secrets from direct etcd database access. It does not protect secrets from being read via the Kubernetes API by authorised users. RBAC controls who can read secrets via the API.

Step 5 — Deploy HashiCorp Vault on Kubernetes
Bash
# Add the HashiCorp Helm repository
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Install Vault in HA mode with integrated storage
helm install vault hashicorp/vault \
--namespace vault \
--create-namespace \
--set server.ha.enabled=true \
--set server.ha.replicas=3 \
--set server.dataStorage.size=10Gi
# Verify Vault pods are running
kubectl get pods -n vault
# NAME READY STATUS RESTARTS
# vault-0 0/1 Running 0 ← Not ready until initialized
# vault-1 0/1 Running 0
# vault-2 0/1 Running 0
# Initialize Vault — generates unseal keys and root token
kubectl exec -n vault vault-0 -- vault operator init \
-key-shares=5 \
-key-threshold=3 \
-format=json > vault-init-keys.json
# CRITICAL: Store vault-init-keys.json securely — losing it = losing all secrets
Security

Never store vault-init-keys.json on the same cluster or any version control system. Store unseal keys in separate secure locations — ideally split across different team members using Shamir's Secret Sharing.

Step 6 — Configure Vault Agent Injector for Automatic Secret Injection
Bash
# Enable Kubernetes authentication in Vault
kubectl exec -n vault vault-0 -- vault auth enable kubernetes
# Configure Vault to trust your cluster's service accounts
kubectl exec -n vault vault-0 -- vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc.cluster.local:443"
# Write a secret into Vault
kubectl exec -n vault vault-0 -- vault kv put secret/production/trading-api \
db_password="Tr@d3Secure#9182" \
api_key="rzp_live_xK9mN3pQ7wL2aB" \
jwt_secret="HS256-prod-secret-zerodha-2024"
# Create a Vault policy allowing read access to this path
kubectl exec -n vault vault-0 -- vault policy write trading-api-policy - <<EOF
path "secret/data/production/trading-api" {
capabilities = ["read"]
}
EOF
# Bind the Kubernetes service account to the Vault policy
kubectl exec -n vault vault-0 -- vault write \
auth/kubernetes/role/trading-api \
bound_service_account_names=trading-api-sa \
bound_service_account_namespaces=production \
policies=trading-api-policy \
ttl=1h
YAML
# deployment-vault.yaml — pod with Vault Agent sidecar injection
apiVersion: apps/v1
kind: Deployment
metadata:
name: trading-api
namespace: production
spec:
template:
metadata:
labels:
app: trading-api
annotations:
vault.hashicorp.com/agent-inject: "true" # Enable Vault sidecar
vault.hashicorp.com/role: "trading-api" # Vault role to use
vault.hashicorp.com/agent-inject-secret-config: "secret/data/production/trading-api"
vault.hashicorp.com/agent-inject-template-config: | # Template the secret as a file
{{- with secret "secret/data/production/trading-api" -}}
export DB_PASSWORD="{{ .Data.data.db_password }}"
export API_KEY="{{ .Data.data.api_key }}"
export JWT_SECRET="{{ .Data.data.jwt_secret }}"
{{- end }}
spec:
serviceAccountName: trading-api-sa
containers:
- name: trading-api
image: registry.zerodha.in/trading-api:v4.2.1
command: ["/bin/sh", "-c"]
args: ["source /vault/secrets/config && node server.js"]
Step 7 — Apply RBAC to Restrict Secret Access
YAML
# rbac-secrets.yaml — only the trading-api service account can read db-credentials
apiVersion: v1
kind: ServiceAccount
metadata:
name: trading-api-sa
namespace: production
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: trading-api-secret-binding
namespace: production
subjects:
- kind: ServiceAccount
name: trading-api-sa
namespace: production
roleRef:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
name: trading-api-secret-reader
Bash
kubectl apply -f rbac-secrets.yaml
# Verify the service account can only access its designated secrets
kubectl auth can-i get secret/db-credentials \
--as=system:serviceaccount:production:trading-api-sa \
-n production
# yes
kubectl auth can-i list secrets \
--as=system:serviceaccount:production:trading-api-sa \
-n production
# no
Tip

Always use resourceNames in your RBAC Role to restrict access to specific named secrets. Without it, the service account can read ALL secrets in the namespace — including other teams' credentials.

Production Best Practices & Common Pitfalls

  • Use External Secrets Operator as an alternative to Vault for teams already using AWS Secrets Manager or GCP Secret Manager — it syncs external secrets into Kubernetes Secrets automatically.
  • Never use kubectl get secret -o yaml on a production terminal that is being screen-recorded or shared. The base64 values are trivially decodable.
  • Rotate all secrets after any team member leaves the organisation. Vault makes this seamless with dynamic secrets that auto-expire.
  • Use Sealed Secrets (by Bitnami) if you must commit secret manifests to Git — it encrypts secrets with a cluster-specific key so the YAML is safe to store in version control.
  • Audit secret access regularly using Kubernetes audit logs — any unexpected get secret event from an unrecognised service account is a red flag.
Common Mistake

Using envFrom: secretRef to inject an entire secret as environment variables. This injects ALL keys including ones the application does not need, violating the principle of least privilege. Always inject only the specific keys your application requires using secretKeyRef.

Quick Reference & Troubleshooting Commands

Command Purpose
kubectl create secret generic <name> --from-literal=KEY=VALUE -n <ns> Create a secret from literal values
kubectl get secret <name> -n <ns> -o jsonpath='{.data.KEY}' | base64 --decode Decode a specific secret value
kubectl describe secret <name> -n <ns> List secret keys without revealing values
kubectl delete secret <name> -n <ns> Delete a secret
kubectl auth can-i get secret/<name> --as=system:serviceaccount:<ns>:<sa> -n <ns> Test RBAC access for a service account
vault kv get secret/production/trading-api Read a secret from Vault
vault kv put secret/production/trading-api key=value Write a secret to Vault
vault lease revoke -prefix secret/production/ Revoke all dynamic secrets under a path
kubectl get externalsecrets -n <ns> List External Secrets Operator synced secrets

Resources

Velero vs etcd Snapshot: Not a Real Choice

Velero vs etcd Snapshot: Not a Real Choice

Velero and etcd snapshots protect different layers of a cluster, not the same thing. Here's what each covers and why production DR needs both.

5 min read•Aug 2026
Istio Ambient vs Linkerd in 2026

Istio Ambient vs Linkerd in 2026

Istio Ambient killed the sidecar-tax argument. The real 2026 decision is waypoint topology and Buoyant's licensing shift, not features vs simplicity.

5 min read•Aug 2026
Nginx Ingress vs Traefik vs Gateway API in 2026

Nginx Ingress vs Traefik vs Gateway API in 2026

Ingress-nginx retired in March 2026. Here's how Traefik and the Gateway API actually compare as replacements — and why "just swap it" is the wrong frame.

5 min read•Aug 2026
OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno: Policy Engine in 2026

OPA Gatekeeper vs Kyverno compared for 2026 - Rego vs YAML, mutation maturity, operational overhead, and which policy engine fits your cluster.

5 min read•Aug 2026
Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize in 2026: Templating vs Patching

Helm vs Kustomize compared for 2026 - templating vs patching, Helm 4's new features, and why most production teams end up running both.

5 min read•Aug 2026
Prometheus vs Datadog vs New Relic: Real Costs

Prometheus vs Datadog vs New Relic: Real Costs

Prometheus, Datadog, and New Relic compared for 2026 - real pricing at scale, hidden cost drivers, and which fits a Kubernetes-heavy stack.

5 min read•Aug 2026
Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter for EKS in 2026

Cluster Autoscaler vs Karpenter compared for EKS in 2026 - provisioning speed, bin-packing, cloud support, and when each is the right default.

5 min read•Aug 2026
GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE vs EKS vs AKS in 2026: Which Fits Your Team?

GKE, EKS, and AKS compared for 2026 - control plane pricing, Autopilot vs Karpenter vs Node Auto Provisioning, and which platform actually fits your team.

5 min read•Aug 2026
K3s vs K8s vs MicroK8s in 2026

K3s vs K8s vs MicroK8s in 2026

K3s, full Kubernetes, and MicroK8s compared for 2026 - resource footprint, production readiness, and which fits edge, homelab, or cloud workloads.

5 min read•Aug 2026
Canary Deployments with Argo Rollouts & Flagger

Canary Deployments with Argo Rollouts & Flagger

Ship to 5% of users first and auto-rollback in minutes — a hands-on guide to canary deployments with Argo Rollouts and Flagger on Kubernetes.

5 min read•Jun 2026
OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry Explained: Metrics, Logs, Traces

OpenTelemetry unifies metrics, logs, and traces under one open standard — how it works, what it replaces, and how to instrument a service in 20 minutes.

5 min read•Jun 2026
Kubernetes Cost Optimization Without Breaking SLOs

Kubernetes Cost Optimization Without Breaking SLOs

Average Kubernetes CPU utilization across production clusters is 8%. Here is the complete 2026 playbook for cutting cloud spend without touching your SLOs.

5 min read•Jun 2026
ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD vs FluxCD: GitOps for Kubernetes in 2026

ArgoCD and FluxCD are the two dominant GitOps engines for Kubernetes in 2026 — this breakdown tells you exactly which one to pick and why.

10 min read•Jun 2026

Explore More in Kubernetes Security and Access Control

All 2 Topics

Frequently Asked Questions

Is Managing Kubernetes Secrets with Vault and ConfigMaps free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Managing Kubernetes Secrets with Vault and ConfigMaps topic cover?

Securely manage Kubernetes Secrets and ConfigMaps in production using HashiCorp Vault, secret injection, encryption at rest, and RBAC access controls.