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.

CLI v7.2.7

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.

Which approach to use

The four runtime approaches below all end with secrets in the process environment. They differ in where the CLI runs and what the container needs to reach:

Approach CLI lives in Container needs Pick it when
Entrypoint The image Network to the varsafe API Default. Restart picks up rotated secrets, nothing on the host
Inject from the host The host Nothing You cannot or will not add the CLI to the image
Export to env_file The host Nothing A tool insists on a file — see the cleanup caveat below
Pipe via stdin The host Nothing One-off runs and scripts, no file at any point

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:

# 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:

# 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 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