> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quintai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Run an agent in a container with Docker

> Build your agent image from Quint's agent-base image, give the container a workload key, and run it with Docker so each start shows as a run on the Workloads page.

At the end of this page your agent runs in a Docker container with Quint inside it, and each container start shows as a run on its workload's page. The container starts as root so that Quint can route the agent's network traffic through itself. Quint then starts your agent as a separate user without root rights.

## What you need

* A workload and its key. See [Set up a workload](https://docs.quintai.dev/dashboard/workloads.md#set-up-a-workload); it needs the **Admin** or **Owner** role.
* Access to the agent-base image, Quint's base image for agent containers. The image is private. Your Quint contact gives you access to it and the details for signing in to the image registry. Sign in before you check the signature or build your image (the first two steps below), and in any CI job that builds it.
* Docker on Linux, or Docker Desktop on Mac. For Docker Desktop on Windows, rootless Docker or Podman, talk to your Quint contact first.
* The **docker run** snippet from the workload's **Keys** tab. The one extra permission Quint needs is `--cap-add NET_ADMIN`, which lets it control the container's network. Don't add `--user`, `--privileged`, `--network host`, `--read-only` or `--security-opt seccomp=unconfined`: Quint refuses to start with any of them.
* At least `--memory 512m` for Quint alone, plus what your agent needs. The dashboard's snippet uses `2g`.

**1. Pin the image by digest**

The **Dockerfile** snippet on the workload's **Keys › Connect a container** names the agent-base image on its `FROM` line, pinned by digest (a fingerprint of one exact build). Pin agent-base by digest, never by tag.

Each image is signed. Once you have signed in to the registry, check the signature before you build from the image. In the command, replace `<image>@sha256:<digest>` with the reference on the snippet's `FROM` line:

```bash theme={"dark"}
cosign verify \
  --certificate-identity-regexp '^https://github\.com/Quint-Security/(proxy/\.github/workflows/release\.yml|quint-platform/\.github/workflows/proxy-release\.yml)@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  <image>@sha256:<digest>
```

The command accepts the two release workflows that sign Quint's images, so it works for every release digest.

**2. Build your image**

Start from the dashboard's **Dockerfile** snippet. It already has the `FROM` line and the line that declares the Claude Code version to Quint. Change two things:

* Replace the snippet's placeholder install line with the commands that install Claude Code, in the version the snippet names. Quint can hold tool calls only for the Claude Code versions it knows, and an image that declares a version it doesn't know does not start. agent-base is `debian:bookworm-slim`, so add whatever else your agent needs.
* Set `CMD` to your agent's command. Keep the base image's `ENTRYPOINT`.

Leave the line that writes `/etc/quint/harness-versions.json` as it is. The file tells Quint which agent is in the image and its exact version, and Quint needs it to hold a tool call while a person decides. Check that `claude --version` in your image prints the version the file declares. Without the file the agent still runs, but every rule that should ask for approval refuses the tool call straight away, and nobody is asked.

In a container, rules on tool calls cover Claude Code. For any other agent, leave `claude-code` out of the file, count on network-level coverage only, and ask your Quint contact about tool calls.

The agent runs as a non-root user, so paths it writes must be writable by it, and its tools must honour `HTTPS_PROXY`.

**3. Give the container its key**

Save the key from the dashboard to a file named `quint-key`. Never put the key itself in the `docker run` command. Quint removes the key from the agent's environment, so the agent never sees it.

* **Docker on Linux, run as root**: the file must be owned by root and readable by root only.
  ```bash theme={"dark"}
  sudo chown 0:0 quint-key && sudo chmod 0400 quint-key
  ```
* **Rootless Docker or rootless Podman**: don't use `sudo`. Leave the file owned by your own account and run `chmod 0400 quint-key`. If the file no longer belongs to your account under rootless Podman, `podman unshare chown 0:0 quint-key` returns it to you.
* **Docker Desktop**: don't mount a key file. Docker Desktop's file sharing ignores a file's owner and permissions, so Quint refuses it. Set `QUINT_WORKLOAD_KEY` from your secret store instead, and pass it by name only.

**4. Say who started the run**

Export `QUINT_RUN_INITIATOR` in the shell or CI job that runs `docker run`, set to the email address the person starting the run signs in to Quint with. The commands in the next step pass it into the container by name. In CI, Quint also reads `GITHUB_ACTOR`, `GITLAB_USER_LOGIN`, `BUILDKITE_BUILD_CREATOR_EMAIL` or `CIRCLE_USERNAME` when you pass it into the container. If none of them is set, the run does not say who started it, and approvers can deny its held tool calls but can't approve them. The person who started a run can't approve its held tool calls, so an approval takes two people.

**5. Run the container**

First export `QUINT_RUN_INITIATOR` in your shell, set to the email address of the person starting the run. Both commands below pass it through by name, and Docker passes only exported variables. Then, with a key file:

```bash theme={"dark"}
docker run --cap-add NET_ADMIN --stop-timeout 90 --memory 2g \
  -e QUINT_WORKLOAD=ci-agent \
  -e QUINT_RUN_INITIATOR \
  -e QUINT_WORKLOAD_KEY_FILE=/run/secrets/quint \
  -v ./quint-key:/run/secrets/quint:ro \
  my-agent:latest
```

On Docker Desktop, with `QUINT_WORKLOAD_KEY` also set in your shell or listed in an `--env-file`:

```bash theme={"dark"}
docker run --cap-add NET_ADMIN --stop-timeout 90 --memory 2g \
  -e QUINT_WORKLOAD=ci-agent \
  -e QUINT_RUN_INITIATOR \
  -e QUINT_WORKLOAD_KEY \
  my-agent:latest
```

`QUINT_WORKLOAD` is the workload's name. If `QUINT_RUN_INITIATOR` is not exported in your shell, the container starts without it and its log says `warning: no run initiator`.

`--stop-timeout` must be longer than the workload's **Stop grace** plus its **Evidence flush timeout** plus a few seconds. 30 covers the defaults. 90 covers the largest values an admin can set, so it stays right if someone raises them later. With a timeout that is too short, Docker ends the container before Quint has sent its last records, and the run shows **Lost contact**.

**6. Check the run**

The container log shows `quint-init: enrolled run` followed by the run's ID and the workload's name. On the [Workloads](https://docs.quintai.dev/dashboard/workloads.md) page, open the workload: the run is on its **Runs** tab with the status **Running**.

Your own rules refuse or hold the run's tool calls only once **Enforcement** is on for your workspace. Until then the run records what they would have done. See [Decide held tool calls](https://docs.quintai.dev/dashboard/workloads.md#decide-held-tool-calls).

## Stop the container

* **`docker stop`**: Quint passes the stop to the agent and waits for the workload's **Stop grace**. An agent that exits in time ends the run with its own exit code. One that doesn't is stopped by force, and the run ends "Terminated from the dashboard or by a runtime stop." with exit code 78. Quint then sends the run's last records and closes the run.
* **Terminate run** on the run's page stops the run from the dashboard. The container exits with code 78 within about 30 seconds. See [Stop a run](https://docs.quintai.dev/dashboard/workloads.md#stop-a-run).
* **`docker kill`**, or a stop timeout that runs out first, ends the container before Quint can close the run. The run then shows **Lost contact**, and its last events may be missing.

## On Kubernetes

The workload's **Kubernetes** snippet runs the same image as a pod. Copy it rather than typing it, and set its `QUINT_RUN_INITIATOR` to the person or system that starts the run, as "Say who started the run" above describes.

The snippet is the `spec:` part of a pod: put it under your own `apiVersion`, `kind` and `metadata`, or use it as the pod template of a Job.

Before you apply it, create a namespace that allows the `privileged` Pod Security level, and a Secret that holds the key from the dashboard under the name `key`:

```bash theme={"dark"}
kubectl create namespace agents
kubectl label namespace agents pod-security.kubernetes.io/enforce=privileged
kubectl -n agents create secret generic quint-workload-key --from-file=key=./quint-key
```

`quint-workload-key` is the Secret name the snippet uses, and `quint-key` is the file you saved the key to. Then:

* Keep the snippet's `seccompProfile`, `runAsUser: 0`, `NET_ADMIN` and `defaultMode: 0400` lines as they are.
* Don't add `fsGroup`, `runAsNonRoot: true`, `hostNetwork` or `privileged`.
* Give `terminationGracePeriodSeconds` the value you would give `--stop-timeout`.
* Keep `requests` equal to `limits`, as the snippet has them. The pod starts without that, but with it the agent, not Quint, is stopped first if memory runs out.

Namespaces at the `baseline` or `restricted` level reject `NET_ADMIN`. Use the [Restricted setup](https://docs.quintai.dev/install/kubernetes-restricted.md) there.

## Where it doesn't run

* **ECS Fargate**: it doesn't allow `NET_ADMIN`.
* **gVisor**: its network stack lacks what Quint needs to set the container's network rules.

## If this fails

The container exits and its log ends with a line that starts `quint-init: refused:`, or a `quint-init: warning:` line explains a limit. The table quotes the part of the line to look for, as the agent-base images released so far print it.

| The log says | What it means | What to do |
| - | - | - |
| `missing capabilities: NET_ADMIN` | The container has no `NET_ADMIN` | Add `--cap-add NET_ADMIN`. |
| `seccomp is off; run with the default seccomp profile` | Seccomp is turned off | Remove `--security-opt seccomp=unconfined`. On Kubernetes, set `seccompProfile` `type: RuntimeDefault`. |
| `refusing a privileged container (full capability set)` | The container runs with `--privileged` | Remove it; add only `NET_ADMIN`. |
| `re-exec agent probe` and `operation not permitted`, or `must run as uid 0` | The container was started as another user | Remove `--user`. On Kubernetes, set `runAsUser: 0`. Quint starts the agent as its own user. |
| `QUINT_WORKLOAD_KEY_FILE: open` and `permission denied` | Quint could not read the key file. Either the container was started with `--user`, or rootless Docker or Podman was given a file owned by root | Remove `--user`. With rootless Docker or Podman, keep the file owned by your own account and run `chmod 0400 quint-key`. |
| `this looks like the host network namespace` | The container uses the host's network | Remove `--network host`. |
| `the image root is read-only` | The container runs with `--read-only` | Remove `--read-only`. |
| `set only one of QUINT_WORKLOAD_KEY_FILE or QUINT_WORKLOAD_KEY, not both` | Both key variables are set | Keep one. On Docker Desktop, keep `-e QUINT_WORKLOAD_KEY` and remove the `QUINT_WORKLOAD_KEY_FILE` and `-v` lines. |
| `the workload key file is readable by the agent` | The agent's user could read the key file: its owner or permissions are wrong, or Docker Desktop's file sharing ignored them. With the images released so far, the line ends `mount it 0400 root` in both cases | On Docker Desktop, pass the key with `-e QUINT_WORKLOAD_KEY` instead of a file. Elsewhere, run `chown 0:0` and `chmod 0400` on the file (rootless: keep it yours, `chmod 0400`). On Kubernetes, set `defaultMode: 0400` and no `fsGroup`. |
| `run auth error 401` | The key is wrong or revoked, or it belongs to a different workload than the one `QUINT_WORKLOAD` names | Check `QUINT_WORKLOAD` and the key. If the key is lost or revoked, create a new one on the workload's **Keys** tab. |
| `run auth error 403` | The workload is **Disabled**, or your workspace can't start new runs | Set **Status** back to **Active** under **Edit settings**. If it already is, talk to your Quint contact. |
| `registration kept failing` and `run register request failed`, about a minute after the start | The container could not reach Quint | Check that the container can reach `https://api.quintai.dev`, then start it again. |
| `registration kept failing` and `status 409`, about a minute after the start | The workload's settings could not be applied, and the workload page shows "Settings could not be applied." A line containing `variant_failed` means the same | Open the workload, click **Retry**, then start the container again. |
| `at its concurrency cap` | The workload already has as many live runs as its **Concurrency cap**, or Quint was too busy to register the run. The container retried for a minute | Wait for a run to end, or raise the cap under **Edit settings**. Then start the container again. |
| `agent_base_too_old` | This image is too old for the workload's network or approval settings | Rebuild from a newer agent-base image. |
| `variant_not_ready` | The workload's new settings are not applied yet | Start the container again when the workload page no longer shows "Applying settings…". If the page shows "Settings could not be applied.", click **Retry** first. |
| `the registration carried no pack digest` | Quint has no signed rules to give this run | Talk to your Quint contact. |
| `remote approval unavailable for this harness`, exit code 70 | Quint doesn't know the Claude Code version the image declares | Install and declare the version in the dashboard's Dockerfile snippet, and build from the image on that snippet's `FROM` line. |
| `daemon not healthy`, exit code 70, right after start | Quint could not start, most often because the memory limit is too low | Raise `--memory` to at least `512m` plus your agent's needs. |
| `warning: no run initiator` | The run does not say who started it: `QUINT_RUN_INITIATOR` was not exported in the shell that ran `docker run`, or it is empty | Export it and start the container again. This run's held tool calls can be denied but not approved. |
| A warning ending `require_approval denies immediately in this container` | The image declares no Claude Code version | If the image runs Claude Code, put back the snippet's line that writes `/etc/quint/harness-versions.json`. For any other agent, the warning is expected. |

## Next

* [Use a workload](https://docs.quintai.dev/dashboard/workloads.md#use-a-workload): read runs, decide held tool calls, stop a run.
* [Run Quint with agents in containers](https://docs.quintai.dev/install/containers.md) for the Kubernetes setups.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.