Your own container image (in development)
In development
This way is in development, so you can't choose it on the platform yet. Until then, connect a GitHub repository or use a local test runner. Projects already connected this way keep working, and the steps below still apply to them.
Use this way for an agent that ships as a container image, from any registry. For every run, the platform starts a fresh virtual machine (a sandbox) in London. It pulls your image at the exact digest it read and starts your test backend next to it. Then it runs the tests with the agent inside the container. The virtual machine is deleted when the run ends. You don't rebuild anything for us.
Before you start
- An image in any registry (Docker Hub, GitHub Container Registry, Google Artifact Registry, a self-hosted registry …), built for
linux/amd64,linux/arm64or both. - An agent that starts with a command inside the image (for example
python -m my_agent.review_entry) and reaches its tools over HTTP or MCP. - A test backend the agent's tools talk to, plus a way to reset it and read its data. Use test environments and test credentials only, and never point a run at production systems or real customer data.
- For a private image: a user name and a read-only token for the registry (for GitHub Container Registry, a token with
read:packages).
1. Create a project and choose the connection
- In the web app, click "New project", pick "Agent that calls tools", and name it.
- Open Targets and choose "Bring your own image".
2. The image reference and the data boundary
Enter the reference as you would for docker pull, with a tag or a digest and without https://: ghcr.io/acme/agent:1.4, registry.example.com:5000/team/agent@sha256:…, or python:3.12 for Docker Hub.
The wizard shows the data boundary. The image is pulled into a sandbox in our cloud and deleted with the virtual machine when the run ends. Only the run bundle stays: evidence, redacted traces and logs. Pull credentials and project secrets are stored encrypted. Tick "I understand".
3. Registry sign-in (private images only)
Enter the registry (filled in from the reference, docker.io for Docker Hub), the user name and a read-only token, and click "Save sign-in". The platform seals it as it arrives and keeps it as a project secret. The page shows the registry, the user name and the token's last four characters, but never the token itself. A public image needs no sign-in. See registry sign-in.
4. Check image
Click "Check image". The platform executor reads the image's manifest from the registry, without pulling it, and shows:
- Digest: the
sha256:…the reference points to now. - Architectures: the platforms the image is built for (from a multi-platform index, or a single image's config).
- The image's own config: its start command (
ENTRYPOINTthenCMD), working folder, user, ports, variables, and the source repository its labels name. For a multi-platform image this is one platform's config,linux/amd64when it has it. Variables show by name only. When a variable's name or value looks like a key (…_KEY,…_TOKEN,…PASSWORD, an address with a user and password…), the executor keeps only its name. The value never leaves the executor, and the platform never stores it.
If the image's USER is root or isn't set (which means root), this step says so. A sandbox always runs the container as uid 1500, not root, so an agent that writes to root's folders in the image, such as its working folder, fails there. Let the agent write to $HOME or /tmp instead, or build the image with that folder owned by 1500 (chown -R 1500 /app) or with USER 1500.
If it can't read the image, it says why: the registry wants a sign-in or refused yours, the image or tag doesn't exist, the registry can't be reached, or the manifest format is too old (push it again with a current docker or buildx). The executor never contacts private network addresses or cloud metadata addresses on a registry's behalf.
Every run is pinned to a digest. When a run starts, the platform uses the digest it read. If it read it more than 10 minutes ago, it reads the registry again first, so when a tag moves, the next run tests the new image. The report's version is the digest.
5. Class and architecture
The class sets the size of the sandbox. The architecture follows the image: "Auto" runs on x86 when the image has it, else on ARM, and only the architectures the image is built for can be chosen. See classes and architecture.
Run location. In the sandbox settings, a project's run location is Auto (the default) or London; both run the sandbox in London. Sweden (EU) is no longer offered since 24 October 2026 (see run location). For where your data is kept, see data processing.
6. Add project secrets
In the "Secrets" section, add every value your review.yaml names by environment variable. They reach the container as environment variables and are masked in the logs. See project secrets.
Your agent's model key is optional. Without OPENAI_API_KEY, a platform model answers the agent's model calls, billed by usage, and the section says which models answer. The container gets OPENAI_BASE_URL and a placeholder OPENAI_API_KEY, and reaches the platform's model through the sandbox, since the container runs on the sandbox's host network. See your agent's model.
7. The test connection file
An image carries no .reviewctl/ folder, so its review.yaml lives in the platform overlay. The wizard opens on a starter file. Once the image's config is read, agent.command is the image's own start command with the task as its last argument (a sh -c command gets it as "$1"). If the image's start command starts a service, such as a web framework or an exposed port, it takes no task on its command line; the starter then keeps the example command and says why. Check or change the command and click "Save to overlay", or click "Let AI finish and check" (below):
schema_version: 1
project: { id: prj_01J8ZK3Q9MXYZ0000000000000, name: expense-agent, domain: expense }
services: # optional: the test backend, started next to the container
db:
image: postgres:16
env: { POSTGRES_PASSWORD: test }
ports: [5432]
ready: pg_isready -U postgres
agent:
driver: command
command: [python, -m, my_agent.review_entry, "{{task}}"] # runs inside the image
env_passthrough: [OPENAI_API_KEY] # values come from project secrets
attach: { mode: endpoint_only } # or http_proxy / mcp_wrap, as for a repository
backend: { mode: own }
judge: { model: "openai:gpt-4.1-mini" }
scenarios: { dir: scenarios }
budget: { per_attempt: { minutes: 10, model_calls: 40 } }- No
environmentsection is needed: the image is the environment. The command runs in the image's ownWORKDIR. - The overlay's files are mounted read-only in the container at
/reviewctl. Add helper files such as an adapter script (for example.reviewctl/harness.py) or test data under "Files in the overlay" in this step: click "Add a file", then paste the content or load it from a local file. In the container that one is/reviewctl/harness.py. Each file is text only, at most 256 KB. - The agent reaches your
serviceson127.0.0.1. Every section and field is in the review.yaml reference.
Let AI finish and check
Rather not write review.yaml yourself? Click "Let AI finish and check" in this step. As for a repository, that one click is all you do. The platform does the rest and shows each step:
- Read the image: the image's config as "Check image" read it (start command, working folder, user, variable names, ports and labels). If its
org.opencontainers.image.sourcelabel names a GitHub repository that this organization's GitHub App can read, the platform reads that repository too, read-only and the same way as for a repository project (never.envor files that hold keys). It reads the commit thatorg.opencontainers.image.revisionnames, or else the head of the default branch. If it can't read the repository, it works from the image's config alone and says so. - Write the connection files: the platform's model writes
review.yaml, plus an adapter script when one is needed. Because the command runs in the image's container, the file usesdriver: commandonly and refers to the overlay's files as/reviewctl/…(.reviewctl/harness.pyis written/reviewctl/harness.py). It has noenvironmentsection, since the image is the environment; if the model writes one, the platform takes it out. The test backend is written as named services, not a Compose file. For an image that starts a service, the adapter calls the function that writes the reply in the same process. If the image needs root, it says so. - Write a test (if needed), check once, up to 10 times, result: as for a repository.
It never changes your image or sets secrets, and it can't loosen the settings a person decides. Text in the image, such as labels and variables, is read as data only, as in a repository. It costs the same way too: its model calls count against the assistant's daily allowance, and each check is billed by sandbox time.
8. Write tests, check once, then test
Write tests under Datasets → "All tests (detailed list)" (scenarios and test sets) and confirm at least one, as for a repository. Then go back to the wizard under Targets and click "Check once". It runs one confirmed test, so the button stays off until there is one. The platform starts a sandbox, pulls the image, starts the services and runs that test once, to see that the test really reaches your agent. You can watch each phase and the logs live.
An exec format error means the image doesn't match the virtual machine's architecture. When the image has both architectures, the platform checks once more on the other one by itself, and if that passes it switches the project's architecture (you can switch back). When the setting names an architecture the image lacks, "Check once" runs on the one it has.
Then click "Test new version" to start a test; the round becomes an experiment. It pre-runs first by itself (at 1× with the normal condition only, the run itself is the pre-run), so the check is optional.
Your CI can start a run for a freshly pushed image with a project API token (the project's Automations → "API tokens"): POST /v1/projects/:id/jobs with {"image_ref": "ghcr.io/acme/agent:pr-7"}. That image is read and pinned by digest for this run only. See API and tokens.
How your agent runs
- The sandbox signs in to your registry with your credential, pulls the image by digest, and signs out at the end.
- Each attempt starts a fresh container that is removed when the attempt ends, so no process outlives it.
- The agent runs as the same unprivileged user as on a repository run (uid 1500), with all Linux capabilities dropped, no new privileges, a limit of 512 processes and a memory limit from the class. The Docker socket is never mounted. Make sure your image works as a non-root user.
- The container uses the virtual machine's network, with the same per-user firewall rules. It can't reach the metadata server or the control plane. It can reach only your services on
127.0.0.1and the internet on port 443.
Allow-lists
The sandbox pulls your image and reaches the internet on port 443 through a fixed outbound IP address in London. If your registry or test backend admits only known addresses, email admin@nice2.io for it.
What enters our cloud
| What comes in | What stays after the run |
|---|---|
| The image (pulled into the sandbox), the test files, project secrets (sealed) | The run bundle. The image is deleted with the virtual machine. |
| The image's config (read by "Check image": start command, working folder, user, ports, variable names, labels; a value that looks like a key is never kept) | Kept on the project, refreshed by the next "Check image", deleted when the project stops using the image. |
See the data boundary table for every connection method.