# Dockerize a Laravel App

Yes. An agent writes a multi-stage Dockerfile and compose.yaml for your Laravel app, boots it with its database in a sandbox and opens a pull request.

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

## What the agent does

The agent clones the repository into its own sandbox and reads what the image has to carry before it writes a line of the Dockerfile. In a Laravel app like laravelio/laravel.io, `composer.json` pins `"php": "8.4.*"` with `laravel/framework` `^11.5`, Livewire 3, Filament 4 and `intervention/image` 3, and `package.json` builds the front end with Vite 6 and Tailwind 4 (`"build": "vite build"`). The test workflow in `.github/workflows/tests.yml` is the most honest list of what the app needs to run: PHP 8.4 with the `dom`, `curl`, `mbstring`, `zip`, `pcntl`, `pdo` and `gd` extensions, Node `22.x` for `npm run build`, and a `mysql:5.7` service with a `laravel` database. There is no `Dockerfile` or compose file in the repository today.

From that it writes a three-stage `Dockerfile`. A Node 22 stage runs `npm ci` and `npm run build` so the hashed assets land in `public/build`; a Composer stage runs `composer install --no-dev --prefer-dist --optimize-autoloader` against `composer.lock`; and the runtime stage starts from a PHP 8.4 image, installs the extensions the workflow lists plus `pdo_mysql`, copies in `vendor/` and `public/build` from the earlier stages, and never ships Node, Composer or the dev dependencies. Two details come from reading the app rather than a template. `database/schema/mysql-schema.sql` is a schema dump, and Laravel loads a dump with the `mysql` command-line client, so the runtime image needs that client or `php artisan migrate` fails on an empty database. And `.env.example` sets `CACHE_STORE=file` and `SESSION_DRIVER=file`, so `storage/` and `bootstrap/cache` have to be writable by the user the container runs as.

Then `compose.yaml`: an `app` service built from that Dockerfile, a `mysql` service on the same 5.7 image CI uses with a health check, `DB_HOST`, `DB_DATABASE`, `DB_USERNAME` and `DB_PASSWORD` passed from the environment rather than baked in, and a named volume for the database. Because `routes/console.php` schedules commands such as `lio:generate-sitemap` and `lio:update-article-view-counts`, the agent adds a `scheduler` service running `php artisan schedule:work` from the same image, or says in the PR why it left one out. It also writes a `.dockerignore` so `vendor/`, `node_modules/`, `.env` and `storage/logs` stay out of the build context.

Last, it proves the thing boots. In the sandbox it runs `docker compose build`, starts the stack, generates an `APP_KEY`, runs `php artisan migrate --force` and `php artisan db:seed` inside the `app` container (the repository ships `UserSeeder`, `TagSeeder`, `ReplySeeder` and others), and opens the home page, served by `HomeController::show`, in the sandbox's browser. It records `docker image ls` for the image size, takes a screenshot of the page, and fixes and rebuilds until the page loads with seeded threads and articles instead of a 500.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "Containerize this Laravel app. First read composer.json, composer.lock, package.json, .env.example, config/database.php, the CI workflow and any schema dump, and list the PHP version, PHP extensions, Node version, database and writable paths the app needs. Write a production multi-stage Dockerfile: a Node stage that builds the Vite assets, a Composer stage with --no-dev and an optimized autoloader, and a slim PHP runtime stage with only the extensions the app uses, the database client a schema dump needs, and a non-root user that can write to storage and bootstrap/cache. Add a .dockerignore and a compose.yaml with the app, the same database version CI uses with a health check and a volume, configuration from environment variables, and a scheduler service if the app schedules commands. Build it in the sandbox, start it, set an APP_KEY, run migrations and seeders inside the app container, 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 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 four new files: `Dockerfile`, `compose.yaml`, `.dockerignore`, and a short section in the README or a `docker/` note with the commands to build, migrate and run. The description lists what the agent read and why each choice follows from it (the PHP 8.4 runtime, the extensions, the MySQL version matched to CI, the database client for the schema dump), the final image size from `docker image ls`, and the screenshot of the home page served by the container.

The proof is in the session's transcript, which keeps every command with its output: each `docker compose build`, the container logs, the `migrate` and `db:seed` runs, and the browser load of the home page, including any attempt that failed before a fix. Your repository's own workflows run on the pull request too; for an app like laravel.io the `Tests` workflow runs Pest against MySQL on every pull request, which shows that adding the Docker files changed nothing the suite covers.

Before merging, a reviewer checks that no secret or `.env` is copied into the image, that the runtime stage carries no Node, Composer or dev packages, that the container runs as a non-root user, and that the MySQL version in `compose.yaml` matches production, not only CI. If production runs a newer MySQL than 5.7, one follow-up message to the same session changes it and boots the stack again.

## Run it on every new service without a Dockerfile

A team moving several Laravel apps to containers can start one run per repository, each a `POST /o/{org}/sessions` with that repository in `resources` and the same prompt. Send an `Idempotency-Key` per repository so a retried request returns the run it already started rather than a second one.

A key runs up to its `max_concurrent` sessions at once (4 by default), each in its own sandbox, so four apps build and boot in parallel without sharing a Docker daemon. A webhook on the key posts `run.completed` or `run.failed` when each turn finishes, with the `session_url` that holds the result and the pull request.

## Why run it on Rush?

Proving a Dockerfile works means building it, starting a database next to it, migrating and seeding, and loading a page, several times over while the image is wrong. On a laptop that fills the local Docker cache with half-built layers and can collide with a MySQL already on port 3306. In a Rush sandbox the agent has its own Docker, its own database container and its own browser, so the build loop runs where it cannot touch anything else, and the sandbox parks with its disk and memory when the turn ends.

The pull request comes with what makes it trustworthy: the build logs, the migrate output, the screenshot of the page served from the container and the image size, all in the transcript next to the commands that produced them. A reviewer can see that the stack booted from a clean checkout, not on a machine that already had PHP 8.4 and the right extensions installed.

## Frequently asked questions

### Will it use Laravel Sail?

Not by default. Sail is a development environment, and the prompt asks for a production image: a slim PHP runtime with no dev dependencies. If your team wants Sail for local work too, say so in the input and the agent can add it alongside, but the production Dockerfile stays separate.

### Which PHP and MySQL versions does it pick?

The ones your repository already states. For laravel.io that is PHP 8.4 from `composer.json` and MySQL 5.7 from the CI workflow's service. If production differs from CI, name the version in the input and it uses that instead.

### Does it run the queue worker and the scheduler?

It reads `routes/console.php` and `config/queue.php` if present. When the app schedules commands, it adds a scheduler service from the same image, and when jobs are queued to a real connection it adds a worker. Either way the pull request says which services exist and why.

### Does it need my production secrets?

No. The compose file reads database credentials and the app key from environment variables, and the agent uses throwaway values inside the sandbox to boot the stack. If the app needs a service such as Algolia to render the home page, the agent reports that instead of guessing a key.
