Secure Database Credentials

Store secrets securely for Python database credentials in Kubernetes. Learn to use Secrets with kubectl and Python client, with hands-on steps and troubleshooting.

Focus: store secrets securely for python database credentials

Sponsored

You've built a Python API that talks to PostgreSQL, and you're about to deploy it to Kubernetes. You know the golden rule: never hard-code passwords or connection strings in your code. But where do you put that DATABASE_URL now? Dump it in a ConfigMap and it sits in plain text, waiting for anyone with cluster access to read it — and the Kubernetes docs are full of warnings about ConfigMaps not being designed for confidential data. So how do you store secrets securely for Python database credentials without locking yourself out of your own deployment? This lesson shows you how to use Kubernetes Secrets — with proper protection at rest — and how your Python app can consume them without leaking them into logs or source control.

In this hands-on lesson, you'll create a Secret for a PostgreSQL connection, mount it into a Python pod, and verify from inside the container that the credentials are readable but not exposed. We'll also compare plain-text ConfigMaps with encrypted Secrets, and I'll show you the common gotchas that trip up even experienced developers.

The problem this lesson solves

Imagine you're deploying a FastAPI service that connects to a managed PostgreSQL database. The connection string looks like this: postgresql://myapp:SuperSecret123@postgres-prod:5432/mydb. If you put that string in your Docker image or your source code, it's committed to your Git history forever. If you put it in a ConfigMap, it's in plain text in etcd — readable by anyone who can access the Kubernetes API.

The real pain: you need to keep that credential secret from other developers, from CI logs, and from anyone who can read your cluster's config. A ConfigMap stores name-value pairs, but they are not secret — they're just configuration. Kubernetes Secrets are the native way to store sensitive data like database passwords, API tokens, and SSH keys. They're encoded (not encrypted) by default, meaning they can be base64-decoded by anyone with access to the cluster, but when configured properly with encryption at rest, they offer a much stronger protection layer.

Without using Secrets, you risk leaking credentials in:

  • Your Git repository (hard-coded in code or environment variables in YAML files)
  • Your container image layers (if you bake credentials into the image during the build)
  • Your application logs (if you print the connection string for debugging)
  • Your ConfigMap (if you store them there, they're in plain text)

By the end of this lesson, you'll be able to store database credentials as a Kubernetes Secret, consume them in your Python app via environment variables or mounted files, and know how to verify that other people cannot easily read them.

Core concept / mental model

Think of the Kubernetes Secret as a vault locker for your application's sensitive configuration. The Secret object stores key-value pairs, where each value is a base64-encoded string. When you mount a Secret as a volume in your pod, your application sees the actual decoded values as files. For example, if you have a key password with the value mysecret, the mounted volume contains a file named password whose content is mysecret.

Unlike ConfigMaps, Secrets are designed with confidentiality in mind, but they are not encrypted by default — they are simply base64-encoded, which is not true encryption. The security comes from:

  1. Restricted access — you can use Kubernetes RBAC to limit who can read Secrets.
  2. Encryption at rest — you can enable encryption at the etcd level so that Secrets are actually encrypted when stored.
  3. Mounting as volumes — this prevents secrets from being exposed in environment variables, which can leak via /proc or pod.spec.containers.env.

Here's a mental picture:

  • ConfigMap = a plain file cabinet — anyone can open it and read the notes.
  • Secret = a locked safe — only those with the key (permission) can open it, and the contents inside are more protected.

For Python developers, the important part is how to read the secret from your code. You have two primary choices:

  • Environment variablesos.environ.get('DB_PASSWORD')
  • Mounted filesopen('/etc/secret-volume/password').read()

Both are simple, but each has security trade-offs. We'll compare them later.

How it works step by step

Store secrets securely for Python database credentials by following this high-level process:

  1. Create the Secret — You create a Kubernetes Secret object with key-value pairs, base64-encoding the values. You can do this via kubectl create secret, kubectl apply -f secret.yaml, or programmatically with the Python client.

  2. Reference the Secret in your Pod spec — You either: - Set environment variables using valueFrom.secretKeyRef - Mount the Secret as a volume using volumes and volumeMounts

  3. Deploy your Python app — Your application reads the secret value at runtime using os.environ or file reads.

  4. Verify and audit — Check the Secret is accessible only where needed, and consider enabling encryption at rest.

Key concepts to understand:

  • base64 encoding — Kubernetes Secrets store values as base64 strings. This is not encryption, but it avoids accidental leaks of binary data or special characters in YAML.
  • secretKeyRef — Used in container env to reference a specfic key from a Secret.
  • volumeMounts — Mounting secrets as files is the recommended way because it avoids exposing them in environment variables that might be printed by kubectl exec or process listings.
  • RBAC — Role-based access control limits which users and service accounts can read Secrets. By default, any user with get permission on secrets can decode them.

Hands-on walkthrough

Let's walk through a complete example: you have a Python app (app.py) that uses psycopg2 to connect to PostgreSQL. We'll store the database credentials as a Secret, run the app in a pod, and confirm it reads the values correctly.

Step 1: Create the Secret

We'll create a Secret called db-credentials with three keys: username, password, and database. Use kubectl create secret generic:

kubectl create secret generic db-credentials \
  --from-literal=username=myapp \
  --from-literal=password=S3cureP@ssw0rd! \
  --from-literal=database=mydb

Check the Secret exists:

kubectl get secrets

You'll see the db-credentials Secret listed. To see its raw data (base64-encoded), run:

kubectl get secret db-credentials -o yaml

The output will show the data as base64 strings:

apiVersion: v1
data:
  database: bXlkYg==
  password: UzNjdXJlUEBzc3cwcmQh
  username: bXlhcHA=
kind: Secret
metadata:
  name: db-credentials
...

Step 2: Create a Python script that reads the credentials

Write a simple app.py that reads the credentials from environment variables and tries to connect to PostgreSQL:

import os
import psycopg2

def main():
    # Reads credentials from environment variables set by Kubernetes
    username = os.getenv('DB_USERNAME')
    password = os.getenv('DB_PASSWORD')
    database = os.getenv('DB_NAME')

    print(f"Username: {username}")
    print(f"Database: {database}")

    # For demonstration, we just print the credentials. In real life, you'd use them to connect.
    print(f"Connection string would be: postgresql://{username}:{password}@{db_host}/{database}")

if __name__ == "__main__":
    main()

Pro tip: In real code, never print credentials to stdout, or you risk leaking them in your logs!

Step 3: Create a Deployment YAML

Now, create a Deployment deployment.yaml that mounts the Secret as environment variables:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-db-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-db-app
  template:
    metadata:
      labels:
        app: python-db-app
    spec:
      containers:
      - name: app
        image: python:3.10-slim
        command: ["python", "/app/app.py"]
        env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password
        - name: DB_NAME
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: database
        # Mounting as a volume is also possible, but we're using env for simplicity.

Apply the Deployment:

kubectl apply -f deployment.yaml

Step 4: Verify your app reads the Secret

Wait for the pod to start and then check the logs:

kubectl get pods
kubectl logs <pod-name>

You'll see output like:

Username: myapp
Database: mydb
Connection string would be: postgresql://myapp:S3cureP@ssw0rd!@db-host/mydb

The environment variables are set from the Secret values, so your app now has the credentials it needs.

Step 5: Try mounting as a volume (alternative method)

Instead of environment variables, you can mount the Secret as a volume. Here's a modified Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-db-app-volume
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-db-app-volume
  template:
    metadata:
      labels:
        app: python-db-app-volume
    spec:
      containers:
      - name: app
        image: python:3.10-slim
        command: ["python", "/app/app.py"]
        volumeMounts:
        - name: secret-volume
          mountPath: /etc/db-credentials
          readOnly: true
      volumes:
      - name: secret-volume
        secret:
          secretName: db-credentials

Then, in your Python code, read these files:

with open('/etc/db-credentials/username') as f:
    username = f.read().strip()
with open('/etc/db-credentials/password') as f:
    password = f.read().strip()

This volume mounting is preferred in security-conscious environments because it doesn't expose the secret in environment variables, which can be seen in process listings or env output.

Compare options / when to choose what

Approach Pros Cons Best for
ConfigMap Simple, easy to edit, not for sensitive data Credentials in plain text, easily readable Non-sensitive config like URLs or enable flags
Secret with env vars Simple to implement, works with any language Secret appears in pod env, may be visible in kubectl exec Quick prototyping, small apps
Secret mounted as volume More secure, secrets not in env, auto-updates on Secret change Requires a mount path, a bit more complex YAML Production apps where credentials must not leak
External secrets (e.g., AWS Secrets Manager, HashiCorp Vault) Centralized management, rotation, audit trails Adds dependencies, complexity Large organizations, high-security environments

Pro tip: For production, always mount Secrets as volumes and enable encryption at rest for your cluster's etcd. Also, limit access to Secrets with RBAC — don't give every pod permission to read every secret.

When to choose what

  • If you're just learning, use the env var approach for simplicity.
  • If you're building a production service, use volume mounts to minimize exposure.
  • If you have a complex multi-service environment with frequent credential rotation, consider external secrets like Vault or AWS Secrets Manager integrated with the Kubernetes CSI driver.

Troubleshooting & edge cases

Base64 decoding issues in Python — When you mount a Secret, the values are already decoded by Kubernetes. However, if you read them from a YAML file manually, you may have to decode base64. Remember to use base64.b64decode() after reading from the Secret object in Python.

Newline characters in files — When you mount a Secret as a volume, the file content may end with a newline. If you use f.read().strip(), you avoid adding a newline to your connection string.

Secret not updating — If you update a Secret after a pod is running, the mounted volume will update automatically (due to kubelet syncing), but environment variables will not update until the pod is restarted. If you use env vars, you must redeploy your pods.

Secret key names with dots or hyphens — If your secret key contains a dot or a hyphen, you can't use it directly as a file name. You'll need to use subPath in your volumeMounts to map it to a specific file name.

Large secrets — Secrets have a default size limit of 1 MiB (1 megabyte). For larger data, consider using external secret stores.

Someone can still see the secret in etcd — Even if you mount a Secret as a volume, if someone has API access to read the Secret object, they can kubectl get secret and decode it. Enable RBAC to restrict read access.

Common error: secret "db-credentials" not found — This happens when you reference a Secret that doesn't exist or is in a different namespace. Double-check the name and namespace.

What you learned & what's next

Good work! In this lesson, you learned to store secrets securely for Python database credentials by:

  • Recognizing the risks of hard-coding or using ConfigMaps for secrets
  • Creating Kubernetes Secrets with kubectl create secret
  • Referencing Secrets via environment variables (secretKeyRef) or volume mounts
  • Mounting Secrets as volumes for better security
  • Comparing Secrets vs. ConfigMaps and external secret stores
  • Debugging common Secret issues like newlines and stale environment variables

You can now confidently deploy Python apps that consume database credentials without leaking them.

What's next? In the next lesson, you'll learn about ConfigMaps for non-sensitive configuration, and how to combine them with Secrets to build a complete, secure configuration story for your Python microservices. You'll also see how to centralize secret management with tools like External Secrets Operator. Keep building!

Practice recap

Create your own Secret with a dummy PostgreSQL password, mount it as a volume in a simple Python pod, and read it from a file. Modify the Secret and observe how the mounted file updates without restarting the pod. Then try using an environment variable approach and note that it requires a pod restart to see the change.

Common mistakes

  • Putting database credentials in a ConfigMap or hard-coding them in your Python code — never do this; use a Secret.
  • Forgetting that Secret values are base64-encoded, not encrypted — they are easy to decode if someone obtains the Secret object.
  • Using environment variables from Secrets and expecting updates to propagate to running pods — env vars are set once at container start; the pod must be restarted.
  • Not stripping newline characters when reading mounted Secret files, causing connection string errors.

Variations

  1. Use kubectl create secret for quick creation, or define the Secret in a YAML file with base64-encoded values for version-controlled manifests.
  2. Read Secrets as files (volume mounts) instead of environment variables for better security and automatic updates.
  3. Integrate with external secret managers like HashiCorp Vault or AWS Secrets Manager using the Kubernetes CSI driver.

Real-world use cases

  • A FastAPI microservice connects to Amazon RDS PostgreSQL; credentials live in a Kubernetes Secret mounted as a volume, so no secrets ship in the Docker image.
  • A data pipeline in Kubernetes reads a DB password from a Secret to connect to an on-prem MySQL database, with passwords rotated via kubectl rollout restart.
  • A multi-tenant SaaS app uses per-tenant Secrets to store database credentials for each customer, ensuring one tenant's data is not accessible to another.

Key takeaways

  • Kubernetes Secrets protect sensitive data only when combined with RBAC and encryption at rest; they are not encrypted by default.
  • Referencing a Secret via environment variables is simple, but volume mounts are more secure because they avoid exposing secrets in process listings.
  • Always update Secrets through the API, and restart pods to pick up new env var values.
  • Read mounted Secret files using .strip() to avoid newline characters.
  • Choose ConfigMaps for non-sensitive data, Secrets for credentials, and external managers for advanced rotation and audit.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.