Skip to main content

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.json config in your project (created by envshed init)

How It Works

Envshed integrates into Docker via two patterns:

  1. Build timeenvshed run wraps your build command, injecting secrets as environment variables so frameworks like Next.js can inline them at compile time
  2. Runtimeenvshed run wraps 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

StageWhat happens
Builderenvshed 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.
Runnerenvshed 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 .
caution

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

  1. Use service tokens, not personal tokens — service tokens are scoped and don't depend on any individual's account. See Service Tokens.
  2. Use read-only tokens — CI/CD and Docker builds only need to read secrets, never write them.
  3. Scope to the narrowest level — use environment-scoped tokens when possible.
  4. Don't bake secrets into images — always use envshed run to inject at runtime. Secrets in image layers can be extracted.
  5. 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.