What the agent does
The agent clones the repository into its own cloud sandbox and reads how the app is built before it writes a line of the Dockerfile. In a SvelteKit app like sveltejs/realworld, package.json declares "type": "module", packageManager as pnpm@10.20.0, SvelteKit ^2.52.2 on Svelte ^5.55.7 and Vite ^7.3.2, and only five scripts: dev, build, preview, lint (a Prettier check) and format. .nvmrc says Node 20, .npmrc sets engine-strict=true, and pnpm-workspace.yaml lists esbuild under onlyBuiltDependencies, so the image has to let pnpm run that one postinstall or Vite will not start.
Then it reads svelte.config.js and finds the real obstacle: the app uses @sveltejs/adapter-vercel, which emits a Vercel function layout, not a server a container can run. The agent adds @sveltejs/adapter-node as a dev dependency with pnpm, switches the adapter in svelte.config.js, and checks what the build produces: a build/ directory with an index.js that listens on PORT and HOST. The app itself is stateless; src/lib/api.js sends every request to https://api.realworld.show/api and src/hooks.server.js reads the session from a jwt cookie, so there is no database to add to the compose file and no secret the container needs at build time.
The Dockerfile it writes has two stages. A build stage on a Node 20 image enables pnpm through Corepack at the version packageManager pins, copies package.json, pnpm-lock.yaml, .npmrc and pnpm-workspace.yaml first so the install layer caches, runs pnpm install --frozen-lockfile, copies the source and runs pnpm build. A runtime stage on a slim Node 20 image copies only build/, package.json and the production node_modules from pnpm install --prod --frozen-lockfile, runs as the node user, sets PORT=3000 and HOST=0.0.0.0, exposes the port and starts with node build. A .dockerignore keeps node_modules, .svelte-kit, build and .git out of the context.
Before it opens anything, the agent builds the image in the sandbox, starts it with docker compose up, and loads the home page in the sandbox browser. The home page renders the global feed from the public API, so a blank page or a 500 is caught here, not after merge. It records the image size that docker images prints and takes a screenshot for the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Containerize this SvelteKit app. First read package.json, the lockfile, .nvmrc, .npmrc, svelte.config.js and vite.config.js, and list the package manager and its version, the Node version, the adapter in use, the build output directory, and any environment variables or services the app needs at runtime. If the adapter is not adapter-node, add @sveltejs/adapter-node with the repository's package manager and switch svelte.config.js to it. Write a production multi-stage Dockerfile: a build stage that installs with the lockfile frozen and runs the build, and a slim runtime stage with only the build output and production dependencies, running as a non-root user with HOST and PORT set. Add a .dockerignore and a compose.yaml with the app service, its port, and a health check, plus any service the app depends on at runtime. Build the image in the sandbox, start it with docker compose, load the home page in the browser and fix anything until it renders. Open a pull request with the image size, the screenshot, and the commands to build and 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 and .dockerignore added, svelte.config.js changed from adapter-vercel to adapter-node, and package.json and pnpm-lock.yaml carrying the new dev dependency in the same commit. The description lists what the agent read and why each choice follows from it: Node 20 from .nvmrc, pnpm 10 from packageManager, the esbuild build script pnpm must be allowed to run, and the build/ output that node build starts.
The description also carries the image size the sandbox build printed, the screenshot of the rendered home page, and the three commands to run it locally: build, docker compose up, and the URL to open. A reviewer checks the adapter change does not break the existing Vercel deploy they care about, that the runtime stage copies no source or dev dependencies, and that the user the container runs as is not root.
The session read returns the repository, the pull request and the screenshot as an artifact, and the transcript holds every command with its output: the pnpm install, the build, the docker build layers, the compose start and the page load.
Run it on every new service without a Dockerfile
A platform team can start this from its own server for each repository that lacks a Dockerfile: one POST /o/{org}/sessions per repository, each with that repository in resources, from a script that walks GET /o/{org}/github for the installations the Rush GitHub App covers. Each run clones its own repository into its own sandbox, so ten repositories mean ten pull requests, not one branch with ten unrelated changes.
Set a webhook_url on the API key and every run the key starts POSTs run.completed when it finishes, with the session_url to read the pull request from. Answer with any 2xx within 5 seconds; retries come 2, 4 and 6 minutes later with the same webhook-id.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
A Dockerfile is only right if the image builds and the container serves the page, and that takes a Docker daemon, a clean Node install and a browser. The sandbox has all three, so the agent builds the image, starts it and loads the home page before the pull request exists, and the transcript keeps the build output and the screenshot as the proof a reviewer reads instead of rebuilding locally.
The adapter switch is the step people get wrong by hand, because adapter-vercel and adapter-node produce different output and the error only shows at container start. The run finds it from svelte.config.js, changes it, and proves the change with a running container, in a sandbox that never touches the developer's own checkout or their Vercel project.
Frequently asked questions
My app deploys to Vercel. Will switching the adapter break that?
The pull request changes svelte.config.js to adapter-node, and a Vercel deploy from the same branch would then build for Node rather than for Vercel functions. If you need both, ask in the prompt for the adapter to be chosen from an environment variable, or keep the Vercel branch separate. The description says which adapter the image uses so the reviewer decides.
Does it work with pnpm, npm, Yarn or Bun?
Yes. The agent reads the packageManager field and the lockfile that exists, and installs with that manager at that version, with the lockfile frozen. With pnpm it also honors pnpm-workspace.yaml, which in the RealWorld app allows the esbuild postinstall that Vite depends on.
What if my app needs a database or an API key at runtime?
The prompt asks for every runtime service and variable to be listed first. A database becomes a second service in compose.yaml with a volume and a health check; a secret becomes an environment variable the compose file reads from .env, never a value written into the image. The RealWorld app has neither, which is why its compose file has one service.
Which Node version does the image use?
The one the repository already declares. In sveltejs/realworld that is .nvmrc with 20, so both stages start from a Node 20 image. When a repository declares nothing, the agent picks the current LTS and says so in the pull request so the reviewer can change it.