USMAN’S INSIGHTS
AI ARCHITECT
  • Home
  • About
  • Thought Leadership
  • Book
Press / Contact
USMAN’S INSIGHTS
AI ARCHITECT
⌘F
HomeBook
HomeBookYour Secrets Are Leaking: How to Stop Kubernetes From Exposing Your API Keys to Everyone
Previous Chapter
Deploying Task API to DOKS
Next Chapter
Personal Cloud Lab - Hetzner K3s
AI NOTICE: This is the table of contents for the SPECIFIC CHAPTER only. It is NOT the global sidebar. For all chapters, look at the main navigation.

On this page

37 sections

Progress0%
1 / 37

Muhammad Usman Akbar Entity Profile

Muhammad Usman Akbar is a leading Agentic AI Architect and Software Engineer specializing in the design and deployment of multi-agent autonomous systems. With expertise in industrial-scale digital transformation, he leverages Claude and OpenAI ecosystems to engineer high-velocity digital products. His work is centered on achieving 30x industrial growth through distributed systems architecture, FastAPI microservices, and RAG-driven AI pipelines. Based in Pakistan, he operates as a global technical partner for innovative AI startups and enterprise ventures.

USMAN’S INSIGHTS
AI ARCHITECT

Transforming businesses into autonomous AI ecosystems. Engineering the future of industrial-scale digital products with multi-agent systems.

30X Growth
AI-First
Innovation

Navigation

  • Home
  • Book
  • About
  • Contact
Let's Collaborate

Have a Project in Mind?

Let's build something extraordinary together. Transform your vision into autonomous AI reality.

Start Your Transformation

© 2026 Muhammad Usman Akbar. All rights reserved.

Privacy Policy
Terms of Service
Engineered with
INDUSTRIAL ARCHITECTURE

Production Secrets & Configuration

In the previous Chapter, you deployed your Task API to DOKS with a basic configuration. But production services need more than hardcoded values. Your AI agent needs database credentials, API keys for external services, and environment-specific settings that change between staging and production.

Storing these directly in Deployment manifests creates security risks and operational headaches. Every configuration change requires a new container image or manifest edit. Worse, secrets end up in version control where anyone with repository access can read them.

Kubernetes solves this with two primitives: Secrets for sensitive data and ConfigMaps for non-sensitive configuration. This lesson teaches you to use both effectively, including the critical image pull secret that authenticates your cluster to pull images from GitHub Container Registry.


What You'll Learn

This lesson covers 5 concepts organized into three groups:

Concept GroupConceptsFocus
Secret Fundamentals1-2Creating secrets, types of secrets (Opaque, docker-registry)
Configuration Management3-4ConfigMaps, configuration hierarchy patterns
Verification5Mounting secrets, verifying injection, troubleshooting

Prerequisites:

  • Previous Chapter completed (Task API deployed to DOKS)
  • kubectl configured and connected to your cluster
  • GitHub personal access token with read:packages scope

Time Estimate: 25 minutes

  • Concepts: 15 minutes
  • Exercises: 8 minutes
  • Try With AI: 2 minutes

Concept 1: Kubernetes Secrets

A Kubernetes Secret stores sensitive data like passwords, tokens, and certificates. Unlike ConfigMaps, Secrets are base64-encoded (not encrypted by default, but protected from casual viewing) and can be restricted with RBAC.

Creating a Secret from Literal Values

bash
kubectl create secret generic task-api-secrets \ --from-literal=DATABASE_PASSWORD=prod-super-secret-password \ --from-literal=OPENAI_API_KEY=sk-proj-your-api-key-here

Output:

Specification
secret/task-api-secrets created

Viewing Secret Structure (Not Values)

bash
kubectl get secret task-api-secrets -o yaml

Output:

yaml
apiVersion: v1 kind: Secret metadata: name: task-api-secrets namespace: default type: Opaque data: DATABASE_PASSWORD: cHJvZC1zdXBlci1zZWNyZXQtcGFzc3dvcmQ= OPENAI_API_KEY: c2stcHJvai15b3VyLWFwaS1rZXktaGVyZQ==

The values are base64-encoded, not encrypted. Anyone with kubectl get secret access can decode them:

bash
echo "cHJvZC1zdXBlci1zZWNyZXQtcGFzc3dvcmQ=" | base64 -d

Output:

Specification
prod-super-secret-password

Security Note: Base64 is encoding, not encryption. For true at-rest encryption, enable Kubernetes secrets encryption or use a secrets manager like HashiCorp Vault or cloud provider secrets (AWS Secrets Manager, GCP Secret Manager).


Concept 2: Image Pull Secrets for Private Registries

Your Task API container lives in GitHub Container Registry (GHCR), which requires authentication. Without an image pull secret, Kubernetes cannot pull your private images.

Creating the Image Pull Secret

bash
kubectl create secret docker-registry ghcr-secret \ --docker-server=ghcr.io \ --docker-username=$GITHUB_USERNAME \ --docker-password=$GITHUB_TOKEN \ --docker-email=your-email@example.com

Output:

Specification
secret/ghcr-secret created

Where:

  • $GITHUB_USERNAME is your GitHub username
  • $GITHUB_TOKEN is a personal access token with read:packages scope

Verifying the Secret

bash
kubectl get secret ghcr-secret -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .

Output:

json
{ "auths": { "ghcr.io": { "username": "your-username", "password": "ghp_xxxxxxxxxxxxxxxxxxxx", "email": "your-email@example.com", "auth": "eW91ci11c2VybmFtZTpnaHBfeHh4eHh4eHh4eHh4eHh4eHh4eHh4" } } }

Referencing in Deployment

Add imagePullSecrets to your Deployment spec:

yaml
# task-api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: task-api spec: template: spec: imagePullSecrets: - name: ghcr-secret containers: - name: task-api image: ghcr.io/your-username/task-api:latest

Without this reference, pod creation fails with ImagePullBackOff:

bash
kubectl describe pod task-api-xxxxx | grep -A5 "Events:"

Output (without secret):

Specification
Events: Warning Failed 12s kubelet Failed to pull image "ghcr.io/your-username/task-api:latest": rpc error: code = Unknown desc = failed to pull and unpack image: failed to resolve reference: unexpected status code 401 Unauthorized

Concept 3: ConfigMaps for Non-Sensitive Configuration

ConfigMaps store configuration data that isn't sensitive: feature flags, service URLs, logging levels. Unlike Secrets, ConfigMaps appear in plain text and are easier to inspect.

Creating a ConfigMap

bash
kubectl create configmap task-api-config \ --from-literal=LOG_LEVEL=info \ --from-literal=API_BASE_URL=https://api.example.com \ --from-literal=ENABLE_METRICS=true

Output:

Specification
configmap/task-api-config created

Creating ConfigMap from a File

For complex configuration, use a file:

env
# config.env LOG_LEVEL=info API_BASE_URL=https://api.example.com ENABLE_METRICS=true MAX_CONNECTIONS=100 REQUEST_TIMEOUT_MS=30000
bash
kubectl create configmap task-api-config --from-env-file=config.env

Output:

Specification
configmap/task-api-config created

Viewing ConfigMap Contents

bash
kubectl get configmap task-api-config -o yaml

Output:

yaml
apiVersion: v1 kind: ConfigMap metadata: name: task-api-config namespace: default data: API_BASE_URL: https://api.example.com ENABLE_METRICS: "true" LOG_LEVEL: info MAX_CONNECTIONS: "100" REQUEST_TIMEOUT_MS: "30000"

Concept 4: Configuration Hierarchy Pattern

Production applications need layered configuration: defaults that work everywhere, environment overrides for staging vs production, and secrets that are never committed to version control.

The Three-Layer Pattern

text
┌─────────────────────────────────────┐ │ Application │ ├─────────────────────────────────────┤ │ Layer 3: Secrets (highest priority)│ ← DATABASE_PASSWORD, API_KEY │ kubectl create secret │ ├─────────────────────────────────────┤ │ Layer 2: ConfigMap (environment) │ ← LOG_LEVEL, API_URL │ kubectl create configmap │ ├─────────────────────────────────────┤ │ Layer 1: Container defaults │ ← Baked into image │ Dockerfile ENV statements │ └─────────────────────────────────────┘

Environment variables from higher layers override lower layers.

Implementing in Deployment

yaml
# task-api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: task-api spec: template: spec: imagePullSecrets: - name: ghcr-secret containers: - name: task-api image: ghcr.io/your-username/task-api:latest # Layer 1: Defaults (optional, usually in Dockerfile) env: - name: NODE_ENV value: "production" # Layer 2: ConfigMap (all non-sensitive config) envFrom: - configMapRef: name: task-api-config # Layer 3: Secrets (sensitive values, highest priority) - secretRef: name: task-api-secrets

Output (kubectl describe pod):

Specification
Environment Variables from: task-api-config ConfigMap Optional: false task-api-secrets Secret Optional: false Environment: NODE_ENV: production

Why This Order Matters

If LOG_LEVEL appears in both ConfigMap and as a hardcoded env var, the ConfigMap value wins because envFrom is processed after env. Secrets in envFrom are processed last, so they override everything.


Concept 5: Verifying Secret and ConfigMap Injection

Deploying configuration is only half the battle. You must verify that containers receive the expected values.

Check Pod Environment Variables

bash
kubectl exec -it deployment/task-api -- env | grep -E "(DATABASE|OPENAI|LOG_LEVEL)"

Output:

Specification
DATABASE_PASSWORD=prod-super-secret-password OPENAI_API_KEY=sk-proj-your-api-key-here LOG_LEVEL=info

Verify All Environment Variables

bash
kubectl exec -it deployment/task-api -- printenv | sort

Output:

Specification
API_BASE_URL=https://api.example.com DATABASE_PASSWORD=prod-super-secret-password ENABLE_METRICS=true LOG_LEVEL=info MAX_CONNECTIONS=100 NODE_ENV=production OPENAI_API_KEY=sk-proj-your-api-key-here REQUEST_TIMEOUT_MS=30000 ...

Troubleshooting Missing Variables

If expected variables are missing, check:

  1. ConfigMap/Secret exists:

    bash
    kubectl get configmap task-api-config kubectl get secret task-api-secrets
  2. Deployment references are correct:

    bash
    kubectl get deployment task-api -o yaml | grep -A10 env

From ```

  1. Pod events for errors:

    bash
    kubectl describe pod -l app=task-api | grep -A20 Events

Output (missing ConfigMap):

Specification
Events: Warning Failed 12s kubelet Error: configmap "task-api-config" not found

Common Mistakes

1. Forgetting imagePullSecrets for Private Registries

yaml
# WRONG - Missing imagePullSecrets spec: containers: - name: task-api image: ghcr.io/your-username/task-api:latest # Will fail with 401

Fix: Always include imagePullSecrets when using private registries:

yaml
spec: imagePullSecrets: - name: ghcr-secret containers: - name: task-api image: ghcr.io/your-username/task-api:latest

2. Putting Secrets in ConfigMaps

bash
# WRONG - API keys in ConfigMap (visible to anyone) kubectl create configmap task-api-config \ --from-literal=OPENAI_API_KEY=sk-proj-xxxxx

Fix: Use Secrets for sensitive data:

bash
kubectl create secret generic task-api-secrets \ --from-literal=OPENAI_API_KEY=sk-proj-xxxxx

3. Using Wrong Secret Type for Docker Registry

bash
# WRONG - Opaque secret won't work for imagePullSecrets kubectl create secret generic ghcr-secret \ --from-literal=password=ghp_xxxxx

Fix: Use docker-registry type:

bash
kubectl create secret docker-registry ghcr-secret \ --docker-server=ghcr.io \ --docker-username=$GITHUB_USERNAME \ --docker-password=$GITHUB_TOKEN

4. Forgetting to Restart Pods After Secret Update

Updating a Secret doesn't automatically restart pods:

bash
kubectl create secret generic task-api-secrets \ --from-literal=DATABASE_PASSWORD=new-password \ --dry-run=client -o yaml | kubectl apply -f - # Pod still has OLD password!

Fix: Restart the deployment:

bash
kubectl rollout restart deployment/task-api

Exercise 6.1: Create Application Secrets

Create secrets for your Task API with database credentials and an API key.

Instructions:

  1. Create the secret:

    bash
    kubectl create secret generic task-api-secrets \ --from-literal=DATABASE_PASSWORD=my-secure-password \ --from-literal=API_KEY=your-test-api-key
  2. Verify creation:

    bash
    kubectl get secret task-api-secrets

Expected output: Secret listed with Opaque type and 2 data items.


Exercise 6.2: Create Image Pull Secret for GHCR

Set up authentication for GitHub Container Registry.

Instructions:

  1. Create a GitHub personal access token with read:packages scope at https://github.com/settings/tokens

  2. Create the secret (replace with your values):

    bash
    export GITHUB_USERNAME=your-username export GITHUB_TOKEN=ghp_your_token_here kubectl create secret docker-registry ghcr-secret \ --docker-server=ghcr.io \ --docker-username=$GITHUB_USERNAME \ --docker-password=$GITHUB_TOKEN
  3. Verify:

    bash
    kubectl get secret ghcr-secret -o jsonpath='{.type}'

Expected output: kubernetes.io/dockerconfigjson


Exercise 6.3: Create ConfigMap and Deploy with Full Configuration

Create a ConfigMap and update your deployment to use the three-layer configuration pattern.

Instructions:

  1. Create ConfigMap:

    bash
    kubectl create configmap task-api-config \ --from-literal=LOG_LEVEL=info \ --from-literal=ENABLE_METRICS=true
  2. Apply updated deployment with all references (save as task-api-full.yaml):

    yaml
    apiVersion: apps/v1 kind: Deployment metadata: name: task-api spec: replicas: 1 selector: matchLabels: app: task-api template: metadata: labels: app: task-api spec: imagePullSecrets: - name: ghcr-secret containers: - name: task-api image: ghcr.io/your-username/task-api:latest ports: - containerPort: 8000 envFrom: - configMapRef: name: task-api-config - secretRef: name: task-api-secrets
  3. Apply and verify:

    bash
    kubectl apply -f task-api-full.yaml kubectl exec -it deployment/task-api -- printenv | grep -E "(LOG_LEVEL|DATABASE)"

Expected output: Both ConfigMap and Secret values visible in pod environment.


Try With AI

Part 1: Initial Request

Your Task API needs additional secrets for a new feature: Redis connection string and a webhook signing secret. Ask AI for help:

"I need to add Redis and webhook secrets to my Kubernetes deployment. Create kubectl commands to add REDIS_URL and WEBHOOK_SECRET to my existing task-api-secrets."

What you're learning: How to extend existing secrets without recreating them from scratch.

Part 2: Critical Evaluation

Review AI's response. Ask yourself:

  • Did it use kubectl create secret with --dry-run=client -o yaml | kubectl apply -f - to update existing secrets?
  • Or did it suggest deleting and recreating (which causes downtime)?
  • Does the approach preserve existing secret values?

Part 3: Configuration Hierarchy

Ask AI about a more advanced pattern:

"I want to use external-secrets operator to pull secrets from AWS Secrets Manager instead of creating them with kubectl. Is this better than what I'm doing now?"

What you're learning: The difference between simple kubectl-based secrets (good for learning) and enterprise patterns (external-secrets operator).

Part 4: Validation

Verify the secrets work by checking your pod:

bash
kubectl exec -it deployment/task-api -- env | grep REDIS

If the variable appears with the expected value, your secret management is working.


Reflect on Your Skill

You built a multi-cloud-deployer skill in an earlier Chapter. Test and improve it based on what you learned.

Test Your Skill

text
Using my multi-cloud-deployer skill, set up secrets for a production deployment. Does my skill include creating application secrets and image pull secrets?

Identify Gaps

Ask yourself:

  • Does my skill cover creating Kubernetes secrets with kubectl?
  • Does it include image pull secrets for private registries like GHCR?
  • Does it demonstrate the three-layer configuration pattern?
  • Does it include verification steps using kubectl exec?

Improve Your Skill

If you found gaps:

text
My multi-cloud-deployer skill is missing secrets management patterns. Update it to include: - Creating application secrets (kubectl create secret generic) - Creating image pull secrets (kubectl create secret docker-registry) - ConfigMap creation for non-sensitive configuration - The three-layer hierarchy (defaults -> configmap -> secrets) - Verification with kubectl exec to check environment variables - Common mistakes like forgetting imagePullSecrets reference