skip to content

In Laravel Sail, how do you switch the app container's PHP version, add a service with `sail:add`, and customise the Dockerfiles with `sail:publish`?

level: middleimportance: should knowfreq 30%

answer

  1. build context names the runtime folder
  2. rebuild with sail build --no-cache
  3. sail:add edits compose.yaml and .env
  4. sail:publish copies runtimes into ./docker
  5. PHP_EXTENSIONS and NODE_VERSION build args

basics

~10 s

PHP is chosen by the laravel.test build context in compose.yaml (vendor/laravel/sail/runtimes/8.x) plus its image name, followed by sail build --no-cache. sail:add appends services; sail:publish copies Sail's Dockerfiles into ./docker for editing.

solid answer

~50 s

The PHP version lives in `compose.yaml`: the `laravel.test` service's `build.context` points at `./vendor/laravel/sail/runtimes/8.5` (runtimes exist for 8.0 to 8.5). Change it to `8.4`, rename `image` to `sail-8.4/app`, then `sail build --no-cache` and `sail up`. Extra extensions go in the `PHP_EXTENSIONS` build argument and another Node version in `NODE_VERSION`, each followed by a rebuild. `php artisan sail:add redis,meilisearch` (or interactively) adds any missing service to `compose.yaml`, links it through `laravel.test`'s `depends_on`, adds a named volume where the service keeps data, updates `.env`, and pulls and builds. `php artisan sail:publish` publishes Sail's runtime Dockerfiles, `php.ini` and supervisor config, plus the database init scripts, into `./docker` and rewrites `compose.yaml` to build from there — after which you own those files. Give the image a unique name and rebuild. `sail:install --devcontainer` writes a `.devcontainer/devcontainer.json` for editors that support dev containers.

go deeper

for a junior

Recall where the PHP version is set in compose.yaml and that changes need sail build --no-cache.

for a middle

Explain what sail:add changes (services, depends_on, volumes, .env) and what sail:publish moves into ./docker.

for a senior

Keep the Sail runtime aligned with production's PHP, prefer build args over publishing, and own the maintenance cost when you do publish.

for a principal

Set a policy for how the team's local runtime tracks production versions, and who maintains customised Dockerfiles once published.

## Where Sail keeps its knobs Everything about a Sail environment is in two places the team commits: **`compose.yaml`**, which describes the containers, and **`.env`**, which supplies their variables. Customising Sail is therefore editing those files — plus, for deeper changes, owning the Dockerfiles. Three tasks come up in practice: changing the PHP version, adding services, and customising the application image. ## Switching the PHP version The `laravel.test` service is **built** from one of Sail's runtime folders: ```yaml laravel.test: build: context: ./vendor/laravel/sail/runtimes/8.5 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' image: sail-8.5/app ``` 1. Change `context` to another runtime — Sail 1.68 ships `8.0`, `8.1`, `8.2`, `8.3`, `8.4` and `8.5`. 2. Change `image` to match, e.g. `sail-8.4/app`, so the old image is not reused under the new name. 3. Run `sail build --no-cache`, then `sail up`. `sail:install --php=8.4` makes the same choice at install time; the default is 8.5. For the team whose laptops run different PHP versions, this is the single line that decides what everyone runs — and it should match the version production uses. ## Extensions and Node The runtime images already include a broad set of extensions (database drivers, Redis, Imagick, Swoole, Xdebug, PCOV and more). For anything else, add a space-separated `PHP_EXTENSIONS` build argument; for another Node release, set `NODE_VERSION` (Node 24 is the default). Both take effect only after a rebuild: ```yaml args: WWWGROUP: '${WWWGROUP}' PHP_EXTENSIONS: 'gmp' NODE_VERSION: '22' ``` ## Adding services with `sail:add` `php artisan sail:add` (or `sail artisan sail:add`) takes a comma-separated list — `sail:add redis,meilisearch` — or shows the same service picker as `sail:install`. For each chosen service it: - adds the service definition to `compose.yaml` if it is not already there; - adds it to `laravel.test`'s `depends_on` list; - declares a named volume such as `sail-redis` for services that keep data; - updates `.env` (for example `REDIS_HOST=redis`, or `SCOUT_DRIVER=meilisearch` and `MEILISEARCH_HOST`); - pulls the images and rebuilds. Available services include `mysql`, `pgsql`, `mariadb`, `mongodb`, `redis`, `valkey`, `memcached`, `meilisearch`, `typesense`, `rustfs` (S3-compatible storage), `mailpit`, `mailtrap-local`, `rabbitmq`, `selenium` and `soketi`. ## Owning the Dockerfiles with `sail:publish` Out of the box, the image is built from files inside `vendor/laravel/sail`, which Composer overwrites on update. To change the image itself — an OS package, a `php.ini` setting, the supervisor program — run: ```bash sail artisan sail:publish ``` It publishes the runtime folders (`Dockerfile`, `php.ini`, `supervisord.conf`, `start-container`) and the database init scripts into a `docker/` directory, and rewrites `compose.yaml` so builds and mounts point at `./docker/8.5` and friends instead of `vendor/`. From then on: - the files are yours: edit, commit, review; - Sail updates no longer change them, so you merge upstream improvements by hand; - give the application image a unique `image` name, especially if several Sail projects share one machine, and run `sail build --no-cache`. ## Dev containers `php artisan sail:install --devcontainer` also writes `.devcontainer/devcontainer.json`, which tells editors that support the Dev Containers specification to open the project *inside* `laravel.test` (as the `sail` user, at `/var/www/html`), reusing the project's `compose.yaml`. It adds `WWWUSER` and `WWWGROUP` values to `.env` for file ownership. ## Keeping local and production in step The point of pinning a runtime is lost if it drifts from what ships. When production moves to a new PHP minor, change `compose.yaml` in the same pull request as the platform change, rebuild, and run the test suite under the new runtime before merging. Review extension lists the same way: an extension added through `PHP_EXTENSIONS` locally must also exist on the production platform, or code that depends on it will pass locally and fail after deploy. Treat `compose.yaml` and any published `docker/` files as code — reviewed, versioned and owned by someone. ## Common mistakes - Editing the Dockerfile inside `vendor/` — lost on the next `composer update`. - Changing `context` but not rebuilding, then wondering why `sail php -v` is unchanged. - Adding a service by hand without adding it to `depends_on` or `.env`.

  • After changing `laravel.test`'s build context from `runtimes/8.5` to `runtimes/8.4`, `sail php -v` still prints 8.5. Why?
    The running container and the cached image were built from the old context, and `sail up` reuses an existing image with the configured name rather than rebuilding it. Rename the `image` to `sail-8.4/app`, run `sail build --no-cache` and then `sail up` so the container is recreated from the new image.
  • What is the cost of running `sail:publish` on a team project?
    The team takes ownership of the Dockerfiles, `php.ini` and supervisor config in `./docker`. Upgrades to `laravel/sail` no longer change them, so security updates, new PHP runtimes and fixes must be merged by hand. It is worth it for real customisation, not for a single extension that `PHP_EXTENSIONS` could add.

saying these in an interview costs you the question

  • Changing the PHP version means reinstalling Sail with composer.
  • sail:add changes compose.yaml but leaves .env untouched.
  • Edits to vendor/laravel/sail/runtimes are the normal way to customise the image.
  • A new build context takes effect on the next sail up without rebuilding.
  • sail:publish is needed just to add one PHP extension.