# Dockerize a Gin API

Yes. An agent writes a multi-stage Dockerfile and compose file for your Gin API, builds it with the SQLite driver's cgo, boots it in a sandbox and opens a PR.

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

## What the agent does

The agent starts by reading what the binary needs, because a Go image that ignores cgo is the usual way this job goes wrong. In a Gin API like `gothinkster/golang-gin-realworld-example-app`, `go.mod` says `go 1.21` with `gin-gonic/gin v1.10.0`, `gorm.io/gorm v1.25.12` and `gorm.io/driver/sqlite v1.5.7`, and the indirect list carries `mattn/go-sqlite3`, which compiles C. So the build stage cannot set `CGO_ENABLED=0`, and the runtime image cannot be `scratch`: it needs the C library the driver links against. `hello.go` is the entrypoint; it opens the database through `common.Init()`, runs GORM `AutoMigrate` for the user, article, tag, favorite and comment models on every start, and listens on `PORT`, defaulting to 8080. `common/database.go` reads `DB_PATH`, defaulting to `./data/gorm.db`, and creates that directory if it is missing. There is no `Dockerfile`, no compose file and no `.dockerignore` in the repository; `.env.example` lists `PORT`, `GIN_MODE` and `DB_PATH`.

From that it writes a two-stage `Dockerfile`. The build stage uses the Go toolchain image for the version the CI matrix in `.github/workflows/ci.yml` tests last, installs `gcc` and the musl headers so `go-sqlite3` compiles, copies `go.mod` and `go.sum` first so `go mod download` caches as its own layer, then builds `hello.go` with `CGO_ENABLED=1`. The runtime stage is a small Alpine image with `sqlite-libs` and CA certificates, a non-root user, a `/data` directory that user owns, `DB_PATH=/data/gorm.db`, `GIN_MODE=release`, `EXPOSE 8080` and a `HEALTHCHECK` that requests `/api/ping`, the route `hello.go` registers for exactly this purpose. A `.dockerignore` keeps `tmp/`, `*.db`, `.env` and the `coverage.out` file CI writes out of the build context, matching what `.gitignore` already excludes.

The compose file is short because the app has no separate database: one `api` service built from the Dockerfile, a named volume mounted at `/data` so the SQLite file survives a restart, `PORT` and `GIN_MODE` passed from the environment, and the health check wired to `depends_on` for anything you add later. Since migrations run inside `main()`, there is no separate migrate step to script, and the agent says so in the pull request rather than inventing one.

Then it proves the image works. In its sandbox it runs `docker compose build`, starts the service, and waits on `/api/ping` the way `scripts/run-api-tests.sh` does. That script also shows how the repository tests its own API: it downloads the Conduit Postman collection and runs it with Newman against port 8080. The agent runs the same collection against the container, so a build that compiled but cannot open its database or write to `/data` fails here, not on your machine. It records `docker image ls` for the image size and fixes and rebuilds until the collection passes.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Containerize this Gin API. Read go.mod, go.sum, hello.go, common/database.go, .env.example, .gitignore, scripts/run-api-tests.sh and .github/workflows/ci.yml first, and list the Go version, the cgo dependencies, the port, the database path and the directories the process writes. Write a multi-stage Dockerfile: a build stage that caches go mod download in its own layer and builds hello.go with cgo enabled for the SQLite driver, and a small runtime stage with the C library the driver needs, a non-root user, a writable data directory for DB_PATH, GIN_MODE=release, EXPOSE for the port and a HEALTHCHECK on /api/ping. Add a .dockerignore and a compose.yaml with the api service, a named volume for the database file and configuration from environment variables. Build it in the sandbox, start it, wait for /api/ping, then run the Conduit Postman collection with Newman against the container the way scripts/run-api-tests.sh 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` and a README section with the build and run commands. The description lists each choice and the file it came from: the Go version from the CI matrix, cgo kept on because of `go-sqlite3`, the C library in the runtime image, the `/data` volume because `DB_PATH` defaults to a relative path the container would lose, and the health check on `/api/ping` because the route already exists.

The transcript on the session keeps every command with its output: each `docker compose build`, the container logs showing GORM's migrations, the wait loop on `/api/ping`, the Newman run against the container, and any build that failed before the fix. The repository's own `CI` workflow runs on the pull request too, so the `gofmt` check, the coverage run, `golangci-lint` and the API test job all report on a change that touches no Go file.

Before merging, a reviewer checks that the runtime image carries no Go toolchain or `gcc`, that the process runs as the non-root user and can still write the database file, that `.env` is not copied into the image, and that the health check path matches the route. If production uses Postgres rather than SQLite, one follow-up message to the same session swaps the driver and adds a database service, and the sandbox boots the stack again.

## Run it on every new Go service without a Dockerfile

A team containerizing several Go 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.

Each run has its own sandbox with its own Docker daemon, so several builds run at once without sharing a layer 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 the loop: build, start, watch it fail on a missing shared library or a read-only directory, change one line, build again. On a laptop that loop fills the local Docker cache and competes with whatever is already on port 8080. In a Rush sandbox the agent has its own daemon and its own ports, and the sandbox parks when the turn ends, so nothing is left running on your machine.

The pull request carries its own proof. The Newman 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 shared Conduit collection from a clean image rather than from a machine that already had Go, gcc and SQLite installed.

## Frequently asked questions

### Why does the image need gcc and a C library for a Go program?

Because `gorm.io/driver/sqlite` depends on `mattn/go-sqlite3`, which compiles the SQLite engine from C through cgo. The build stage needs a C compiler and the runtime stage needs the C library the binary links to. A `CGO_ENABLED=0` build or a `scratch` image fails at runtime with a driver error, which is why the agent reads `go.mod` before writing the Dockerfile.

### Could it switch to a pure-Go SQLite driver instead?

It could, and the pull request mentions the option, but it does not change the driver unless the input asks. Swapping `go-sqlite3` for a pure-Go driver changes the database layer of the app, which is a different review from adding a Dockerfile.

### Where does the database file live when the container restarts?

On a named volume mounted at `/data`, with `DB_PATH` pointing there. Without the volume, the file would sit in the container's writable layer and disappear with it. The non-root user owns the directory, so GORM can create the file on first start.

### Does it run the Postman collection against my API?

Yes, in the sandbox only, against the container it just built. The script `scripts/run-api-tests.sh` already downloads the Conduit collection and runs it with Newman; the agent reuses that collection as the proof the containerized API behaves like the one CI tests.
