Cybersecurity

Secrets Management for Developers: From .env Files to Secret Managers

Python Docker Kubernetes RAG Encryption Authentication OAuth JWT SQL Injection XSS CI/CD Git GitHub AWS Cloud Databases SQL Node.js Credentials Passwords Hashing Certificates TLS HTTPS
1,497 words Includes Code

Secrets Management for Developers: From .env Files to Secret Managers

How to stop treating API keys and passwords like configuration — and start treating them like the sensitive assets they are.

KEY TAKEAWAY
Secrets management is the practice of storing, accessing, rotating and revoking credentials securely. .env files work for local development but lack access control, audit trails and rotation. Environment variables improve on this by keeping secrets out of repositories. Vault-style and cloud secret managers add encryption, access policies, versioning and automatic rotation. The right approach depends on your team size, environment and compliance requirements.

Every application needs secrets: database passwords, API keys, signing keys, certificates. The question is not whether you have secrets — it is how you manage them. Poor secrets management is one of the most common causes of data breaches, and it is entirely preventable.

This article compares four approaches to secrets management, explains rotation and least privilege, and provides a practical checklist for securing credentials across environments.

The Problem: Secrets in the Wrong Place

When secrets are stored insecurely, the blast radius of a breach expands dramatically. A single leaked API key can compromise payment processing, data storage and third-party integrations simultaneously.

# DANGEROUS: Hardcoded in source code
DATABASE_URL = "postgresql://admin:p4ssw0rd@db.prod.com:5432/mydb"
STRIPE_KEY = "sk_live_REAL_KEY_NOT_FAKE"
API_SECRET = "super-secret-value"

This pattern is still found in production codebases because developers prioritize getting things working over getting things secure. The fix is architectural, not behavioral — build a system where secrets cannot end up in the wrong place.

Four Approaches Compared

Feature .env File Env Variables Vault / Secret Mgr Cloud Secret Mgr
Access Control None OS-level Fine-grained ACL IAM roles
Encryption at Rest No No AES-256 AES-256 / KMS
Audit Trail None None Full access log CloudTrail / logging
Rotation Manual Manual Automatic Automatic
Versioning No No Yes + rollback Yes + rollback
Git Safe No Yes Yes Yes
Setup Complexity Trivial Low High Medium
Cost Free Free Free (OSS) Pay per secret
Team Scaling Poor Poor Good Good

Approach 1: The .env File

The .env file is the most common starting point. It is a plain-text file containing key-value pairs loaded by libraries like python-dotenv, dotenv for Node.js, or godotenv for Go.

# .env (NEVER commit this file)
DATABASE_URL=postgresql://app_user:s3cret@localhost:5432/mydb
STRIPE_API_KEY=sk_test_FAKE_KEY
JWT_SECRET=change-this-to-random-string

# .env.example (SAFE to commit - no real values)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
STRIPE_API_KEY=sk_test_your_key_here
JWT_SECRET=generate-a-random-secret-here

Every .env file should be accompanied by a .env.example that documents the required variables without containing real values.

What to Put in .gitignore

# Secrets and credentials
.env
.env.local
.env.*.local
*.pem
*.key
*.cert
*.p12
secrets.json
credentials.json
service-account*.json

Approach 2: Environment Variables

Environment variables remove secrets from the filesystem entirely. They are set by the runtime environment — Docker, CI/CD, Kubernetes, or the operating system — and accessed through the OS API.

# Set in Docker
docker run -e DATABASE_URL="postgresql://..." myapp

# Set in docker-compose.yml (reference external file)
env_file:
  - .env.production

# Set in CI/CD (GitHub Actions)
env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}

# Python: validate at startup
import os
required = ["DATABASE_URL", "JWT_SECRET", "API_KEY"]
missing = [v for v in required if not os.environ.get(v)]
if missing:
    raise EnvironmentError(f"Missing secrets: {missing}")

Startup Validation Pattern

import os
import sys

REQUIRED_SECRETS = [
    "DATABASE_URL",
    "JWT_SECRET",
    "STRIPE_API_KEY",
]

def validate_secrets():
    missing = [v for v in REQUIRED_SECRETS if not os.environ.get(v)]
    if missing:
        print(f"FATAL: Missing required secrets: {missing}")
        print("Set them as environment variables before starting.")
        sys.exit(1)

validate_secrets()
# Application starts only if all secrets are present

Approach 3: Vault / Secret Managers

Secret managers store credentials centrally with encryption, access control, versioning and audit logging. HashiCorp Vault is the leading open-source option; cloud providers offer managed alternatives.

# HashiCorp Vault: store a secret
vault kv put secret/myapp/database \
  username=app_user \
  password=s3cret

# HashiCorp Vault: retrieve a secret
vault kv get -field=password secret/myapp/database

# AWS Secrets Manager (Python)
import boto3, json
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='myapp/database')
secret = json.loads(response['SecretString'])

# GCP Secret Manager (Python)
from google.cloud import secretmanager
client = secretmanager.SecretManagerServiceClient()
response = client.access_secret_version(
    name="projects/my-project/secrets/db-password/versions/latest"
)

Approach 4: Cloud Secret Managers

Cloud providers offer managed secret storage integrated with their IAM systems. The main options:

Provider Service Rotation Pricing
AWS Secrets Manager Built-in Lambda rotation $0.40/secret/month + $0.05/10K API calls
GCP Secret Manager Custom rotation functions $0.06/version/month + $0.03/10K API calls
Azure Key Vault Managed identity rotation $0.03/10K operations
HashiCorp Vault (self-hosted) Built-in rotation policies Free (OSS) / Enterprise pricing

Secret Rotation

Rotation means regularly changing credentials so that a leaked secret has a limited window of usefulness. Without rotation, a leaked API key is valid indefinitely.

Rotation Strategies

Strategy How It Works Best For
Scheduled Rotate every 30-90 days automatically Database passwords, API keys
Event-driven Rotate immediately after suspected breach Incident response
Continuous Use short-lived tokens (JWT, OAuth) Service-to-service auth
Blue-Green Deploy new secret, switch, revoke old Zero-downtime rotation

Critical principle: Old credentials must stop working after rotation. If the old key remains valid, rotation provides a false sense of security.

Least Privilege for Secrets

Every service should have the minimum secret access it needs. If one service is compromised, the damage is limited to that service's permissions.

# BAD: One master credential for everything
# If leaked -> everything compromised

# GOOD: Scoped per service
Service: web-app
  -> Database: READ-ONLY user (specific tables)
  -> Stripe: payments scope only
  -> AWS: specific IAM role, not root

Service: worker
  -> Database: WRITE user (specific tables)
  -> Queue: send/receive only

Service: admin-panel
  -> Database: admin user (requires MFA)
  -> Full Stripe access (requires human approval)

Database Least Privilege Example

-- BAD: Application uses admin user
CREATE USER app WITH PASSWORD 'secret';
GRANT ALL PRIVILEGES ON DATABASE mydb TO app;

-- GOOD: Minimal permissions per service
CREATE USER app_reader WITH PASSWORD 'secret';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_reader;

CREATE USER app_writer WITH PASSWORD 'secret';
GRANT SELECT, INSERT, UPDATE ON orders, customers TO app_writer;
-- app_writer CANNOT: DROP, ALTER, DELETE, truncate

Docker and Container Secrets

Containers create unique secrets management challenges. Secrets must not be baked into images, and they must be injected at runtime.

# BAD: Secret baked into image (visible to anyone)
FROM python:3.12
ENV API_KEY=real-secret-key   # NEVER DO THIS
COPY . /app

# BETTER: Docker secrets (Swarm)
services:
  app:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt

# BEST: External secret manager + runtime injection
# Secrets fetched at container startup
# Never written to image layers or filesystem

Pre-Commit Secret Scanning

Even with the best practices above, developers accidentally commit secrets. Pre-commit hooks and scanning tools provide a safety net:

Tool Type Best For
git-secrets Pre-commit hook (AWS) Blocking commits with AWS keys
gitleaks Scanner + pre-commit General secret detection
truffleHog Git history scanner Finding secrets in past commits
detect-secrets Yelp pre-commit Baseline-based detection
GitHub Secret Scanning Push protection GitHub-hosted repositories

Secrets Management Checklist

[  ] No secrets in source code or Git history
[  ] .env files in .gitignore
[  ] .env.example documented (no real values)
[  ] Required secrets validated at startup
[  ] Each service has its own credentials
[  ] Database users have minimal permissions
[  ] API keys use scoped permissions
[  ] Secrets rotated on a schedule
[  ] Old credentials revoked after rotation
[  ] Pre-commit secret scanning configured
[  ] Secrets encrypted at rest
[  ] Access audit trail enabled
[  ] Docker images don't contain secrets
[  ] Short-lived tokens preferred over long-lived keys

Try It Yourself

Explore security concepts with BestWordz tools:

Related BestWordz Articles

Further Reading

Conclusion

Secrets management is not optional — it is a fundamental part of software engineering. The approach you choose depends on your context: .env files work for solo development on a laptop. Environment variables handle Docker and CI/CD. Vault and cloud secret managers cover production systems, teams and compliance requirements.

Regardless of which approach you use, three principles always apply: never commit secrets to Git, rotate credentials regularly, and give each service only the access it needs. These three practices prevent the majority of credential-related breaches.

Start with .gitignore and .env.example today. Add pre-commit scanning this week. Evaluate a secret manager when your team or compliance requirements demand it.

BOTTOM LINE
Secrets that live in source code will eventually be leaked. Move them to environment variables at minimum, and to a secret manager for production. Rotate regularly, scope narrowly, and scan constantly.

Try the Password Strength Checker

Put what you've learned into practice with this free BestWordz tool.

Open Tool →

💬 Discuss on BestWordz Community

Join the conversation about Python, Docker, Kubernetes on the BestWordz Community forum.

Visit Forum →