All jobs

Dockerize a Fastify App With MySQL

Yes. An agent writes a multi-stage Dockerfile for your TypeScript Fastify app, adds it to Compose beside MySQL with a health check, boots the stack and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads how the app is built and started before it writes the Dockerfile, because the answers are in several small files. In a Fastify app shaped like fastify/demo, package.json is an ES module project on fastify 5.10 with TypeScript 6.0, @fastify/autoload, @fastify/env, knex with mysql2, postgrator for migrations and close-with-grace; build runs tsc into dist, start runs npm run build && node dist/server.js, dev runs tsx --env-file=.env ./src/server.ts, and the db:* scripts run the files under scripts/ with the same --env-file. tsconfig.json extends fastify-tsconfig with rootDir: src and outDir: dist. There is no Dockerfile; docker-compose.yml has one service, db, on mysql:8.4 with a mysqladmin ping healthcheck and a named volume. CI in .github/workflows/ci.yml runs on Node 22 and 24 against a MySQL 8.4 service.

Three details change what a naive Dockerfile would do. .gitignore lists package-lock.json, yarn.lock and pnpm-lock.yaml, so there is no lock file to feed npm ci; the agent uses npm install and says in the pull request that a committed lock file is what makes the image reproducible, leaving that decision to the maintainers. .npmrc sets ignore-scripts=true and min-release-age=7, which the image inherits by copying the file before install. And src/plugins/external/env.ts validates the environment at startup with a schema that requires MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, COOKIE_SECRET, COOKIE_NAME and COOKIE_SECURED, so a container started without them exits with a clear error, and .env.example already lists them with RATE_LIMIT_MAX=4 for the tests.

The Dockerfile has a build stage and a runtime stage on the Node version CI tests, which the agent names in the pull request. The build stage copies package.json and .npmrc, installs, copies src, @types and tsconfig.json, and runs npm run build. The runtime stage installs with --omit=dev, copies dist and migrations, runs as the image's node user, exposes 3000, which src/server.ts listens on when PORT is unset, and starts with node dist/server.js rather than npm start, so the build does not rerun in the container and signals reach the process; close-with-grace in server.ts already handles them with FASTIFY_CLOSE_GRACE_DELAY. A .dockerignore excludes node_modules, dist, .env and uploads/tasks. In docker-compose.yml the agent adds an app service built from the Dockerfile, with env_file: .env, MYSQL_HOST=db overriding the file, depends_on on db with condition: service_healthy, and a one-shot migrate service built from the Dockerfile's build stage, where tsx is still installed, that runs scripts/migrate.ts once before app starts, since the migrations live in scripts/ and are not run by the server.

For a container healthcheck it makes one small code change: src/plugins/external/under-pressure.ts already runs SELECT 1 through knex every 5 seconds as its healthCheck, but exposes no route, so the agent adds exposeStatusRoute at /health to that plugin's config and uses it in the Compose healthcheck, which then reflects database connectivity rather than process liveness. It runs docker compose up --build, waits for app to turn healthy, requests /health and the home route, and runs npm test, which seeds the database and runs the Node test runner under c8, against the Compose MySQL. The pull request carries the Compose log and the health response.

Start it with one API call

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

{
  "agent": "claude",
  "input": "Dockerize this Fastify app. Read package.json, .npmrc, .gitignore, tsconfig.json, src/server.ts, src/app.ts, src/plugins/external/env.ts, src/plugins/external/under-pressure.ts, scripts/migrate.ts, .env.example, docker-compose.yml and .github/workflows/ci.yml first. Write a two-stage Dockerfile on a Node version from the CI matrix: build with tsc, then a runtime stage with production dependencies only, running dist/server.js as the node user on port 3000. Note that lock files are gitignored, use npm install, and say in the pull request what a committed lock file would change. Add a .dockerignore. Add app and one-shot migrate services to docker-compose.yml, depending on the db healthcheck, with env_file .env and MYSQL_HOST=db. Expose under-pressure's status route at /health and use it as the app healthcheck. Run docker compose up --build, request /health and the home route, run npm test against the Compose database, and open a pull request with the Compose log and responses.",
  "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 that adds Dockerfile and .dockerignore, extends docker-compose.yml with the app and migrate services, and makes one change in src/plugins/external/under-pressure.ts to expose the status route. No route, schema or repository file changes. The description names the Node image tag it chose from the CI matrix, quotes the Compose log through the point where migrate exits and app turns healthy, and includes the /health response and the home route's response.

A reviewer checks that the image copies .npmrc before npm install, so ignore-scripts=true and min-release-age=7 apply in the build as they do on a laptop; that .env is excluded from the build context and COOKIE_SECRET arrives only through the environment; and that the npm test run in the transcript used the Compose database, since the test script seeds it first. The lock-file paragraph in the description is the decision the maintainers make, not the agent.

Run it on every service without a Dockerfile

A script on your server lists the organization's repositories with GET /o/{org}/github, checks each for a package.json that depends on fastify and has no Dockerfile, and sends one POST /o/{org}/sessions per match with this prompt and that repository as the resources URL. A request_id per repository makes the script safe to re-run.

Set a webhook_url on the API key and Rush POSTs run.completed when each turn finishes, with the session_url to read the pull request from; a run.failed names the service whose stack the agent could not bring up.

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

Why run it on Rush?

Containerizing a TypeScript service is a chain of small decisions: which Node, npm ci or npm install when there is no lock file, whether scripts run, how migrations happen, what the healthcheck means. The agent reads the files that answer each one, writes the decision into the pull request, and proves the result by bringing the stack up in its own sandbox and running the test suite against it. Your laptop stays out of it.

The pull request is reviewable on its own: the Compose log, the health response and the test run are in the transcript, so the reviewer reads rather than reproduces. Several services can be containerized at once, each in its own sandbox and branch.

Frequently asked questions

Why not npm ci?

Because .gitignore excludes every lock file, so there is nothing for npm ci to install from. The agent uses npm install, and the pull request says plainly that committing a lock file is what would make image builds reproducible. That is a repository policy, so it is left for the maintainers to decide.

How do the migrations run?

Through a one-shot migrate service in the Compose file that runs scripts/migrate.ts once db is healthy, before app starts. The server does not run migrations itself; postgrator is driven from scripts/, so the agent keeps that separation rather than adding a startup hook.

What does the health check actually test?

Database connectivity. @fastify/under-pressure is already configured with a healthCheck that runs SELECT 1 through knex every 5 seconds; the agent only exposes its status route at /health. A container whose database connection is lost reports unhealthy, which a check on the home route would not.

Will the tests pass inside the container?

The tests run from the sandbox against the Compose MySQL, the way CI runs them against its MySQL service, with RATE_LIMIT_MAX=4 from .env.example because the rate-limit test depends on it. The transcript holds the npm test output. The image itself carries production dependencies only, so tests are not run inside it.