With Laravel Sail, why run `sail artisan migrate` rather than `php artisan migrate`, and what do `sail composer`, `sail npm` and `sail test` do?
answer
- the script wraps docker compose exec
- runs inside laravel.test as user sail
- DB_HOST=mysql resolves only in containers
- container PHP, not the host's
- unknown commands go to Compose
basics
~20 sSail's commands run inside the laravel.test container, where the pinned PHP runs and hosts like mysql resolve. Host php artisan migrate uses the host's PHP and cannot reach DB_HOST=mysql; sail composer, npm and test do the same in the container.
solid answer
~50 sThe `sail` script turns `sail artisan migrate` into `docker compose exec -u sail laravel.test php artisan migrate`: the command runs **inside the application container** as the `sail` user. That matters for two reasons. After `sail:install`, `.env` says `DB_HOST=mysql` and `REDIS_HOST=redis` — names that only resolve on the Compose network, so the host's `php artisan migrate` fails to connect. And the container's PHP version and extensions are the ones the project pinned, not whatever the host has. The same pattern covers `sail composer install` (Composer with the container's PHP, so platform checks match), `sail npm run dev` and `sail node`, `sail test` (which runs `php artisan test`, so tests use the `testing` database Sail created), plus `sail tinker`, `sail shell` and `sail mysql`. Unknown subcommands such as `up` or `logs` go straight to `docker compose`. With the containers stopped, exec commands exit with 'Sail is not running.' A shell alias saves typing `./vendor/bin/sail`.
go deeper
Recall that on a Sail project you prefix artisan, composer, npm and test with sail, and why: they must run inside the container.
Explain the mapping to docker compose exec on laravel.test as the sail user, the service-name hosts in .env, and the pass-through of unknown commands.
Show how running tools in the container keeps Composer resolution, tests and migrations consistent across the team, and how to handle fresh clones and CI.
Weigh the friction of prefixing every command against the consistency it buys, and decide how CI should mirror or diverge from the local Sail environment.
## What the `sail` script does `./vendor/bin/sail` is a bash script that translates short commands into Docker Compose calls. Before doing anything it loads the project's `.env` (so variables such as `APP_PORT` are available), checks that Docker is running, and checks whether the Sail containers are up. Then it maps the first word you typed: | You type | Sail runs, roughly | |---|---| | `sail artisan migrate` (also `sail art`, `sail a`) | `docker compose exec -u sail laravel.test php artisan migrate` | | `sail php -v` | `... exec -u sail laravel.test php -v` | | `sail composer require laravel/sanctum` | `... exec -u sail laravel.test composer require laravel/sanctum` | | `sail npm run dev`, `sail node`, `sail npx`, `sail pnpm`, `sail yarn`, `sail bun` | the same Node tooling inside the container | | `sail test --filter=Invoice` | `... exec -u sail laravel.test php artisan test --filter=Invoice` | | `sail pest`, `sail phpunit`, `sail pint`, `sail tinker` | the matching tool in the container | | `sail shell` / `sail root-shell` | a Bash session as `sail` or as root | | `sail mysql`, `sail psql`, `sail redis` | a client connected to that service's container | | `sail up -d`, `sail ps`, `sail logs`, `sail build` | passed straight to `docker compose` | The application service is `laravel.test` and the user is `sail` unless `APP_SERVICE` or `APP_USER` say otherwise. ## Why not just `php artisan migrate`? 1. **Host names.** `sail:install` rewrites `.env` so `DB_HOST=mysql`, `REDIS_HOST=redis`, `MAIL_HOST=mailpit`. Those are Compose service names; they resolve inside the containers, not on your laptop. Host-side `php artisan migrate` reads the same `.env` and fails to connect. 2. **The PHP runtime.** The container runs the PHP version and extensions the project chose. Host PHP may be older, newer or missing an extension, so the same command can behave differently. 3. **Composer's platform checks.** `sail composer install` resolves and installs dependencies with the container's PHP, so the lock file and `vendor/` match the runtime that will execute them. 4. **Consistency.** Every developer's command runs in an identical environment, which is the point of using Sail at all. The Laravel documentation writes commands as `php artisan ...`, `composer ...` and `npm ...`; on a Sail project, prefix them with `sail`. ## Tests and the `testing` database `sail test` is `sail artisan test`, so it accepts the runner's usual options. On first start, the MySQL, MariaDB and PostgreSQL containers create a second database called `testing`, and `sail:install` points `phpunit.xml`'s `DB_DATABASE` at it — tests therefore do not wipe your development data. ## When Sail is not running Exec-style commands (`artisan`, `composer`, `npm`, `test`, `tinker`, `shell` …) need the containers up. If they are not, the script prints `Sail is not running.` and suggests `sail up` or `sail up -d`, exiting with an error. Pass-through commands like `sail up` obviously work regardless. ## The alias Typing `./vendor/bin/sail` gets old quickly. The documented alias, added to `~/.zshrc` or `~/.bashrc`, is: ```bash alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)' ``` It prefers a `sail` script in the project root — which exists if you published it — and otherwise uses `vendor/bin/sail`. ## Why interviewers ask The question is a quick check that a candidate knows where their code actually runs. People who have only followed a Sail tutorial often type `php artisan` out of habit, hit a connection error for `mysql`, and start editing `.env` back to `127.0.0.1` — which then breaks the application inside the container. A strong answer explains the exec mapping, the service-name hosts and the pinned runtime, and so shows the candidate can reason about the environment rather than memorise a prefix. ## A few practical notes - The first `sail composer install` on a fresh clone needs `vendor/bin/sail` to exist, which needs Composer — a chicken-and-egg problem teams solve with a one-off `docker run` of a Composer image or a host Composer run. - Input from a pipe works: the script adds Compose's no-TTY flag when stdin is not a terminal, so `cat dump.sql | sail mysql` behaves. - `sail debug migrate` runs an Artisan command with Xdebug triggered, for step debugging a command.
- Why does `sail composer install` matter more than running Composer on the host when the host has a different PHP version?Composer checks each package's PHP and extension requirements against the PHP running it. On the host it checks the host's PHP, so it may choose versions that need a newer PHP than the container has, or refuse ones the container supports. `sail composer` runs Composer with the container's PHP, so `composer.lock` and `vendor/` match the runtime that executes the app.
- What happens when you run `sail up -d` versus `sail artisan migrate` while the containers are stopped?`up` is not a Sail-defined command, so the script passes it to `docker compose`, which starts the containers. `artisan` needs to exec into the running `laravel.test` container; with nothing running, the script prints 'Sail is not running.', suggests `sail up`, and exits with an error.
saying these in an interview costs you the question
- sail artisan and php artisan are interchangeable because both read the same .env.
- DB_HOST=mysql works from the host because Docker adds it to the host's DNS.
- sail test runs tests against the development database by default.
- Sail's commands run on the host and only the database runs in Docker.
- sail up only accepts Sail's own flags, not Docker Compose options.