K0KEYZERO

Use Case

Secret Management for Docker

Resolve secrets at container runtime from Vault, AWS, or 1Password instead of baking them into Docker images or compose files.

Docker's default approach to secrets is to pass them as environment variables or build arguments. Both have well-documented problems: environment variables are visible in docker inspect, build arguments are stored in image layers, and .env files used with docker-compose sit on disk as plaintext. This is a textbook case of runtime secret resolution vs. env files. Docker Swarm has a built-in secrets mechanism, but it is limited to Swarm mode and does not integrate with external vault backends.

Common Docker Secret Anti-Patterns

Build Arguments

ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL
RUN npm run migrate

Build arguments are stored in the image history. Anyone with access to the image can run docker history and see every ARG value. Even multi-stage builds do not fully protect against this -- the argument exists in the build cache.

Baked Environment Variables

ENV API_KEY=sk-abc123

Hardcoded in the Dockerfile. Visible to anyone who pulls the image. Committed to source control with the Dockerfile.

Compose .env Files

# docker-compose.yaml
services:
  app:
    env_file: .env

The .env file contains plaintext credentials and must exist on every machine that runs the compose stack. It is frequently committed to Git by accident (despite .gitignore entries), left on CI runners, and readable by any process on the host.

Docker Secrets (Swarm Only)

Docker Swarm secrets mount credentials as files in /run/secrets/. This is a reasonable approach, but it requires Swarm mode, does not support external vault backends, and has no policy layer for access control.

How KeyZero Works with Docker

KeyZero resolves secrets at container runtime, not build time. Credentials never appear in Dockerfiles, image layers, or compose files.

Pattern 1: kz run as Entrypoint

Install KeyZero in your Docker image and use kz run as the entrypoint:

FROM node:20-slim

# Install KeyZero
RUN curl -fsSL https://get.keyzero.dev | sh

WORKDIR /app
COPY . .
RUN npm ci --production

# keyzero resolves secrets at runtime
ENTRYPOINT ["kz", "run", "--"]
CMD ["node", "server.js"]

The .keyzero.toml maps environment variables to vault references:

[secrets]
DATABASE_URL      = { provider = "vault", ref = "secret/data/prod/db/url" }
REDIS_URL         = { provider = "vault", ref = "secret/data/prod/redis/url" }
STRIPE_SECRET_KEY = { provider = "aws-sm", ref = "prod/stripe-secret-key" }
JWT_SIGNING_KEY   = { provider = "vault", ref = "secret/data/prod/jwt/key" }

When the container starts, kz run resolves each secret from the configured backend and injects the values into the node server.js process. The image contains no credentials. The compose file contains no credentials. The running container's environment has resolved values only in the subprocess -- docker inspect shows only the kz run command.

Pattern 2: Docker Compose Integration

# docker-compose.yaml
services:
  api:
    build: .
    entrypoint: ["kz", "run", "--"]
    command: ["node", "server.js"]
    volumes:
      - ./keyzero.toml:/app/.keyzero.toml:ro
    environment:
      - VAULT_ADDR=https://vault.internal:8200

  worker:
    build: ./worker
    entrypoint: ["kz", "run", "--"]
    command: ["python", "worker.py"]
    volumes:
      - ./keyzero.toml:/app/.keyzero.toml:ro
    environment:
      - VAULT_ADDR=https://vault.internal:8200

Each service resolves its own secrets at startup. The compose file contains only the Vault address (not a secret) and the KeyZero config is mounted read-only.

Pattern 3: Sidecar for Non-Modifiable Images

For images you cannot modify (third-party services, vendor images), use a KeyZero init container pattern:

# docker-compose.yaml
services:
  secrets-init:
    image: getkeyzero/keyzero:latest
    command: ["kz", "export", "--format", "env", "--output", "/secrets/.env"]
    volumes:
      - secrets-vol:/secrets
      - ./keyzero.toml:/app/.keyzero.toml:ro

  postgres:
    image: postgres:16
    depends_on:
      secrets-init:
        condition: service_completed_successfully
    env_file:
      - /secrets/.env  # resolved by KeyZero, not committed to Git
    volumes:
      - secrets-vol:/secrets:ro

volumes:
  secrets-vol:

The init container resolves secrets and writes them to a shared volume. The target container reads them as a standard env file. The env file exists only in a Docker volume, not on the host filesystem.

Blind Mode in Containers

For containers running AI workloads, blind mode prevents credential exposure in agent context windows:

ENTRYPOINT ["kz", "run", "--blind", "--"]
CMD ["python", "agent.py"]

The agent process inside the container receives opaque tokens. The KeyZero proxy runs alongside the process and swaps tokens for real credentials on outgoing HTTP requests. This is particularly relevant for AI inference containers that call external APIs -- the LLM component never sees raw credentials. For more deployment strategies, see five patterns for secret-safe AI deployments.

Comparison with Docker Swarm Secrets

FeatureDocker Swarm SecretsKeyZero
Requires Swarm modeYesNo
Works with ComposeNo (standalone)Yes
External vault backendsNoVault, AWS SM, 1Password, GCP SM, Age
Policy controlNoneCEL-based per-identity policies
AI/agent supportNoneBlind mode, opaque tokens
Secret rotationRecreate serviceBackend handles rotation

Key Principle

Docker images should be credential-free artifacts. Build once, deploy anywhere, resolve secrets at runtime. KeyZero enforces this pattern by moving credential resolution to the container entrypoint, pulling from vault backends that manage rotation, access control, and audit logging independently of the container lifecycle. For container orchestration at scale, see the Kubernetes integration guide. To understand how the resolution pipeline works under the hood, read the KeyZero architecture deep dive.