Guide

Getting Secrets Into a Docker-Based AI Coding Sandbox Safely

Isolating the agent and protecting its key are two different jobs. Here is how to do the second one without an ENV line that lives in your image forever.

Quick Answer

To integrate a vault with a Docker-based AI coding sandbox, never put the secret in an ENV or ARG line in the Dockerfile, because both persist in the image and show up in docker history indefinitely. Instead, use BuildKit's secret mount for anything only needed during the build, a Docker Compose secrets entry or a bind-mounted file for anything the running container needs, or a secrets manager's agent for a longer-lived setup. Each of these keeps the value out of the image and out of docker inspect, and hands it to the container only for as long as it is needed. None of them stop the agent inside the container from reading the secret once it has been handed over; that part is still on you.

A Docker sandbox and a vault solve different problems

Running an AI coding agent inside Docker gives it its own filesystem and, if you set it up that way, its own network policy. That is a real boundary: a mistake the agent makes cannot reach your real files or your other repositories.

None of that answers where the agent's API key lives. A container still needs some credential to call its model provider, push to a repository, or reach whatever service the task requires, and the ordinary ways people hand that credential in are the same ways that leak it.

Vault integration is the part that decides how the secret gets from your host into the running container without sitting somewhere permanent along the way.

The two mistakes that put a secret in the image forever

The first mistake is an ENV or ARG line in the Dockerfile with the actual value written in. Docker's own build documentation is direct about this: build arguments and environment variables are inappropriate for passing secrets to a build, because they persist in the final image. Anyone who can pull or inspect that image can read the value back out with docker history, long after the key has been rotated or the Dockerfile cleaned up.

The second mistake is assuming an environment variable set on a running container is private. It is not. docker inspect prints a running container's environment variables in plain text to anyone who can run that command against it, which includes anyone with access to the Docker daemon, not just the agent inside the container.

  • ENV or ARG with a real secret value: baked into the image, visible in docker history indefinitely.
  • An environment variable on a running container: visible to docker inspect, not just to the process inside.

BuildKit secrets, for anything the build itself needs

If the secret is only needed while the image is being built, for example to pull a private dependency, BuildKit's secret mount is the direct fix. You pass the value with a build flag naming a local file as its source, then consume it inside the Dockerfile with a RUN instruction that mounts that same secret id. The value is available only for the duration of that one instruction and is never written into a layer.

This is the opposite of ENV or ARG in exactly the place it matters: once the build step finishes, the secret is gone from the resulting image, so docker history has nothing to show.

  • A build-time secret flag passes the value in without baking it into the image.
  • A RUN instruction that mounts the secret consumes it for one step, then it disappears.

Compose secrets and bind mounts, for a running container

For a secret the running agent needs, not just the build, Docker Compose's top-level secrets key is the simplest option that does not require Swarm mode. Point a secret at a local file, reference it from your service, and Compose mounts it into the container at /run/secrets instead of setting it as an environment variable.

True Docker secrets, encrypted and distributed over a mutual TLS connection, are a Swarm feature only and are not available to a standalone container. Compose's file-based secrets are the practical substitute for local development and for a single-host setup, which covers most people running a coding agent sandbox on their own Mac or a dev server.

A plain bind-mounted file works too, as long as you are deliberate about it: write the secret to a file outside the build context, mount it read-only at container start, and set its permissions so only the process that needs it can read it. The point in every version is the same. The value lives in a file the container reads at runtime, never in a layer or in the process environment table that docker inspect prints.

  • Compose secrets: file source, mounted at /run/secrets, no Swarm required.
  • True Docker secrets, the encrypted kind, need Swarm and are not available to a plain container.
  • A bind-mounted, read-only file is a fine substitute when neither of the above fits your setup.

What a VS Code dev container and Pi still leave open

If you are using a VS Code dev container, the devcontainer.json that defines it is about the development environment, not secret delivery. It mounts your project, sets up the toolchain, and can enforce a network policy, such as the default-deny firewall in the example dev container the Claude Code project publishes. None of that moves your secrets into the container safely on its own. You still apply one of the methods above inside it.

If the agent you are running is Pi, a minimal coding agent with no sandbox of its own, the container is not an optional extra. It is the entire boundary around the agent, so get the secret handling right before you get anything else right.

What vault integration does not solve

Every method above controls where the secret sits before and during delivery into the container. None of them control what the agent does with the value after it has it. A process that received a key as a file in /run/secrets or as an environment variable can still read that file, print it, or send it to a model as part of its context, exactly like a process running outside Docker.

So scope the key. Give the container's credential the least access it can get away with, and keep anything with broad access out of a session where an agent is experimenting.

  • Hiding where a secret sits is not the same as limiting what the credential can do.
  • Scope every key to the one job the container needs, not to everything the provider account can do.

Where Forkbench fits on the Mac side

Forkbench runs in a terminal on your Mac, and its Vault keeps secrets in your Keychain so a command can use one by name instead of you typing the value in. If that terminal's command is a docker run or a docker compose up, Forkbench can hand the API key to that one command at the moment it starts, the same way it does for any other command, so the key is not sitting in a committed Dockerfile or a compose file checked into your repository.

Forkbench's own protection stops at the container boundary. Once docker has the value, whether it ends up in /run/secrets or in the container's environment table is up to how you wrote the Dockerfile or compose file, not up to Forkbench. And an unpinned Vault key is still readable by whatever program it was handed to, container or not.

  • Forkbench can hand a docker command its secret at launch, without the value sitting in a file.
  • Once docker has the value, what happens to it inside the container is the container setup's job, not Forkbench's.

Related: How to sandbox AI coding agents on macOS safely, Vault management for multi-agent systems on GitHub, Give an agent deploy access without the credential, How Forkbench handles your data

Frequently asked

  • Does putting a secret in an ENV line in a Dockerfile keep it safe?

    No. Docker's own documentation says build arguments and environment variables persist in the final image, so the value stays visible in docker history even after you remove it from the Dockerfile and rebuild.

  • Do Docker secrets work without Swarm mode?

    The encrypted secrets backend is a Swarm-only feature and is not available to a standalone container. Docker Compose's file-based secrets work without Swarm and are the usual substitute for local development.

  • Can I use Docker Compose secrets for local development?

    Yes. Point a secrets entry at a local file and reference it from your service; Compose mounts it at /run/secrets inside the container instead of setting it as an environment variable.

  • Does a VS Code dev container protect my API keys automatically?

    No. A devcontainer.json configures the development environment and can enforce a network policy, but it does not move secrets into the container for you. You still need BuildKit secrets, Compose secrets, or a mounted file.

  • Does Forkbench manage secrets inside a Docker container?

    Forkbench can hand a docker command its secret at the moment you launch it from a Forkbench terminal, using the Vault. What the container does with that value once docker has it is the container setup's responsibility, not Forkbench's.

Keep reading