Security

Securing CI/CD Pipelines

Best practices for using scoped API keys and client-side encryption in automated pipelines.

Back to all guides

CI/CD pipelines are where secrets are most exposed: they run unattended, often on shared infrastructure, and they touch production. This guide collects the practices that keep dotEnv Cloud secrets safe across any pipeline; the principles apply equally to GitHub Actions, GitLab CI, and Azure DevOps.

Use organization API keys, not personal logins

Never use OAuth or a personal session in CI. Authenticate with an organization API key, which is scoped to a single organization and needs no browser. Provide it to the CLI through the DOTENV_API_KEY environment variable, sourced from your CI provider's encrypted secret store.

terminal
# The key comes from the CI secret store, never the repo
export DOTENV_API_KEY="$CI_DOTENV_API_KEY"
dotenv pull my-app/production/api --output=.env

Never hardcode keys

Keep keys out of source control and out of your pipeline definition. Always reference them from masked/secret variables so they are encrypted at rest and redacted from logs. A key committed to a repository should be considered compromised and rotated immediately.

Scope each key narrowly and rotate it

Use a dedicated API key per pipeline or environment rather than one shared key everywhere. That way you can revoke or rotate a single key without disrupting everything, and audit entries attribute actions to the right source. Rotate keys on a schedule and after any suspected exposure.

Pull at runtime, don't bake secrets into artifacts

Fetch secrets as late as possible. Pulling into a transient .env or directly into the job environment means nothing sensitive is stored in an image layer, a build artifact, or a cache:

terminal
# Into the process environment for the current step only
eval "$(dotenv export my-app/production/api --format=shell)"
# Or a transient file you never commit or upload
dotenv pull my-app/production/api --output=.env

Prefer client-managed encryption for sensitive projects

With client-managed (zero-knowledge) encryption, decryption happens on the runner and dotEnv Cloud only stores ciphertext. Supply the key to the pulling step the same careful way you supply the API key (a file passed with --client-key, or the value in DOTENV_CLIENT_KEY) and keep it in your CI secret store:

terminal
export DOTENV_API_KEY="$CI_DOTENV_API_KEY"
export DOTENV_CLIENT_KEY="$CI_DOTENV_CLIENT_KEY"
dotenv pull my-app/production/api --output=.env
Passing a key value (rather than a file path) on the command line or via an environment variable can leak through shell history and the process list. Prefer a key file with --client-key=./key where your platform allows writing one.

Match the right level to the right pipeline

Pull the most specific level a job needs, so a staging deploy can never accidentally read production-only secrets. Combined with Environment Inheritance, this keeps each pipeline's blast radius small.

Clean up and verify

  • Delete any .env file the job wrote once it is no longer needed, and never cache or upload it as an artifact.
  • Confirm your CI provider masks the secret variables you reference.
  • Review the audit log periodically to confirm automated activity looks as expected.

Applied together, these practices let your pipelines use real secrets without ever persisting them where they don't belong.