What the agent does
The agent clones the repository into its own sandbox and reads how the app builds and what it needs at build time and at run time, which for Next.js are not the same list. In an app like nextjs/saas-starter, package.json has next at 15.6.0-canary.59, React 19.1.0, drizzle-orm with the postgres driver, and stripe; the lockfile is pnpm-lock.yaml with no packageManager field, so the image enables pnpm through Corepack rather than assuming npm. next.config.ts turns on experimental.ppr and clientSegmentCache and sets no output, and .env.example lists POSTGRES_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, BASE_URL and AUTH_SECRET. There is no Dockerfile.
Two things in the code decide how the build stage is written. lib/db/drizzle.ts throws POSTGRES_URL environment variable is not set at import time, and app/(dashboard)/pricing/page.tsx sets export const revalidate = 3600 and calls getStripePrices() and getStripeProducts(), so next build prerenders the pricing page and talks to Stripe while it does. The agent runs the build once in the sandbox to see exactly which of those fail without values, then picks a fix and writes it down in the PR: pass the variables as build arguments for a build that prerenders, or make the page render on request so the image builds with no secrets in it. It does not quietly bake a key into a layer.
Then it writes the Dockerfile: a dependencies stage that runs pnpm install --frozen-lockfile, a build stage that runs pnpm build with output: 'standalone' added to next.config.ts, and a runtime stage on a slim Node image that copies only .next/standalone, .next/static and public, runs as a non-root user and starts node server.js on port 3000. Migrations are not part of the server, so the image also carries drizzle.config.ts and lib/db/migrations/ (one migration today, 0000_soft_the_anarchist.sql) and drizzle-kit, which this app lists under dependencies, so a one-off migrate service can run pnpm db:migrate against the database before the app starts.
compose.yaml gets a postgres service with a health check and a named volume, the migrate service that waits for it, and the app service that waits for the migration, with POSTGRES_URL pointing at the postgres host and every other value read from the environment. In the sandbox the agent runs docker compose up --build, watches the migration apply, loads / and /sign-in in the sandbox's browser, and checks that /dashboard redirects to /sign-in without a session, as middleware.ts does. It records the image size from docker image ls and takes a screenshot of each page.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Containerize this Next.js app. Read package.json, the lockfile, next.config, .env.example, the database client and migration config, and list which environment variables the build needs and which only the running server needs. Run the build once to confirm. Write a production multi-stage Dockerfile using output standalone, the project's own package manager from its lockfile, a slim Node runtime stage with a non-root user, and no secret baked into any layer; if a page needs a secret at build time, choose between a build argument and rendering it on request, and explain the choice. Add a .dockerignore and a compose.yaml with Postgres (health check and volume), a one-off service that runs the database migrations, and the app, all configured from environment variables. Build and start it in the sandbox, run the migrations, load the home page and the sign-in page in the browser, and fix anything until they render. Open a pull request with the image size, the screenshots 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 a new Dockerfile, compose.yaml and .dockerignore, the one-line output: 'standalone' change in next.config.ts, and any change the build-time variables needed, such as a page moved to render on request. The description lists each environment variable with whether the build or the server reads it, the final image size from docker image ls, and screenshots of / and /sign-in served by the container.
The proof is in the session's transcript, which keeps every command with its output: the first pnpm build that showed what was missing, each docker compose up --build, the migration log, and the browser loads, including any that failed before a fix. If your repository runs checks on pull requests they run here as well; saas-starter has no workflows, so for an app like it the transcript is the record that the stack booted.
Before merging, a reviewer checks that no .env or Stripe key is copied into the image, that the standalone server starts with only what the runtime stage copies, that migrations run once and not on every app start, and that the Node version in the runtime image matches what production uses.
Run it on every new Next.js service
When a team starts a new service from the same template, one POST /o/{org}/sessions per repository, with that repository in resources and this prompt as input, gives each one its container setup with the same structure. An Idempotency-Key per repository makes a retried request return the run it already started.
A webhook on the API key posts run.completed or run.failed to your endpoint when the turn finishes, with the session_url to read the result and the pull request, so a script that creates repositories can also wait for their Docker setup without polling.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
A Next.js Dockerfile is easy to write and easy to get wrong in ways that only show up when it runs: a build that needs a database URL it does not have, a standalone server missing its static files, a migration that never ran. Finding those means building, booting Postgres, migrating and loading pages several times. In a Rush sandbox the agent has its own Docker, its own Postgres container and a browser, so none of that runs against your laptop's port 5432 or fills its build cache.
The result is a pull request with the evidence beside it: build logs, the migration output, the image size and screenshots of pages the container served, all kept in the transcript with the commands that produced them. When a reviewer wants a smaller base image or a different Node version, a follow-up message to the same session wakes the parked sandbox where it stopped and rebuilds.
Frequently asked questions
Does the build need my Stripe and database secrets?
It depends on which pages prerender. In saas-starter the pricing page calls Stripe during next build, and the database client throws on import without POSTGRES_URL. The agent runs the build to find out, then either passes values as build arguments or makes those pages render on request, and says which in the pull request.
How do I give the agent a Stripe test key?
Save it as a Project secret with POST /o/{org}/secrets and start the session in that Project with POST /o/{org}/p/{project}/sessions; the agent reads it as an environment variable. Without one, the agent boots the app with placeholder values and reports which pages need a real key to render.
Will it use npm if my project uses pnpm?
No. It reads the lockfile and uses the package manager that wrote it, so a pnpm-lock.yaml means pnpm install --frozen-lockfile in the image. With a packageManager field in package.json, it uses that exact version.
Where do migrations run?
In a separate one-off service in compose.yaml that runs before the app starts, using the project's own migration command (pnpm db:migrate with drizzle-kit here). Running them in the app's start command would race when you scale to more than one container.