← Back to Blog

How to Secure Environment Variables in Production

Environment variables are the standard way to manage configuration and secrets in modern applications. But the default approach of .env files is full of pitfalls. This guide covers the complete lifecycle of environment variable security, from local development through CI/CD to production, with specific guidance for Docker, Kubernetes, and major cloud providers.

Why .env Files Are Not Enough

The .env file pattern popularized by the Twelve-Factor App methodology was a step forward from hardcoding secrets in source code. But it introduces its own risks:

  • Accidentally committed to git: The number one source of credential leaks. See our guide on detecting secrets in GitHub repos.
  • Exposed on web servers: Over 2.6 million .env files are publicly accessible on the internet. Use the Exposure Checker to verify yours is not one of them.
  • No access control: Anyone with file system access can read the file
  • No rotation: Secrets in .env files are static. There is no built-in mechanism for rotation.
  • No audit trail: No way to track who accessed which secrets and when
  • Shared across environments: Developers copy .env files between machines, creating multiple untracked copies

Level 1: .env File Best Practices

If you are still using .env files (acceptable for local development), follow these minimum practices:

# .gitignore - ALWAYS include these
.env
.env.*
!.env.example
*.pem
*.key

Create a .env.example file with placeholder values that is committed to the repository:

# .env.example - safe to commit
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_your_key_here
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key

Use the SecureBin ENV Validator to verify your .env file structure and catch common mistakes.

Level 2: Secrets Managers

For production, use a dedicated secrets manager. These provide encryption, access control, rotation, and audit logging.

AWS Secrets Manager

# Store a secret
aws secretsmanager create-secret   --name production/myapp/database   --secret-string '{"host":"db.example.com","password":"secure-password"}'

# Retrieve in your application (Node.js)
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
const client = new SecretsManagerClient({ region: 'us-east-1' });
const secret = await client.send(new GetSecretValueCommand({ SecretId: 'production/myapp/database' }));
const credentials = JSON.parse(secret.SecretString);

HashiCorp Vault

# Store a secret
vault kv put secret/myapp/database host=db.example.com password=secure-password

# Retrieve a secret
vault kv get -field=password secret/myapp/database

Doppler

Doppler is a newer secrets manager designed for developer experience. It syncs secrets across environments and integrates with most deployment platforms:

# Run your app with secrets injected
doppler run -- node server.js

# Sync secrets to a .env file (for local development)
doppler secrets download --no-file --format env > .env

Are Your Secrets Exposed on the Web?

Even with a secrets manager, misconfigurations can expose .env files and config files publicly. Check your domain in seconds.

Scan Your Domain Free

Level 3: CI/CD Secret Management

CI/CD pipelines are a common source of secret leaks through build logs, artifact storage, and overshared pipeline variables.

GitHub Actions

# .github/workflows/deploy.yml
env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}

# Use OIDC instead of static AWS credentials
permissions:
  id-token: write
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/deploy-role
      aws-region: us-east-1

Best practices for CI/CD secrets:

  • Never print secrets in build output (mask them in logs)
  • Use OIDC federation instead of static credentials for cloud access
  • Scope secrets to specific environments (production, staging)
  • Rotate CI/CD secrets regularly
  • Audit who has access to pipeline secret configuration

Level 4: Docker and Container Secrets

# WRONG: Secrets in Dockerfile
ENV DATABASE_PASSWORD=supersecret

# WRONG: Secrets in docker-compose.yml
environment:
  - DATABASE_PASSWORD=supersecret

# RIGHT: Docker secrets (Swarm)
echo "supersecret" | docker secret create db_password -

# RIGHT: External secrets in docker-compose
environment:
  - DATABASE_PASSWORD  # Value comes from host environment

# RIGHT: BuildKit secrets for build-time needs
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm install

Level 5: Kubernetes Secrets

# Basic K8s Secret (base64, not encrypted!)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  DATABASE_PASSWORD: c3VwZXJzZWNyZXQ=  # base64 only!

# BETTER: External Secrets Operator (pulls from AWS SM, Vault, etc.)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: app-secrets
  data:
    - secretKey: DATABASE_PASSWORD
      remoteRef:
        key: production/myapp/database
        property: password

Read our comprehensive guide on Kubernetes secrets management for detailed implementation patterns.

Secret Rotation Strategy

  1. Automate rotation: Use AWS Secrets Manager rotation with Lambda, or Vault's dynamic secrets
  2. Rotate on schedule: 90 days for API keys, 30 days for database passwords, immediately after any suspected compromise
  3. Use short-lived credentials: Prefer STS temporary credentials, OAuth tokens with short expiry, and dynamic database credentials
  4. Test rotation: Broken rotation is worse than no rotation. Test the rotation process in staging first.

Frequently Asked Questions

Should I encrypt .env files in the repository?

Tools like SOPS, git-crypt, and BlackBox encrypt secrets within the repository. This is better than plain text but adds complexity and still lacks access control and rotation capabilities. It is a reasonable middle ground for small teams that are not ready for a full secrets manager but want to store configuration alongside code. For production secrets, use a proper secrets manager.

How do I migrate from .env files to a secrets manager?

Migrate incrementally. Start by moving the most sensitive secrets (database credentials, API keys) to the secrets manager while keeping less sensitive configuration in environment variables. Update your application to read from the secrets manager first, falling back to environment variables. Once everything is working, remove the .env file from production. Keep .env files for local development only.

Are Kubernetes Secrets actually secure?

Kubernetes Secrets are base64 encoded, not encrypted. Anyone with read access to the namespace can decode them. To secure K8s secrets: enable encryption at rest (EncryptionConfiguration), use RBAC to restrict secret access, use External Secrets Operator to pull from a real secrets manager, and enable audit logging to track secret access. Base Kubernetes Secrets alone are not sufficient for sensitive production credentials.

Verify Your Environment Is Not Leaking

Even with proper secrets management, web server misconfigurations can expose your .env files publicly. Verify your domain is secure.

Run Free Security Check

The Bottom Line

Environment variable security is a spectrum. Start where you are and move up: .env with .gitignore, then a secrets manager, then automated rotation, then short-lived credentials. Each level dramatically reduces your risk. The most important step is the next one you take. And regardless of your secrets management approach, always verify that your web infrastructure is not accidentally exposing configuration files by running the SecureBin Exposure Checker regularly.

Related reading: Exposed .env Files Danger, Kubernetes Secrets Management, Secure API Keys in Code, Docker Environment Variables Guide.