What the agent does
The agent starts by reading how the app is built and what it talks to at runtime, because a Nuxt image is small only if the runtime stage ships the Nitro output and nothing else. In an app like nuxt/movies, package.json pins the toolchain through devEngines: Node ^24.0.0 and pnpm 11.1.2, both with onFail: download. The build script is nuxt build, start is node .output/server/index.mjs, and pnpm-workspace.yaml adds a second package, proxy, with shamefullyHoist: true and allowBuilds set to false for sharp, esbuild and @parcel/watcher. nuxt.config.ts loads @vueuse/nuxt, @unocss/nuxt, @nuxt/image, @nuxtjs/i18n with fourteen locale files under internationalization/, and @nuxtjs/html-validator. There is no Dockerfile, no compose file and no .dockerignore; .gitignore already excludes node_modules, .output, .nuxt, .nitro and .env*.
Then it works out the one runtime dependency that matters. The app does not call The Movie Database directly: nuxt.config.ts and app/composables/tmdb.ts both hardcode apiBaseUrl to a hosted proxy, with a commented http://localhost:3001 beside it, and the image provider is ipx through that same proxy. The proxy/ package is a Nitro app that reads TMDB_API_KEY from its runtime config, serves /tmdb/** through a cached handler and /ipx/** for images, and runs on port 3001 in development. So the container works with no secret at all against the public proxy, and the agent writes the Dockerfile that way by default, with the self-hosted proxy as a second service you opt into. It also notes in the pull request that the composable reads its own constant rather than runtimeConfig.public.apiBaseUrl, so pointing the app at your proxy is a one-line change in that file, not an environment variable.
The Dockerfile has a build stage on the Node 24 image that installs pnpm 11.1.2 with corepack, copies package.json, pnpm-lock.yaml, pnpm-workspace.yaml and the proxy's package.json first so pnpm install --frozen-lockfile caches as its own layer, then copies the rest and runs pnpm build. The runtime stage is the same Node image with only .output/ copied in, a non-root user, PORT=3000, HOST=0.0.0.0, EXPOSE 3000 and node .output/server/index.mjs as the command, which is exactly the repository's start script. A second stage builds the proxy with nitropack build for the compose file. A .dockerignore mirrors .gitignore and adds playwright-report/ and test-results/.
Then it proves the image runs. In the sandbox the agent runs docker compose up --build, waits on port 3000, requests / and /search and checks the HTML renders server-side rather than returning an error page. It runs the repository's own checks against the result: pnpm run test:unit as .github/workflows/tests-unit.yml does, and the Playwright spec in tests/e2e/ against the container by setting BASE_URL, which playwright.config.ts reads to point the Nuxt test utilities at a running host. It records docker image ls for the image size and fixes and rebuilds until the requests and the tests pass.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Containerize this Nuxt app. Read package.json, pnpm-workspace.yaml, pnpm-lock.yaml, nuxt.config.ts, app/composables/tmdb.ts, proxy/package.json, proxy/nitro.config.ts, proxy/.env.example, .env.example, .gitignore, playwright.config.ts and .github/workflows/tests-unit.yml first, and list the Node and pnpm versions, the build and start commands, the workspace packages, the port and every external service the app calls at runtime. Write a multi-stage Dockerfile: a build stage that caches pnpm install in its own layer and runs nuxt build, and a runtime stage with only the .output directory, a non-root user, HOST and PORT set and EXPOSE for the port. Add a .dockerignore and a compose.yaml with the app service and an optional proxy service built from the proxy package that takes TMDB_API_KEY from the environment. Build it in the sandbox, start it, request the home page and the search page, run the unit tests and the Playwright spec against the container with BASE_URL, fix anything until they pass, 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 and a README section with the build and run commands. The description lists each choice and the file it came from: Node 24 and pnpm 11.1.2 from devEngines, the .output/ runtime from the start script, port 3000 from .env.example, the public proxy default from nuxt.config.ts, and the proxy service built from the proxy/ workspace package with TMDB_API_KEY from its .env.example.
The transcript on the session keeps every command with its output: each docker compose build, the container logs from Nitro starting, the requests to / and /search with their status codes, the unit test run and the Playwright run against the container, and any build that failed before the fix. The repository's own Tests unit workflow runs on the pull request too, so vitest, eslint and nuxt typecheck report on a change that touches no Vue file.
Before merging, a reviewer checks that the runtime image holds no node_modules from the build stage beyond what Nitro bundled into .output/, that the process runs as the non-root user, that no .env file is copied into the image, and that the proxy service is the only place TMDB_API_KEY appears. If the team wants the app to use the self-hosted proxy by default, one follow-up message to the same session changes the constant in app/composables/tmdb.ts and nuxt.config.ts and rebuilds.
Run it on every new Nuxt project without a Dockerfile
A team containerizing several Nuxt apps 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 proceed at once without sharing a pnpm store or port 3000. 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 time in this job goes to the loop: install, build, start, discover that the server bound to localhost inside the container, or that a native dependency pnpm was told not to build is missing at runtime, change one line, build again. On a laptop that loop competes with the dev server already on port 3000 and leaves images behind. In a Rush sandbox the agent has its own daemon and its own ports, and the sandbox parks when the turn ends.
The pull request carries its own proof. The requests against the container, the Playwright run with BASE_URL pointed at it and the image size sit in the transcript beside the commands that produced them, so a reviewer can see the app rendered from a clean image rather than from a machine that already had Node, pnpm and a warm .nuxt cache.
Frequently asked questions
Does the container need a TMDB API key?
Not by default. The app reads movie data through the proxy URL hardcoded in nuxt.config.ts and app/composables/tmdb.ts, which is a public hosted instance of the proxy/ package. The key is only needed by the proxy service in the compose file, which you start when you want the app to go through your own proxy.
Why is the runtime stage not an Alpine image with pnpm installed?
Because nuxt build produces a self-contained Nitro server in .output/ that runs with plain node, so the runtime stage needs no package manager and no node_modules directory of its own. Copying only .output/ is what keeps the image small and is what the repository's own start script already assumes.
Does it respect the pnpm version the repository pins?
Yes. package.json pins pnpm 11.1.2 through devEngines, and the build stage activates exactly that version with corepack before running pnpm install --frozen-lockfile, so the lockfile resolves the same way it does in the Tests unit workflow.
Can it run the end-to-end tests against the container?
Yes. playwright.config.ts passes BASE_URL to the Nuxt test utilities as the host to test against when the variable is set, so the agent runs the spec in tests/e2e/ with BASE_URL pointed at the container instead of a dev server. The run and its result are in the transcript.