All jobs

Dockerize a Vue App

Yes. An agent writes a two-stage Dockerfile for your Vite-built Vue app with an nginx config for the history router, builds and runs it, and opens a pull request.

Run this on RushIn a Project that has your repository.

What the agent does

The agent clones the app into its own cloud sandbox and reads how it is built before it writes a Dockerfile. In a Vue app like realworld-apps/vue-realworld-example-app, package.json has Vue ^3.5.0, vue-router ^5.1.0 and pinia ^3.0.0 with Vite ^8.0.16, the scripts are build (vite build), serve (vite), preview, lint, format:check and the Playwright test scripts, and the lockfile is bun.lock, which both .github/workflows/playwright.yml and deploy.yml honour with oven-sh/setup-bun and bun install. There is no Dockerfile, no compose file and no .dockerignore. deploy.yml builds dist/ and attaches it to a GitHub release as a zip, which tells the agent the output is a static bundle rather than a Node server.

Two details decide the image. First, src/main.js imports ../realworld/assets/theme/styles.css, and .gitmodules says realworld is a git submodule of the RealWorld spec repository; both workflows check out with submodules: true, so the build context must contain that directory or Vite fails on the first import. Second, src/common/config.js reads import.meta.env.VITE_API_URL with a fallback to https://api.realworld.show/api, and Vite bakes that value in at build time, so the API base is a build argument, not a runtime environment variable. src/router/index.js uses createWebHistory, so a deep link such as /article/some-slug or /profile/someone must be answered with index.html by the server. index.html loads Ionicons and Google Fonts from CDNs, which the container does not need to serve.

The Dockerfile it writes has two stages. The build stage starts from the official Bun image, copies package.json and bun.lock first so the install layer caches, runs bun install --frozen-lockfile, copies the rest of the context including realworld/, takes VITE_API_URL as an ARG and runs bun run build. The runtime stage starts from an nginx image, copies dist/ into the web root and adds an nginx.conf with try_files $uri $uri/ /index.html for the router, long cache headers for the hashed files under /assets/, no caching for index.html, and gzip on. A .dockerignore keeps node_modules, dist, playwright-report, test-results and .git out of the context, and a compose.yaml passes the build argument and maps the port.

Before it opens anything, the agent runs git submodule update --init, builds the image in the sandbox, starts it with docker compose up, and checks three things with curl and the sandbox browser: / returns the app shell, /article/example and /editor return index.html with a 200 rather than nginx's 404, and a hashed file under /assets/ comes back with the cache header. It opens the home page in the browser, which renders the global feed from the public API, takes a screenshot, and records what docker images prints for the image. It then runs bun run lint and bun run format:check so the new files do not break the existing lint.yml check.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Dockerize this Vue app. Read package.json, bun.lock, vite.config.js, src/main.js, src/common/config.js, src/router/index.js, .gitmodules and the workflows under .github/workflows first. Write a two-stage Dockerfile: a build stage on the official Bun image that copies package.json and bun.lock before the rest, runs bun install --frozen-lockfile, takes VITE_API_URL as a build argument and runs bun run build with the realworld submodule present in the context; and a runtime stage on nginx that serves dist with an nginx.conf using try_files to index.html for the history-mode router, long cache headers for /assets, no cache for index.html and gzip enabled. Add a .dockerignore and a compose.yaml that passes the build argument. Run git submodule update --init, build the image, start it with docker compose, confirm with curl that / and a deep link like /article/example return the app shell with status 200 and that a hashed asset carries the cache header, open the home page in the browser and take a screenshot, and record the image size from docker images. Run bun run lint and bun run format:check. Open a pull request with the files, the commands to build and run, the screenshot, and a note that VITE_API_URL is fixed at build time.",
  "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 adding Dockerfile, nginx.conf, .dockerignore and compose.yaml, with no change to package.json, vite.config.js or the workflows. The description explains each choice from what the agent read: the Bun image because of bun.lock and the workflows, the submodule step because of the stylesheet import in src/main.js, the try_files rule because of createWebHistory, and the build argument because VITE_API_URL is read at build time. It includes the three curl results, the image size docker images printed in the sandbox, the screenshot of the home page, and the commands to build and run locally.

The session read names the repository and the pull request, and the screenshot is one of the session's artifacts with a download link that lasts an hour per read. The transcript at GET /o/{org}/sessions/{id}/messages keeps the submodule update, the docker build layers, the compose start and every curl, so a reviewer can see the deep link test without checking the branch out.

Before merging, a reviewer confirms the API base the team deploys against is the one passed as the build argument, decides whether CI should build the image on every pull request, which is a one-step addition to playwright.yml or a new workflow, and checks that the nginx config does not cache index.html, since a cached shell would keep pointing at old hashed assets after a deploy.

Run it on every new Vue repository

A team that starts front-end repositories from a template sends this request once per new repository, from a script or the console, with resources pointing at it and a request_id per repository so a retry returns the run already started. Each run has its own sandbox, branch and pull request.

Set a webhook URL on the key and Rush POSTs run.completed with data.session_url when the turn ends, so a handler can read the pull request URL and post it where the team will see it. A review request to add a health endpoint or a different API URL is a follow-up on the same session, which wakes the parked sandbox with the image already built.

The use case this job follows, with its automation and trigger: Hand off from your terminal.

Why run it on Rush?

A Dockerfile that was never built is a guess, and the two things that break this one, the missing submodule and a router deep link answered with a 404, only show up when the image runs. The agent builds and runs it in its own sandbox, checks the deep links with curl, and loads the page in a browser before the pull request exists, so a reviewer sees evidence rather than a promise.

The evidence stays with the result: the build layers, the compose start, each curl and the screenshot are on the session. Several front-end repositories can be containerised at once, each in its own sandbox, without any of the builds touching a developer's Docker daemon.

Frequently asked questions

Why is the API URL a build argument and not an environment variable?

Because src/common/config.js reads import.meta.env.VITE_API_URL, and Vite replaces that at build time, so the value is in the bundle by the time nginx serves it. The pull request says so and shows --build-arg in the build command. Making it runtime-configurable would need a small change to the app, which can be a follow-up.

Does the image need the realworld submodule?

Yes, for the build stage. src/main.js imports ../realworld/assets/theme/styles.css, so the submodule must be checked out in the build context, as both existing workflows do with submodules: true. The runtime stage copies only dist/, so the submodule is not in the final image.

Will deep links work?

That is what the try_files $uri $uri/ /index.html rule in nginx.conf is for, since src/router/index.js uses createWebHistory. The agent requests /article/example and /editor on the running container and quotes the 200 responses in the pull request.

Does it change the existing release workflow?

No. deploy.yml keeps zipping dist/ onto a GitHub release, and playwright.yml keeps running the end-to-end tests against the dev server. Building the image in CI is a suggested next step in the description, not part of this change.