⚠️ DEPRECATED / RETIRED (2026-06-05). The canonical end-to-end test suite now lives in thebuild-servicerepo attests/e2e/(a pytest suite with a shared client lib, fixtures, and coverage for cancel/delete/logs/ chained/concurrent/isolation). These standalone scripts are superseded — their unique scenarios (chained,concurrent_builds) were ported intobuild-service/tests/e2e/test_chained.pyandtest_concurrent.py. Use that suite; this repo is kept only for reference.
End-to-end test scripts for the RunPod container build service. Each script exercises a different feature; if it exits 0, that flow works.
| Script | What it does |
|---|---|
tests/basic_example.py |
Submit a build, stream logs, pull and run the image. |
tests/chained_example.py |
Build A; build B FROM A's image; verify B inherits A's layers. |
tests/failing_example.py |
Submit a deliberately-broken Dockerfile; verify the failure is surfaced. |
tests/multistage_example.py |
Build a multistage Dockerfile; verify only the final stage's content ships. |
tests/versions_example.py |
Build the same image twice; verify both versions are tagged and listed. |
tests/isolation_example.py |
Two accounts; verify neither can see, read, or pull the other's resources. |
Requires Python 3.10+ and Docker.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtYou need API keys for two separate accounts. Set both before running anything:
export RUNPOD_API_KEY=rpa_... # account #1 — used by every script
export RUNPOD_API_KEY_2=rpa_... # account #2 — used only by isolation_example.pyRUNPOD_API_KEY_2 MUST resolve to a different account than RUNPOD_API_KEY
— isolation_example.py exits with an error if the two keys are equal.
The other five scripts ignore RUNPOD_API_KEY_2.
| Variable | Default | Used by |
|---|---|---|
RUNPOD_API_KEY |
required | all six scripts |
RUNPOD_API_KEY_2 |
required for isolation | isolation_example.py only |
BUILD_SERVICE_API_URL |
https://v2-rest-brody.runpod.dev |
all six scripts |
REGISTRY_HOST |
registry-brody.runpod.dev |
all six scripts |
python3 tests/basic_example.py
python3 tests/chained_example.py
python3 tests/failing_example.py
python3 tests/multistage_example.py
python3 tests/versions_example.py
python3 tests/isolation_example.pyEach script prints *** SUCCESS … *** and exits 0 when its flow works.
tests/ runnable example scripts
testdata/ Dockerfiles the scripts submit to the build service
The testdata/Dockerfile.* files are sent verbatim. Edit freely — but
if you change a marker string a script greps for, update the matching
constant at the top of that script. testdata/Dockerfile.child uses a
__BASE_IMAGE__ placeholder that chained_example.py substitutes at
submission time.
Each script deletes the repository it created and removes the local
image on success. To inspect artifacts after a run, comment out the
[cleanup] block at the bottom of main().
- Each build runs in an ECS task (the
builder-agent) that spawns build processes using BuildKit under the hood. Eventually, this should probably move to a single orchestrator + fully isolated Firecracker microVMs (fast cold-boot) that get spun up per-build. - Build jobs are queued + scheduled. A separate scheduler service assigns build jobs to available
builder-agenttasks. - Currently, each
builder-agenttask can spawn at most 1 build subprocess at a time. We could do some horizontal scaling here (1 microVM builder per CPU core) so eachbuiler-agenttask actually manages it's own pool of builder slots. - Currently, the "builder fleet" is a single task — would obviously scale up, but it works as a "single-build-queue-consumer" PoC.
builder-agentwrites back to the API (for e.g. streaming log chunks, build status updates, etc.), and the API service maintains all build state in Postgres.- logs are flushed back to the API every 250ms. For e.g. 64 microVM builders per task, that's 256 QPS to the API's
POST /logsendpoint per task. With potentially thousands of tasks, this will likely have to be separately optimized (maybe redis? have to do some research on write path optimization) builder-agentuploads built artifacts to the custom authenticated registry deployed athttps://registry-brody.runpod.dev. I'm usingdistribution/distributionbacked by Cloudflare R2 storage for the registry, and I rolled my own auth (mostly just to learn how registry auth works).- Full OAuth 2.0 all the way through the stack. The system speaks short-lived JWTs from top to bottom. Real RunPod user identities and scopes.
- The scheduler service (not the builder-agent!) mints JWTs for the custom registry and passes them into the builder-agent when they are invoked. This includes a refresh token so the builder-agent can still authenticate if, for instance, a build takes too long and outlives the initial token TTL. The builder (which runs user-supplied code and may be subject to remote code execution vulnerabilities) can never mint credentials for other users.
- Multi-tenant + isolated by default. User A cannot view/modify containers built by User B.