What the agent does
The agent starts with the build, because a Rust service that uses SQLx has a compile step that breaks in Docker before any Dockerfile line is wrong. In an Axum API like launchbadge/realworld-axum-sqlx, Cargo.toml names axum 0.3.4, tokio 1.14 and sqlx 0.5 with the runtime-tokio-native-tls and postgres features, and the handlers under src/http/ call sqlx::query! and sqlx::query_as! in 24 places. Those macros check every query against a live database at compile time, so cargo build inside a Docker build stage fails with no DATABASE_URL reachable. The repository has no sqlx-data.json and no rust-toolchain file, .gitignore excludes only /target/ and .env, and main.rs runs sqlx::migrate!() from the migrations/ directory on every start, so the schema is embedded in the binary.
From that it fixes the build in the order SQLx expects. In the sandbox it starts Postgres, installs sqlx-cli with the postgres feature the way .github/workflows/rust.yml does, runs sqlx db setup to create the database and apply 1_setup.sql through 4_article.sql, then runs cargo sqlx prepare to write sqlx-data.json. That file is what lets the Docker build compile the queries with SQLX_OFFLINE=true and no database. The agent adds it to the pull request and a note that it must be regenerated when a query changes.
Then it writes the two-stage Dockerfile. The build stage uses the Rust toolchain image, installs pkg-config and the OpenSSL headers because the native-tls feature links against them, copies Cargo.toml and Cargo.lock first and builds a stub binary so dependency compilation caches as its own layer, then copies src/, migrations/ and sqlx-data.json and builds in release mode with SQLX_OFFLINE=true. The runtime stage is a small Debian image with libssl and CA certificates, a non-root user, EXPOSE 8080 because src/http/mod.rs binds 0.0.0.0:8080 with no port setting, and the binary as the entrypoint. A .dockerignore keeps target/ and .env out of the context, matching .gitignore.
The compose file has two services: api built from the Dockerfile with DATABASE_URL and HMAC_KEY passed from the environment, the two values src/config.rs requires through clap, and postgres with a named volume and a pg_isready health check that api waits on, the same check the CI workflow gives its service container. There is no migrate service because the binary migrates itself. To prove it, the agent runs docker compose up --build, waits for port 8080, then runs the RealWorld Postman collection against http://localhost:8080/api with the same run-api-tests.sh the CI job checks out, and fixes and rebuilds until that passes. The RUST_LOG value from .env.sample goes into the compose file so request logs from tower_http are visible.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Containerize this Rust API. Read Cargo.toml, Cargo.lock, src/main.rs, src/config.rs, src/http/mod.rs, migrations/, .env.sample, .gitignore and .github/workflows/rust.yml first, and list the crate versions, the SQLx features, every environment variable the binary reads, the port it binds and whether the queries use compile-time checked macros. If they do, start Postgres in the sandbox, run sqlx db setup and cargo sqlx prepare, and commit the offline query data so the Docker build can run with SQLX_OFFLINE=true. Write a multi-stage Dockerfile: a build stage that caches dependency compilation in its own layer and builds a release binary, and a small runtime stage with the TLS library the binary links, a non-root user and EXPOSE for the port. Add a .dockerignore and a compose.yaml with the api service, a postgres service with a volume and a pg_isready health check, and configuration from environment variables. Build it in the sandbox, start it, wait for the port, run the RealWorld Postman collection against the container the way the CI workflow does, fix anything until it passes, and open a pull request with the image size and the commands to run it locally.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request on its own branch with Dockerfile, compose.yaml, .dockerignore, sqlx-data.json and a README section with the build and run commands. The description lists each decision and the file it came from: offline query data because of the query! macros, OpenSSL in both stages because of runtime-tokio-native-tls, port 8080 from src/http/mod.rs, DATABASE_URL and HMAC_KEY from src/config.rs, and no migration step because main.rs runs sqlx::migrate!() itself.
The transcript on the session keeps every command with its output: the sqlx db setup run, the cargo sqlx prepare run, each docker compose build including any that failed before the fix, the container logs showing the migrations applying, and the Postman collection run against the container. The repository's own Rust workflow runs on the pull request too, so cargo build and cargo test against the service container report beside the new files.
Before merging, a reviewer checks that the runtime image carries no Rust toolchain, that the binary runs as the non-root user, that .env is not copied into the image, that sqlx-data.json matches the queries on the branch, and that HMAC_KEY is read from the environment rather than written into the compose file. If the team would rather drop OpenSSL from the image, one follow-up message to the same session switches the SQLx feature to runtime-tokio-rustls and rebuilds.
Run it on every new Rust service without a Dockerfile
A team containerizing several Rust services starts one run per repository: a POST /o/{org}/sessions with that repository in resources and this prompt. Send an Idempotency-Key per repository so a retried request returns the run it already started rather than a second one.
Each run has its own sandbox with its own Docker daemon and its own Postgres, so several builds proceed at once without sharing a cargo cache or a port. Up to the key's max_concurrent runs proceed in parallel, and a webhook on the key receives run.completed or run.failed for each turn with the session_url that holds the pull request.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
The slow part of this job is not writing the Dockerfile, it is the loop around it: a release build of a Rust service with SQLx and OpenSSL takes minutes, and the first attempts fail on a missing header, a query macro with no database, or a runtime image without the shared library the binary wants. On a laptop that loop fills the local Docker cache and blocks the machine. In a Rush sandbox the agent has its own daemon, its own Postgres and its own cargo cache, and the sandbox parks when the turn ends.
The pull request carries its own proof. The Postman collection run against the container, the health check output and the image size are in the transcript beside the commands that produced them, so a reviewer can see the API answered the RealWorld requests from a clean image rather than from a machine that already had Rust, Postgres and OpenSSL installed.
Frequently asked questions
Why does the Docker build need sqlx-data.json?
Because the handlers use sqlx::query! and sqlx::query_as!, which check each SQL statement against a database when the crate compiles. A Docker build stage has no database, so the agent runs cargo sqlx prepare once against a migrated database and commits the result; with SQLX_OFFLINE=true the macros read that file instead. When a query changes, cargo sqlx prepare must run again, and the pull request says so.
Does it change the database driver or the TLS library?
No. The build keeps runtime-tokio-native-tls as Cargo.toml declares it, which is why the build stage installs the OpenSSL headers and the runtime stage installs the shared library. Switching to rustls would remove that dependency, and the pull request mentions it as an option, but it changes the crate's features and is a separate review.
How do migrations run in the container?
They run inside the binary. main.rs calls sqlx::migrate!() before serving, which applies the files under migrations/ that were embedded at compile time. The compose file only has to make sure Postgres is healthy before api starts, which the pg_isready health check and depends_on handle.
Will the Rust version in the image match what the team uses?
The repository has no rust-toolchain file, so the agent uses the toolchain image for the current stable release and says so in the pull request. If a rust-toolchain.toml exists in your repository, the agent reads the channel from it and pins the image to that version instead.