Container images that build cleanly can still fail at runtime.
The server doesn't start, the port never binds, or a migration errors against the image's real filesystem. Vercel Sandbox lets you catch these failures before deploying by running your checks inside an isolated Firecracker microVM, so nothing touches your host machine or production systems.
There are two ways to test an image in a sandbox. You can boot it as a custom Sandbox image from Vercel Container Registry (VCR), or install Docker inside a sandbox and run the image exactly as Docker would. This guide helps you pick the right path, run pass/fail checks against your image, and wire the validation into CI or an agent workflow.
Copy link to headingOverview
In this guide, you'll learn how to:
- Choose between booting an image as a custom Sandbox image and running Docker inside a sandbox
- Run install, start, and HTTP smoke checks against the image
- Verify the container's network behavior with sandbox network policies
- Automate the validation in CI or an agent workflow
- Read the results: what a passing sandbox run does and doesn't guarantee
Copy link to headingPrerequisites
Before you begin, make sure you have:
- A Vercel account and the Vercel CLI installed
- A linked project with an OIDC token available locally. Run
vercel link, thenvercel env pullto downloadVERCEL_OIDC_TOKEN. In external CI systems, use an access token instead. - The
@vercel/sandboxSDK installed:npm i @vercel/sandbox - A container image for testing.
Copy link to headingChoose your validation path
Vercel Sandbox works with container images in two ways, and each validates something different:
| Boot the image as the sandbox | Run Docker inside a sandbox | |
|---|---|---|
| Mechanism | Sandbox.create() with a custom image pulled from VCR | Install Docker in a default sandbox, start the daemon with sudo, then docker run the image |
| What it validates | The image's filesystem, toolchain, and dependencies, plus any command you run with runCommand() | The image exactly as Docker executes it, including ENTRYPOINT and CMD |
| Key caveat | Sandbox does not run Docker ENTRYPOINT or CMD; you start processes yourself | Containers don't inherit the sandbox's proxy CA certificate; daemon startup needs sudo |
| Prerequisites | Image pushed to VCR with a linux/amd64 manifest | Any image source reachable from the sandbox |
| Speed | Fast: boots from a precompiled snapshot | Slower first run (Docker install plus image pull); persistent sandboxes carry both over between sessions |
| Best for | Repeated smoke tests against a known image, and agent toolchains | End-to-end validation, testing the start command itself, and images not yet in VCR |
The decision rule: if you need to validate the container's own start behavior (ENTRYPOINT, CMD, and signal handling), use Path B. If you need a fast, repeatable environment built from the image's contents, use Path A.
Copy link to headingBoot the image as a custom Sandbox image (Path A)
Copy link to heading1. Push the image to VCR
Sandbox pulls custom images from VCR, a project-scoped registry hosted at vcr.vercel.com. VCR only serves an image to Sandbox once it has prepared an optimized linux/amd64 build, so build for that platform and use zstd compression:
To authenticate locally before pushing, run vercel vcr login, which mints a short-lived, project-scoped OIDC credential valid for 12 hours. After the push, check the repository details page in your project dashboard for the readiness state.
For repository setup and other authentication options, see the Vercel Container Registry documentation.
Copy link to heading2. Create the sandbox from the image
Pass the image reference to Sandbox.create(), along with the port:
The image parameter accepts a repository name, tag, digest, or fully qualified VCR URL. The env parameter sets default environment variables for every command run in the sandbox, which is how you supply configuration the image expects.
Vercel Sandbox does not run Docker ENTRYPOINT or CMD for custom images. The sandbox boots with your image's filesystem in place, but you start processes yourself with sandbox.runCommand(). If the Dockerfile defines WORKDIR, commands start in that directory.
Copy link to heading3. Run your checks with runCommand()
Verify the toolchain and dependencies the image claims to ship, then start the server explicitly. Use detached: true for the server process:
Any nonzero exitCode on a blocking command is a failed check. For the detached server, stream server.logs() if you need to watch for a readiness line.
Copy link to heading4. Smoke-test over the exposed port
sandbox.domain(port) returns a public URL for any port you declared in ports. Send a request and assert on the response:
A 200 response confirms the image's dependencies and your start command produce a working server. If the request hangs, the server likely never started.
Copy link to headingRun the image with Docker inside a sandbox (Path B)
Copy link to heading1. Create a sandbox and install Docker
Each sandbox is a Firecracker microVM with its own kernel, running an Ubuntu-based managed image by default (legacy runtimes run Amazon Linux 2023) with sudo access, which is what lets you start the Docker daemon inside it.
Install Docker, start the daemon detached, and wait for it to report ready:
Sandboxes are persistent by default, so the Docker installation and any pulled images carry over between sessions through filesystem snapshots. Running processes don't survive a stop, though, so restart dockerd on each resume.
The onResume hook in Sandbox.getOrCreate() handles this for you (by default it fires when the session actually resumes on your first SDK call; pass resume: true to resume immediately).
Copy link to heading2. Authenticate and pull the image
Pull the image under test from VCR or another registry. For VCR, pass your OIDC token into the sandbox and log in with the oidc username:
VCR implements the standard Docker Registry HTTP API v2, so docker login works from inside a sandbox the same way it does locally, with no environment-specific setup.
The OIDC token expires after 12 hours, so run vercel env pull again if you see authentication errors. In external CI systems where VERCEL_OIDC_TOKEN is unavailable, use a Vercel access token instead, with the team ID that owns the project as the Docker username. Never write either token into scripts or logs; pass it through env as shown.
Copy link to heading3. Run the container as Docker would
This is the step Path A can't cover: Docker executes the image's real ENTRYPOINT and CMD. Map the container's port to the sandbox port you exposed:
Copy link to heading4. Exercise it
Check the container is running, inspect its logs, and hit it over the exposed port:
An empty result from docker ps means the container exited; run sudo docker logs under-test to see why, and check the exit code with sudo docker inspect under-test --format "{{.State.ExitCode}}".
If you suspect memory pressure, also inspect the sandbox’s Memory Usage chart against its configured limit and 85% warning line. This measures the sandbox, not an individual container.
Copy link to heading5. Optional: validate with backing services
Docker in a sandbox is also useful for running containerized services like Redis or Postgres as test dependencies alongside the image under test:
This lets migrations, seeds, and integration checks run against real services without provisioning anything outside the microVM.
Copy link to headingLock down the network (optional, both paths)
Sandbox egress traffic passes through a firewall you control. The networkPolicy parameter defaults to allow-all, but you can pass deny-all or an allow list of domains to confirm the image makes only the outbound calls you expect:
Domain entries without a wildcard are exact matches only. To allow all subdomains, use a leading wildcard such as *.example.com.
Domain allow lists match traffic by SNI, so they apply to TLS traffic only. If the image makes plain-HTTP outbound calls, those are blocked under a domain allow list even when the domain is listed; use subnets.allow to permit non-TLS destinations.
You can also change the policy at runtime with sandbox.update({ networkPolicy }), for example to allow the image pull, then switch to deny-all before starting the container. If your smoke test passes under a restrictive policy, the code paths you exercised made no unexpected outbound calls. Paths the test didn't reach, such as deferred jobs or retries, can still have outbound dependencies.
One behavior to watch in Path B: a container has its own trust store, so it can't see the sandbox's proxy CA certificate, and HTTPS requests from inside the container fail TLS verification when the firewall terminates them.
Copy link to headingAutomate it
Structure the validation as a single script that exits nonzero on failure, so CI can gate a deploy on it.
This example follows the create, check, assert, and stop pattern with Path A:
Run it in CI with VERCEL_OIDC_TOKEN (or a Vercel access token) and IMAGE_REF set. For one-off manual runs, the Sandbox CLI offers sandbox create with --publish-port and --network-policy flags. The same script works as a tool for AI agents that build images and need to verify them before promotion.
Copy link to headingWhat a passing run does and doesn't validate
Treat a green sandbox run as one ring of validation, not production parity:
| A passing run confirms | A passing run does not confirm |
|---|---|
| The image's filesystem, toolchain, and dependencies work | Fluid compute lifecycle behavior: $PORT resolution and the 30-second SIGTERM grace period on scale-down |
The start command boots a working server (Path B tests the real ENTRYPOINT and CMD) | Production request routing and CDN behavior |
| The server answers HTTP requests with expected status and body | Performance under Active CPU pricing and concurrent load |
| The image's outbound network profile matches your policy | Behavior of environment variables and integrations only present in production |
For the next ring of validation, deploy the image to a preview deployment and test it on the real Functions runtime before promoting to production.
Copy link to headingTroubleshooting
Copy link to headingSandbox.create() returns image_not_ready
VCR prepares an optimized linux/amd64 snapshot after each push, and Sandbox.create() fails with image_not_ready until preparation finishes. Check the readiness state on the repository details page, then retry. Confirm the image was built with --platform linux/amd64.
Copy link to headingThe server never responds in Path A
Custom Sandbox images skip Docker ENTRYPOINT and CMD, so the server only runs if you started it with runCommand(). Start it explicitly with detached: true, and confirm it listens on a port you declared in the ports array.
Copy link to headingTLS failures inside containers in Path B
The sandbox trusts its proxy CA certificate at /etc/pki/ca-trust/source/anchors/vercel-proxy-ca.pem, but containers can't see it. Mount the certificate into the container and add it to the container's trust store:
Use update-ca-certificates on Debian or Ubuntu base images and update-ca-trust on Amazon Linux, Fedora, or RHEL. If the app reads a CA bundle from an environment variable, point that variable (such as NODE_EXTRA_CA_CERTS) at the mounted path instead.
Copy link to headingThe sandbox times out mid-validation
Sandboxes run for 5 minutes by default. Pass a longer timeout at creation, or call sandbox.extendTimeout() while running. The ceiling is 45 minutes on Hobby plans and 24 hours on Pro and Enterprise plans.
Copy link to headingBest practices
- Pin by digest, not tag: Validate
my-repository@sha256:...so the artifact you tested is byte-for-byte the one you deploy. Tags can move between validation and deploy. - Keep runs cheap: Data a sandbox downloads (packages, images, and Git repositories) is free, but traffic on exposed ports and data sent out is billable per GB, so keep smoke payloads small. Sandbox creation itself costs $0.60 per million creations.
- Reuse persistent sandboxes for Path B: The Docker installation and pulled images survive between sessions, which skips 30 or more seconds of setup on every run. Use
Sandbox.getOrCreate()with anonResumehook that restartsdockerd. - Fail loudly: Exit nonzero on any failed check so CI blocks the deploy, and print the container logs on failure so the run is debuggable from CI output alone.
Copy link to headingRelated resources and next steps
- Read the Vercel Sandbox documentation for the isolation model and authentication setup
- Explore the JS SDK reference for
runCommand(), ports, snapshots, and network policy APIs - Learn how custom images work in Sandbox images
- Set up repositories and authentication in Vercel Container Registry
- See the broader picture in Running Docker on Vercel
- Review Sandbox pricing and limits before scheduling validation runs in CI