Skip to content
varsafe
Esc
navigateopen⌘Jpreview
On this page

Docker

Inject varsafe secrets into containers at runtime — entrypoint wrapping, compose interpolation, and build-time mounts that never bake secrets into image layers.

The goal: containers receive secrets in their process environment at runtime, and nothing sensitive ever lands in an image layer, a build argument, or a committed file. The only credential a container needs is a single team-scoped API token.

Build Time vs Runtime

This distinction drives every pattern on this page:

  • Runtime — the container fetches or receives secrets when it starts. Rotating a secret in varsafe takes effect on the next restart, with no rebuild. This is the default for application secrets.
  • Build time — a secret is needed while building the image (private registry credentials, license keys). Use BuildKit secret mounts, never ARG/ENV.

Install the CLI in the image and make varsafe run the entrypoint wrapper — secrets exist only in the application process environment, fetched fresh at every container start. This is a tested configuration:

# Runtime secret injection in a container: varsafe wraps the entrypoint,
# so secrets exist only in the process environment — never in a layer,
# build arg, or file.
FROM oven/bun:1-slim

RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && curl -fsSL https://varsafe.dev/install.sh | bash

ENV PATH="/root/.varsafe/bin:${PATH}"

WORKDIR /app
COPY . .
RUN bun install --production

# VARSAFE_TOKEN is provided at runtime (docker run -e VARSAFE_TOKEN=...)
ENTRYPOINT ["varsafe", "run", "-i", "your-project-id", "-e", "production", "--"]
CMD ["bun", "src/index.ts"]

Run it with the token as the only secret you handle yourself:

docker run -e VARSAFE_TOKEN=vs_at_... my-image

Or with compose, taking the token from the host environment (also a tested configuration):

# Runtime injection with Docker Compose: the token comes from the host
# environment; application secrets never appear in this file.
services:
  app:
    build: .
    environment:
      VARSAFE_TOKEN: ${VARSAFE_TOKEN}
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 30s
      timeout: 5s
      retries: 3

Rotate secrets in varsafe and containers pick them up on the next restart — no rebuild, no redeploy.

Inject from the Host Environment

Keep the CLI out of the image entirely: inject secrets into the shell that starts the containers, and let compose interpolate them.

varsafe run -- docker compose up
services:
  app:
    image: my-image
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - API_KEY=${API_KEY}

Compose resolves ${VAR} from the environment varsafe run created. Filter what gets injected with --include:

varsafe run --include 'DB_*,API_*' -- docker compose up

With docker run, list each variable to pass through (a bare -e KEY with no value forwards it from the host):

varsafe run -- docker run -e DATABASE_URL -e API_KEY my-image

Export to env_file

When a file is unavoidable, export one — and keep it out of git and off persistent disk where possible.

With docker run, use --format docker (unquoted KEY=value), since docker run --env-file treats quotes as literal characters:

varsafe export -f docker -o .env.production
docker run --env-file .env.production my-image

With compose, prefer a plaintext export to RAM-backed tmpfs, consumed and deleted in one command chain:

varsafe export --plain --tmpfs -o app.env
docker compose --env-file /dev/shm/app.env up -d
rm -f /dev/shm/app.env

Pipe via stdin

Pass secrets into docker run without any file at all:

varsafe export -f docker | docker run -i --env-file /dev/stdin my-image

Build-Time Secrets

For secrets needed during docker build, use BuildKit secret mounts — the secret is available to the single RUN instruction and never written to a layer:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=env,target=/tmp/.env \
    export $(cat /tmp/.env | xargs) && npm run build
varsafe export -f docker | docker build --secret id=env,src=/dev/stdin .

CI/CD

In a pipeline, store the API token as a masked CI variable and combine it with any pattern above — the full walkthrough, with tested GitLab CI and GitHub Actions configurations, is in CI/CD Pipelines.

Verify

  • docker run -e VARSAFE_TOKEN=vs_at_... my-image env | grep -c '=' inside a shell entrypoint shows the injected variables arrived (avoid printing the values themselves)
  • docker history my-image must show no secret values in any layer
  • Rotate a test secret in the dashboard, restart the container, and confirm the new value is live