.env in CI without leaking it

CI is where secrets most often leak: echoed in logs, baked into artifacts, or committed "just for the pipeline". The rules are simple and mostly free.

Rule 1: the pipeline gets its own secrets, not your .env

Your local .env contains YOUR keys. CI should run with its own scoped credentials: a test database, a sandbox payment key, a deploy token with least privilege. When the pipeline is compromised (a malicious dependency, a leaked log), the blast radius is the CI keys, not production.

Rule 2: use the platform's secret store, with masking

GitHub Actions:

# Settings -> Secrets and variables -> Actions
env:
  DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
  API_KEY: ${{ secrets.SANDBOX_API_KEY }}

GitLab CI: Settings → CI/CD → Variables, and tick Masked (hides the value in job logs) and Protected (only protected branches see it). The masking matters: one stray printenv in a debug session otherwise publishes everything to whoever can read logs.

Rule 3: never write the .env into the repo or the image

The classic accidents: committing .env.ci "temporarily", or a Dockerfile with COPY .env . (that one gets its own guide). Both put plaintext secrets into places with long memories.

Rule 4: inject at runtime, write nothing to disk

For steps that genuinely need the shared team config (integration tests against a staging stack), inject variables into the process instead of materializing a file:

$ keyline run -- npm test   # secrets exist only in the child process env

A scoped machine identity with read-only access to a ci environment beats a copy of a human's .env in every way: revocable, auditable, least-privilege.

The checklist

More guides

Two minutes to the first encrypted push

$ curl -fsSL keyline.sh/install | sh

Solo is free forever. Team is $19 flat for up to 10 people, 14-day trial.

See the whole journey

No card for Solo. Your secrets are encrypted before they leave your laptop.