# Dockerize a NestJS App

Yes. An agent reads your NestJS build setup, writes a multi-stage Dockerfile and compose file, starts the container in a sandbox and opens a pull request.

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

## What the agent does

The agent clones the repository into its own sandbox and reads how the app compiles before choosing a base image. In a NestJS app like nestjs/typescript-starter, `package.json` is `"type": "module"` with `@nestjs/core` `^12.0.1`, `@nestjs/platform-express` and `@nestjs/observe`, a `package-lock.json` at `lockfileVersion` 3 and no `engines` field; `@types/node` is `^24.0.0`, so the agent picks Node 24 and says why. The scripts are the ones `nest new` generates: `build` is `nest build`, `start:prod` is `node dist/main`, `lint` is `oxlint src/ test/`, and `test` and `test:e2e` run Vitest with `vitest.config.ts` and `vitest.config.e2e.ts`.

`nest-cli.json` sets `sourceRoot` to `src` and `deleteOutDir` to true, and `tsconfig.build.json` extends `tsconfig.json` with `rootDir: ./src` and excludes `test` and `*spec.ts`, so `nest build` writes a flat `dist/` with `main.js` at its root. `tsconfig.json` uses `module: nodenext` with `target: ES2023`, `emitDecoratorMetadata` and `importHelpers`, which means `tslib` and `reflect-metadata` are runtime dependencies the production install must keep. `src/main.ts` listens on `process.env.PORT ?? 3000`, and `src/app.module.ts` reads `OBSERVE_APP_KEY` and `OBSERVE_APP_SECRET` from the environment with empty defaults, so the container needs no secret to boot.

The Dockerfile it writes has three stages. A dependencies stage on a Node 24 image copies `package.json` and `package-lock.json` and runs `npm ci`, so the layer caches until the lockfile changes. A build stage copies the source and runs `npm run build`, then `npm prune --omit=dev` to drop the `@nestjs/cli`, Vitest, oxlint and TypeScript packages. A runtime stage on a slim Node 24 image copies `dist/`, `package.json` and the pruned `node_modules`, runs as the `node` user, sets `NODE_ENV=production` and `PORT=3000`, exposes the port and starts with `node dist/main`. A `.dockerignore` keeps `node_modules`, `dist`, `.env` and `.git` out of the context, and `compose.yaml` runs the one service with a health check that requests `/`.

Before it opens the pull request, the agent builds the image in the sandbox, starts it with `docker compose up`, and requests `/`, which the `AppController` answers with the `Hello World` greeting that `test/app.e2e-spec.ts` expects. It also runs `npm run test:e2e` once against the source so the pull request shows the app's own check passing, records the image size from `docker images`, and keeps the container log that shows Nest mapping its routes.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Containerize this NestJS app. First read package.json, the lockfile, nest-cli.json, tsconfig.json, tsconfig.build.json and src/main.ts, and list the Node version the repository implies, the package manager, the build command and its output directory, the entry file, the port, and every environment variable or service the app needs at runtime. Write a production multi-stage Dockerfile: a dependencies stage that installs from the lockfile, a build stage that runs the Nest build and prunes dev dependencies, and a slim runtime stage with only the compiled output and production dependencies, running as a non-root user with NODE_ENV and PORT set. Add a .dockerignore and a compose.yaml with the app service, its port and a health check, plus any database or queue the app depends on. Build the image in the sandbox, start it with docker compose, request the root route and fix anything until it answers. Open a pull request with the image size, the container log, 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 three new files, `Dockerfile`, `compose.yaml` and `.dockerignore`, and no change to the source. The description lists what the agent read and the choice each line follows from: Node 24 from `@types/node`, `npm ci` from the lockfile, `dist/main` from `nest-cli.json` and `tsconfig.build.json`, `tslib` and `reflect-metadata` kept because the compiled output imports them, and port 3000 from `src/main.ts`.

It also carries the image size the sandbox build printed, the container's startup log with the routes Nest mapped, the response from `/`, and the commands to build and run locally. A reviewer checks that the runtime stage copies no TypeScript source or dev dependencies, that the container does not run as root, and that the health check hits a route that exists.

The session read returns the repository and the pull request, and the transcript holds every command with its output: the install, `nest build`, the prune, the `docker build` layers, the compose start and the request to the root route.

## Run it on every new service without a Dockerfile

Start one run per repository from your own server: `POST /o/{org}/sessions` with this prompt and the repository in `resources`, one call each. A script can walk `GET /o/{org}/github` for the repositories the Rush GitHub App installation covers and start a run for each one that has no `Dockerfile`. Every run clones into its own sandbox and opens its own pull request.

With a `webhook_url` on the key, each run POSTs `run.completed` when it finishes, with the `session_url` to read the pull request from. A key has at most `max_concurrent` runs in progress at once, 4 by default, so a batch over a whole organization queues past that.

## Why run it on Rush?

A Dockerfile that was never built is a guess. The sandbox has Docker, so the agent builds the image, starts the container and requests the route before the pull request exists, and the transcript keeps the build layers and the container log as the evidence a reviewer reads instead of pulling the branch to try it.

The details that make a NestJS image wrong are all in config files: the `nodenext` module setting, `importHelpers` pulling in `tslib`, the flat `dist/` from `deleteOutDir`, the port read from the environment. The agent reads them in the sandbox and the pull request names the file behind each line of the Dockerfile, so the next person can change it with the same reasons in front of them.

## Frequently asked questions

### Will it use the official Node image or Alpine?

A slim Debian-based Node image for the runtime stage by default, because native modules such as bcrypt or Prisma's engines build and load there without extra packages. If the prompt asks for Alpine the agent uses it and adds what the app's native dependencies need, and the image size in the pull request shows the difference.

### My app uses pnpm or Yarn. Does the Dockerfile follow that?

Yes. The agent reads the `packageManager` field and the lockfile that exists and installs with that manager, with the lockfile frozen; for pnpm it uses `pnpm install --frozen-lockfile` and `pnpm prune --prod`. The starter uses npm because it ships `package-lock.json`.

### What about my database, Redis or a queue?

The prompt asks for every runtime service to be listed first. Each becomes a service in `compose.yaml` with a volume and a health check, and the app's connection string becomes an environment variable the compose file reads from `.env`, never a value baked into the image. The starter needs none, so its compose file has one service.

### Does it keep devDependencies out of the image?

Yes. The build stage runs `npm prune --omit=dev` after `nest build`, which removes `@nestjs/cli`, TypeScript, Vitest and oxlint, and the runtime stage copies only `dist/`, `package.json` and the pruned `node_modules`. The pull request's image size reflects that.
