Blog

Best practices for secrets management

This guide explains best practices for keeping your secrets where they belong—secured away from public code and prying eyes.


It's 2 am, you're wrapping up a critical feature, and you push what seems like an innocent commit. By morning, your API keys are trending on GitHub, your production environment is compromised, and TechCrunch is running a story about how your startup leaked 100,000 customer records.

All because of a single access token accidentally committed to a public repo. This isn't hypothetical. It happens every day. In 2023, GitHub reported scanning over 1.4 billion commits and catching over 15 million secrets.

That's 15 million potential security disasters, ranging from exposed AWS credentials to leaked database passwords. And those are just the ones that were caught.

This guide explains best practices for keeping your secrets where they belong—secured away from public code and prying eyes.

Foundation: understanding secrets in modern applications

A secret is any piece of information that, if exposed, could grant unauthorized access to your systems, data, or services. This includes:

  • API keys
  • Database credentials
  • SSH and encryption keys
  • Passwords

Even seemingly innocuous tokens for development tools or CI services can provide access to your systems.

Prevention: stopping secrets before they escape

Pre-commit scanning

The best time to catch an exposed secret is before it reaches your repository. Tools like ggshield scan every commit for potential secrets. If secrets are detected, the commit is aborted.

ggshield detecting a private key in a staged file and preventing the commit

Setting up ggshield takes just a few minutes and can save countless hours of incident response. It's particularly effective because it's automated – you can't forget to run it, and it can't be easily bypassed in a moment of haste.

Pre-commit scanning is one of the best ways to prevent future heartache.

Version control security

Modern version control systems have evolved to provide robust security features, but proper configuration and vigilance are still essential. For instance, GitHub’s secret scanning now supports automated credential rotation for certain token formats.

When a supported token is accidentally committed, GitHub takes these automated steps to mitigate the breach:

  • Detect the exposed secret: GitHub scans repositories for known token patterns in real time.
  • Automatically invalidate the token: The exposed token is immediately flagged as compromised and rendered inactive.
  • Issue a new token: For providers that support auto-rotation, GitHub interacts with their APIs to generate a new credential.
  • Notify relevant team members: Alerts are sent to repository administrators and the token owner for visibility and follow-up action.

This automation significantly reduces the exposure window and accelerates incident response, but it shouldn’t be your sole line of defense. Secrets should never be committed to repositories in the first place.

Layer on tools like GitGuardian, which monitors public and private repositories in real time for plaintext credentials. This pairs very well with pre-commit scanning.

Environment management: the foundation of secret handling

Environment variables provide a secure and flexible way to manage secrets, such as API keys and database credentials, across local development and production systems. Instead of hardcoding secrets or committing them to version control, modern applications rely on environment files to define and load sensitive values.

In a Next.js project, you’ll commonly encounter these environment files:

my-nextjs-app/
├── .env.example # Template for required variables (committed)
├── .env.local # Local development values (ignored)
├── .env # Shared defaults (ignored)
├── .env.production # Production-specific values (careful!)
└── .gitignore # Ensures secrets aren't committed

.env.example: Serves as a blueprint for required environment variables, showing developers what values they need to provide without exposing sensitive data.

DATABASE_URL=postgresql://user:password@localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_...

.env.local and .env: Contain real secrets for local and shared environments. These files must be excluded from version control using .gitignore to prevent accidental exposure:

# Ignore sensitive files
.env
.env.local
!.env.example

Loading Variables: Libraries like dotenv automatically load variables from .env files into the runtime environment, where they can be accessed via process.env.

Production Considerations: Use environment-specific files like .env.production with caution. Secrets should be injected securely at runtime using environment configuration tools or deployment pipelines rather than committed in plaintext.

Adhering to this structure creates a clear and secure workflow for managing secrets while ensuring developers have all the information they need to set up their environments correctly.

Scaling secrets management

As applications grow in complexity, simple environment variables often fall short. Modern secrets managers offer advanced features like automated rotation, fine-grained access control, and dynamic secrets, which are essential for scaling securely.

Dynamic secrets with HashiCorp Vault

HashiCorp Vault can generate temporary credentials that automatically expire, reducing the risk of long-lived secrets. For example, Vault can provide time-limited database credentials:

const vault = require('node-vault')({
  apiVersion: 'v1',
  endpoint: 'http://vault:8200',
});

async function getDatabaseCredentials() {
  const { data } = await vault.read('database/creds/readonly');
  return {
    username: data.username,
    password: data.password,
    expiresIn: data.lease_duration, // Credentials auto-expire
  };
}

This approach ensures credentials are ephemeral and tightly scoped, enhancing security for enterprise systems.

Cloud provider native solutions

AWS Secrets Manager integrates seamlessly with AWS applications and automatically rotates credentials on a defined schedule. Applications fetch secrets at runtime, ensuring access to up-to-date values:

import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

async function getSecret(secretName) {
  const client = new SecretsManagerClient();
  const command = new GetSecretValueCommand({ SecretId: secretName });

  try {
    const response = await client.send(command);
    return JSON.parse(response.SecretString);
  } catch (error) {
    console.error("Error retrieving secret:", error);
    throw error;
  }
}

By using AWS Secrets Manager, you avoid hardcoding secrets in your codebase and gain features like encrypted storage, audit logs, and automated rotation.

Recommendations for scaling secret management

1. Minimize hardcoded secrets: Use runtime fetching to keep secrets out of your codebase.

2. Automate rotation: Rely on tools like Vault or cloud-provider solutions to update secrets without manual intervention.

3. Audit access: Regularly review secret usage logs to detect unauthorized access or unusual patterns.

Modern secrets managers go far beyond basic environment variables, providing the tools you need to handle sensitive data securely at scale.

Cross-team secret sharing and compliance

As teams grow, managing secrets across multiple environments and stakeholders becomes increasingly complex.

Centralized tools like Doppler simplify this process by offering:

  • Role-based access control (RBAC): Define granular access policies for team members.
  • Environment segregation: Keep development, staging, and production secrets isolated.
  • Audit logging and version history: Track who accessed or modified secrets and roll back changes if needed.
  • Automated rotation: Reduce the risk of stale or compromised secrets.

These features not only improve security but also ensure compliance with standards like SOC 2 and GDPR, which require:

1. Comprehensive audit trails for secret access and modifications.

2. Regular secret rotation to limit exposure risks.

3. Access review procedures to detect unauthorized usage.

4. Incident response documentation and clear processes for handling breaches.

5. Employee training on secure handling practices.

Centralized secrets management prevents leaks and helps you meet compliance requirements.

Infrastructure secrets: CI/CD and containers

CI/CD Pipeline Security

Continuous integration and delivery pipelines often require secrets for deployment, testing, and builds. Modern platforms like GitHub Actions provide secure ways to manage these secrets:

jobs:
  deploy:
    environment:
      name: production
      url: https://production.example.com
    steps:
      - uses: actions/checkout@v2
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
        with:
          # Manual approval for critical actions
          required_reviewers: |
            security-team
            platform-team

Best Practices:

  • Use environment protection rules to require approvals before accessing sensitive secrets.
  • Restrict secrets to specific environments and avoid sharing between development and production.
  • Rotate and audit CI/CD secrets regularly.

Container security

For containerized applications, secrets management must integrate seamlessly with orchestration platforms like Kubernetes. Use platform-native solutions to secure secrets:

# Kubernetes Secret with RBAC and HashiCorp Vault integration
apiVersion: v1
kind: Secret
metadata:
  name: api-secrets
  namespace: production
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/agent-inject-secret-config: "secret/data/api"
type: Opaque
---
apiVersion: v1
kind: Pod
metadata:
  name: api-service
spec:
  serviceAccountName: api-service-account  # RBAC-controlled
  containers:
  - name: api
    image: my-api:latest
    env:
    - name: API_KEY
      valueFrom:
        secretKeyRef:
          name: api-secrets
          key: api-key

Key Considerations:

  • Use RBAC to control which pods or services can access specific secrets.
  • Enable encryption for secrets in your cluster at rest.
  • Automate secret injection into containers using tools like Vault or Kubernetes annotations.

Moving forward

Effective secrets management costs less than a breach but requires a comprehensive approach that evolves with your organization.

Start with the basics: scan your commits, use environment variables correctly, and leverage your VCS's security features.

As you scale, graduate to dedicated secrets managers and implement sophisticated access controls.

You can create a robust, scalable strategy for secret storage and sharing by combining centralized tools, compliance-oriented practices, and infrastructure-native solutions.

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.