Docker and .env: keeping secrets out of images

Images are archives with perfect memory. Most Docker secret leaks are one of three mistakes, all of them one line long.

Mistake 1: COPY .env .

The file is now a layer. Deleting it in a later layer changes nothing; docker history and any registry pull can recover it. If an image with a copied .env ever left your machine, rotate those secrets today.

Mistake 2: ENV API_KEY=... in the Dockerfile

Same problem, different spelling: build args and ENV values are baked into image metadata, visible with docker inspect. Dockerfiles are configuration, and configuration gets committed; secrets in them end up in git too.

Mistake 3: secrets in build args

--build-arg NPM_TOKEN=... persists in the image history as well. If the build genuinely needs a secret (private package registry), use BuildKit secret mounts, which exist precisely for this:

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
$ docker build --secret id=npmrc,src=$HOME/.npmrc .

The secret is available during that RUN step and absent from every layer.

The right pattern: inject at runtime

# docker run reads the file at start; nothing enters the image
$ docker run --env-file .env my-app

# docker compose
services:
  app:
    env_file: .env

The image stays generic; the environment supplies the secrets. Which leaves only the team problem: everyone needs the same current .env next to their compose file. That's the part Keyline handles: keyline pull before docker compose up, encrypted end to end, and nobody pastes anything in chat. Or skip the file entirely:

$ keyline run -- docker compose up   # compose interpolates from the process env

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.