Docker
Inject Envshed secrets into Docker builds and containers using service tokens and the envshed run command. No .env files are baked into your images — secrets are fetched at build time and runtime.
Prerequisites
- An Envshed account with a project and environment configured
- A service token with read access to the target environment
- A
.envshed.jsonconfig in your project (created byenvshed init)
How It Works
Envshed integrates into Docker via two patterns:
- Build time —
envshed runwraps your build command, injecting secrets as environment variables so frameworks like Next.js can inline them at compile time - Runtime —
envshed runwraps your start command, injecting secrets into the process environment on every container start
Secrets never touch the filesystem. They exist only in the process environment of the wrapped command.
Dockerfile Pattern
Here's a production-ready multi-stage Dockerfile that uses Envshed at both build and runtime:
# Stage 1: Dependencies
FROM node:22-slim AS dependencies
RUN corepack enable pnpm
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# Stage 2: Build with secrets injected
FROM node:22-slim AS builder
RUN corepack enable pnpm
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
# These build args are passed from the CI/CD environment
ARG ENVSHED_ENVIRONMENT
ARG ENVSHED_TOKEN
# Install envshed and build with secrets injected
RUN if [ -n "$ENVSHED_TOKEN" ]; then \
pnpm add -g envshed && \
envshed run -e $ENVSHED_ENVIRONMENT -- pnpm build; \
else \
pnpm build; \
fi
# Stage 3: Production runtime
FROM node:22-slim AS runner
RUN corepack enable pnpm
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME="0.0.0.0"
# Install envshed for runtime secret injection
RUN pnpm add -g envshed
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/public ./public
COPY --from=builder --chown=node:node /app/.envshed.json ./.envshed.json
USER node
EXPOSE 3000
# Secrets are injected at container start — not baked into the image
CMD ["sh", "-c", "envshed run -e $ENVSHED_ENVIRONMENT -- node server.js"]
Key Details
| Stage | What happens |
|---|---|
| Builder | envshed run -e $ENVSHED_ENVIRONMENT -- pnpm build injects secrets as env vars during the build. Frameworks like Next.js inline NEXT_PUBLIC_* variables at this stage. |
| Runner | envshed run -e $ENVSHED_ENVIRONMENT -- node server.js fetches fresh secrets on every container start. If secrets rotate, just restart the container. |
The .envshed.json File
The .envshed.json config tells the CLI which org, project, and environment to pull from. Copy it into your Docker image so the CLI can resolve the correct secrets:
COPY --from=builder --chown=node:node /app/.envshed.json ./.envshed.json
If you prefer to pass everything via flags instead, you can skip copying .envshed.json and use explicit args:
CMD ["sh", "-c", "envshed run -o my-org -p my-project -e $ENVSHED_ENVIRONMENT -- node server.js"]
Fallback Without Token
The conditional if [ -n "$ENVSHED_TOKEN" ] in the build stage allows the Dockerfile to work in local development without a service token. When ENVSHED_TOKEN is not set, the build runs normally using whatever environment variables are already present.
Building the Image
Pass the service token and environment as build arguments:
docker build \
--build-arg ENVSHED_TOKEN=envshed_svc_... \
--build-arg ENVSHED_ENVIRONMENT=production \
-t my-app .
Build arguments are visible in the image layer history. The ENVSHED_TOKEN in the builder stage is only used during the build and is not present in the final runner stage. However, the runner stage needs the token at runtime via environment variables (not build args).
Running the Container
Pass the service token and environment as runtime environment variables:
docker run \
-e ENVSHED_TOKEN=envshed_svc_... \
-e ENVSHED_ENVIRONMENT=production \
-p 3000:3000 \
my-app
Or with Docker Compose:
services:
app:
build:
context: .
args:
ENVSHED_TOKEN: ${ENVSHED_TOKEN}
ENVSHED_ENVIRONMENT: production
environment:
- ENVSHED_TOKEN=${ENVSHED_TOKEN}
- ENVSHED_ENVIRONMENT=production
ports:
- "3000:3000"
Monorepo Dockerfile
For monorepos, the Dockerfile pattern is similar but includes workspace-aware dependency installation:
FROM node:22-slim AS dependencies
RUN corepack enable pnpm
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/my-app/package.json ./apps/my-app/
COPY packages/ ./packages/
RUN pnpm install --filter @my-org/my-app --frozen-lockfile
# Build stage
FROM node:22-slim AS builder
RUN corepack enable pnpm
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
ARG ENVSHED_ENVIRONMENT
ARG ENVSHED_TOKEN
RUN if [ -n "$ENVSHED_TOKEN" ]; then \
pnpm add -g envshed && \
envshed run -e $ENVSHED_ENVIRONMENT -- pnpm build; \
else \
pnpm build; \
fi
# ... runner stage same as above
Security Best Practices
- Use service tokens, not personal tokens — service tokens are scoped and don't depend on any individual's account. See Service Tokens.
- Use read-only tokens — CI/CD and Docker builds only need to read secrets, never write them.
- Scope to the narrowest level — use environment-scoped tokens when possible.
- Don't bake secrets into images — always use
envshed runto inject at runtime. Secrets in image layers can be extracted. - Rotate tokens regularly — create a new token, update your deployment config, then revoke the old one.
Troubleshooting
"envshed: command not found"
The CLI wasn't installed in the Docker stage. Make sure pnpm add -g envshed (or npm install -g envshed) runs before any envshed command.
"Failed to fetch secrets (HTTP 401)"
The ENVSHED_TOKEN environment variable is missing or the token is expired. Verify the token is passed correctly via --build-arg (build time) or -e (runtime).
"Failed to fetch secrets (HTTP 403)"
The token doesn't have access to the specified environment. Check the token's scope in the Envshed dashboard under Service Tokens.
Build works locally but fails in Docker
Make sure .envshed.json is copied into the Docker build context. If it's in .dockerignore, remove it or pass org/project/env as explicit CLI flags.