# Dockerize a Rust API

Yes. An agent writes a multi-stage Dockerfile and compose file for your Rust API, solves the SQLx offline build, boots the stack in a sandbox and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/dockerize-app/rust/), in a Project that has your repository.

## 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`

```json
{
  "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"
    }
  ]
}
```

1. **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.
2. **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.
3. **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.

## 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.
