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.
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 |
varsafe as the Entrypoint (recommended)
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: 3Rotate 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-imagemust show no secret values in any layer- Rotate a test secret in the dashboard, restart the container, and confirm the new value is live