# Dockerize a SvelteKit App

Yes. An agent switches your SvelteKit app to adapter-node, writes a multi-stage Dockerfile and compose file, boots the image in a sandbox and opens a pull request.

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

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

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

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

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