# Dockerize an Angular App

Yes. An agent builds your Angular app in a Bun stage, serves the output from nginx with a single-page fallback, checks deep links in a sandbox and opens a PR.

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

## What the agent does

The agent starts with how the app is built and where the output lands, because an Angular image is a static-file image and every mistake is in those two facts. In an app like `realworld-apps/angular-realworld-example-app`, `package.json` pins Angular `21.2.12` with the `@angular/build` application builder, `engines.node` is `>=20.11.1`, and the package manager is Bun: `bun.lock` is checked in, the `setup` script runs `git submodule init && git submodule update && bun install`, and every workflow under `.github/workflows/` installs with `bun install`. `angular.json` writes the build to `dist/angular-conduit`, and `deploy.yml` zips `dist/angular-conduit/browser`, which is the directory the runtime image has to serve. There is no `Dockerfile`, no `.dockerignore` and no nginx configuration.

Two details in `angular.json` decide whether the Docker build works at all. The `styles` entry is `realworld/assets/theme/styles.css` and an `assets` glob copies `realworld/assets/media/*.svg`, and `realworld` is a git submodule declared in `.gitmodules`, so a build context cloned without `--recurse-submodules` fails on a missing stylesheet. The agent documents `git submodule update --init` before `docker build` and keeps `submodules: true` in any workflow it adds, matching the existing ones. The other detail is `src/_redirects`, copied to the output root with `/*  /index.html 200`: it is a Netlify-style rule that nginx ignores, so the agent writes the equivalent `try_files $uri $uri/ /index.html` into `nginx.conf` so `/article/some-slug` and `/profile/someone` load on refresh.

The `Dockerfile` has a build stage on the Bun image that copies `package.json` and `bun.lock` first so `bun install --frozen-lockfile` caches as its own layer, then copies the source and the submodule and runs `bun run build`, which uses the `production` configuration with `outputHashing: all` and the bundle budgets of 500 kB warning and 1 MB error from `angular.json`. The runtime stage is `nginx:alpine` with `dist/angular-conduit/browser` copied to the web root and an `nginx.conf` that adds the fallback, gzip for text types, a long cache lifetime for the hashed files and no cache for `index.html`. No backend container is needed: `src/app/core/interceptors/api.interceptor.ts` prefixes every request with `https://api.realworld.show/api`, so the pull request says where to change that for your own API. A `.dockerignore` keeps `node_modules`, `dist`, `.angular/cache`, `playwright-report` and `test-results` out of the context.

Then it proves the image serves the app. In the sandbox it builds and starts the container, requests `/` and a deep link such as `/article/does-not-exist` and checks both return the `index.html` shell with status 200, and checks a hashed script file comes back with the long cache header. It then runs the repository's own end-to-end suite against the container: `playwright.config.ts` uses `baseURL: http://localhost:4200` with `reuseExistingServer` on outside CI, so publishing the container on port 4200 lets `bun run test:e2e` run unchanged against nginx rather than the dev server. It records `docker image ls` for the image size and fixes and rebuilds until the requests and the suite pass.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Containerize this Angular app. Read package.json, bun.lock, angular.json, .gitmodules, src/_redirects, src/index.html, src/app/core/interceptors/api.interceptor.ts, playwright.config.ts, .gitignore and the workflows under .github/workflows/ first, and list the Angular version, the package manager, the build command, the output directory, any git submodule the build reads, the API base URL and the port the Playwright config expects. Write a multi-stage Dockerfile: a build stage that caches the dependency install in its own layer and runs the production build, and an nginx runtime stage that serves the browser output with a single-page fallback to index.html, gzip, long cache headers for hashed files and no cache for index.html. Add a .dockerignore. Build it in the sandbox, start it on the Playwright port, request the home page and a deep link and confirm both return the app shell, run the existing Playwright suite against the container, fix anything until it passes, 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"
    }
  ]
}
```

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`, `nginx.conf`, `.dockerignore` and a README section with the build and run commands, including the submodule step. The description lists each choice and the file it came from: Bun from `bun.lock` and the workflows, the output directory from `angular.json` and `deploy.yml`, the fallback rule from `src/_redirects`, the submodule from `.gitmodules`, and the external API from the interceptor.

The transcript on the session keeps every command with its output: each `docker build`, the nginx access log lines for `/` and the deep link, the header check on a hashed file, the Playwright run against the container, and any build that failed before the fix. The repository's own `Format Check`, `Playwright Tests` and `Security Tests` workflows run on the pull request too, so Prettier and both Playwright suites report on a change that touches no TypeScript file.

Before merging, a reviewer checks that the runtime image contains no Bun or `node_modules`, that `index.html` is served with no cache so a deploy is picked up, that the fallback does not swallow requests for missing hashed assets with a 200, and that the API base URL is the one production should use. If the team deploys behind a path prefix, one follow-up message to the same session changes `base href` and the nginx location and rebuilds.

## Run it on every new Angular project without a Dockerfile

A team containerizing several front ends 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 and its own browser for Playwright, so several builds proceed at once without sharing a port or a cache. 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.

## Why run it on Rush?

The mistakes in this job show up only when the container runs: a refresh on a deep link returns the nginx 404 page, a stylesheet from a submodule is missing, or a stale `index.html` keeps pointing at hashed files that no longer exist. On a laptop that means building, clicking around, and building again while the dev server holds port 4200. In a Rush sandbox the agent has its own Docker daemon, its own browser and its own ports, and the sandbox parks when the turn ends.

The pull request carries its own proof. The deep-link requests, the cache header check, the Playwright run against the container and the image size sit in the transcript beside the commands that produced them, so a reviewer can see the app served from nginx rather than from `ng serve` on a machine that already had Bun and the submodule checked out.

## Frequently asked questions

### Why does the build need the git submodule?

Because `angular.json` reads the theme stylesheet and the media SVGs from `realworld/assets/`, which `.gitmodules` declares as a submodule. A clone without it has an empty `realworld/` directory and the build fails on the missing stylesheet. The pull request documents `git submodule update --init` before `docker build`, the same step the repository's `setup` script and its workflows already perform.

### Does it containerize a backend too?

No. The app calls `https://api.realworld.show/api` from `api.interceptor.ts`, so the image is a static site with nothing else to run. The pull request names that file as the place to point the app at your own API, and if you want nginx to proxy `/api` to a backend container, one follow-up message adds that location block.

### Will refreshing a deep link work?

Yes. `src/_redirects` only works on hosts that read that file, so the `nginx.conf` adds `try_files $uri $uri/ /index.html`, which serves the app shell for any path that is not a real file. The agent checks this in the sandbox by requesting an article URL directly and confirming the shell comes back with status 200.

### Does it run the Playwright tests against the container?

Yes. `playwright.config.ts` targets `http://localhost:4200` and reuses an existing server when it is not running in CI, so the agent publishes the container on port 4200 and runs `bun run test:e2e` unchanged. The run and its report are in the transcript.
