xecret with Docker
Keep secrets out of image layers — runtime injection, BuildKit secret mounts, Compose, and the export format that exists only for docker run.
Updated 2026-08-16 · read this page as markdown
On this page
One rule shapes everything on this page:
Runtime injection — the right default
Do not put secrets in the image at all. Build an image that expects its configuration from the environment, and supply it when the container runs.
# Dockerfile — no secrets anywhere in it
FROM node:22-slim
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["node", "server.js"]xecret run -- docker run --rm \
-e DATABASE_URL -e STRIPE_SECRET_KEY \
my-appThe mechanism is worth understanding. xecret run puts the values into its
own process environment. -e NAME with no =value tells Docker to
forward the variable of that name from the surrounding environment. The value
therefore never appears on a command line, in your shell history, or in ps
output.
Writing -e DATABASE_URL="$DATABASE_URL" would undo all of that.
When the container should fetch its own secrets
For an orchestrator that cannot forward a long list of variables, put the CLI in the image and make it the entrypoint:
FROM ghcr.io/playxoft/xecret:1.2.0 AS xecret
FROM node:22-slim
COPY --from=xecret /usr/local/bin/xecret /usr/local/bin/xecret
WORKDIR /app
COPY . .
RUN npm ci && npm run build
ENTRYPOINT ["xecret", "run", "--"]
CMD ["node", "server.js"]docker run --rm -e XECRET_TOKEN=xst_… my-appWhat you are trading: the container now needs network access to xecret at
start-up, and one credential (XECRET_TOKEN) instead of many. That is often a
good trade — one secret to manage in your orchestrator rather than twenty — but
it does mean a xecret outage delays a container restart. There is no offline
cache under a service token, deliberately; see
the offline cache.
Keep the token scoped to exactly one project and environment, read-only.
Build-time secrets
Sometimes a build genuinely needs a credential — a private npm registry, a private Go module proxy. Use BuildKit secret mounts, the one mechanism that keeps the value out of layers and build history:
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY . .
# The secret exists only for this RUN, as a tmpfs file. It is in no layer.
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ciFeed one value from xecret into the mount without it touching disk:
XECRET_TOKEN=xst_… xecret secrets get NPM_TOKEN --plain \
| docker build --secret id=npm_token,src=/dev/stdin .Docker Compose
Compose reads env_file and environment from the host. Inject at the point
you run Compose:
# compose.yaml
services:
api:
build: .
environment:
- DATABASE_URL
- STRIPE_SECRET_KEYxecret run -- docker compose upAgain: names only, no =value. Compose forwards each from the environment
xecret run created.
For a stack where each service needs a different environment, run Compose under the widest one and scope inside your application, or split the services into separate projects with separate tokens.
The docker export format
xecret pull --format docker produces the shape docker run --env-file
expects. It exists for runtime use:
xecret pull --format docker -o /run/secrets.env # 0600
docker run --env-file /run/secrets.env my-app
rm /run/secrets.envEven here you are writing plaintext to disk, so prefer the -e NAME form
above. Never feed this format to docker build.
What not to do
# ✗ bakes every secret into a layer, readable with `docker history`
xecret pull --format docker > args.txt && docker build $(cat args.txt) .# ✗ the file is in the layer even if a later step deletes it
COPY .env .env
# ✗ ARG and ENV values are visible in image metadata
ARG STRIPE_SECRET_KEY
ENV STRIPE_SECRET_KEY=$STRIPE_SECRET_KEYDeleting a file in a later layer does not remove it from the image. Each layer is kept.
Kubernetes and other orchestrators
The same fork applies:
- Sync into the platform's own secret store at deploy time, and let the platform mount them. Your workloads need no xecret access at all.
- Give each workload a service token and use
xecret runas the entrypoint, as above.
The first is the conventional choice for production clusters; the second is simpler to keep in sync. Both are legitimate — pick one deliberately rather than ending up with half of each.
Next
- Secrets in CI — building the image.
- Tokens — scoping the credential the container holds.
- The offline cache — why CI and containers have none.