Laravel Sail: full local-stack guide
Laravel Sail is a Docker Compose wrapper around a typical Laravel app: PHP-FPM (the laravel.test service), database, Redis, Meilisearch, Selenium, and optional extras. It is aimed at local development (and CI that can run Compose)ânot a drop-in production topology. This guide walks through common customizations and clarifies where Sail ends and real deployment begins.
In this series: Databases & Docker services ¡ Queues & workers ¡ Environments & deployment ¡ Troubleshooting ¡ All tools
Table of contents
- What Sail is (and is not)
- Prerequisites and mental model
- Daily commands alias
- Change the PHP version
- Default services and
sail:installoptions - Add Redis when it is missing
- Add RabbitMQ and wire queues
- Switch from MySQL to PostgreSQL
- MongoDB (container + Laravel)
- Queues: connections, workers, and Sail
- Mail and debugging (Mailpit)
- Volumes, performance (WSL2 / macOS)
- Xdebug and debugging
- Customizing
docker-compose.yml - Environment files: local, Docker-only, teams
- Local vs dev/staging/production
- Troubleshooting checklist
What Sail is (and is not)
- Is: A published
docker-compose.yml+ Dockerfiles (undervendor/laravel/sail/runtimes/âŚ) plus the./vendor/bin/sailscript. Your project copies this into the repo when you runphp artisan sail:install(or install Sail with Laravel). - Is not: A hosting product. On a server you might still use Docker or Kubernetes, but you typically do not run the Sail script in productionâyou run images, orchestration, health checks, secrets managers, and supervised queue workers.
Treat Sail as one reproducible dev environment that mirrors some production choices (same PHP extensions, same DB engine) without being identical to prod networking or scale.
Prerequisites and mental model
- Docker Engine + Docker Compose v2 installed and running.
- WSL2 (Windows): store the project inside the Linux filesystem (
~/projects/...), notC:\..., for acceptable I/O; bind mounts from NTFS are slow. - Sail runs commands inside containers. Host PHP is optional; most teams use only
./vendor/bin/sail artisan âŚandsail composer âŚ.
Daily commands alias
Add to your shell profile:
alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'
Then: sail up -d, sail artisan migrate, sail npm run dev, sail shell, sail down.
Change the PHP version
Sail images are built from published runtime Dockerfiles. Typical flow:
-
Publish Sail assets (if you have not customized yet):
sail artisan sail:publishThis adds
docker-compose.yml(if not present) and aDockerfileundervendor/laravel/sailis referenced from composeâafter publish you often get adocker/(or runtime) path in your project; follow what your Laravel/Sail version generates. -
In
docker-compose.yml, find thelaravel.testservicebuild.args(e.g.PHP_VERSION=8.3). Set the desired minor (e.g.8.4) that Sail supports for your Laravel version. -
Rebuild without cache so the base image layer updates:
sail build --no-cache sail up -d -
Confirm:
sail php -v
If you maintain a custom Dockerfile, change the FROM line to the matching laravel/sail-php/x.y image (check laravel/sail on GitHub for current tags). After edits, always build --no-cache to avoid stale layers.
Default services and sail:install options
When installing Sail, you choose services, e.g.:
php artisan sail:install --with=mysql,redis,meilisearch,mailpit,selenium
mysql/pgsql/mariadb: primary SQL database container.redis: cache, session, queue backend.memcached: alternative cache session store.meilisearch,typesense,soketi, etc.: optional stacks.
If you skipped Redis but your .env still has REDIS_HOST=redis, either add the service (next section) or point REDIS_HOST to 127.0.0.1 and run Redis on the host (not recommended for parity).
Add Redis when it is missing
- Edit
docker-compose.yml: add a service (names may vary; align with Laravel docs for your Sail version):
redis:
image: 'redis:alpine'
ports:
- '${FORWARD_REDIS_PORT:-6379}:6379'
volumes:
- 'sail-redis:/data'
networks:
- sail
healthcheck:
test: ["CMD", "redis-cli", "ping"]
-
Add the volume
sail-redisundervolumes:at the bottom of the file. -
On
laravel.test, ensuredepends_onincludesredisif you want startup ordering. -
In
.env(inside the app container, use service names as hosts):
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
sail up -dand test:sail exec redis redis-cli ping.
Add RabbitMQ and wire queues
Laravelâs core queue drivers include database, redis, beanstalkd, sqs, etc. AMQP/RabbitMQ is not enabled out of the box; you add a community package (e.g. one that provides a rabbitmq queue driver) and a RabbitMQ container.
1. Compose service (example):
rabbitmq:
image: 'rabbitmq:3-management-alpine'
hostname: rabbitmq
ports:
- '${FORWARD_RABBITMQ_PORT:-5672}:5672'
- '${FORWARD_RABBITMQ_MANAGEMENT_PORT:-15672}:15672'
volumes:
- 'sail-rabbitmq:/var/lib/rabbitmq'
networks:
- sail
environment:
RABBITMQ_DEFAULT_USER: '${RABBITMQ_USER:-sail}'
RABBITMQ_DEFAULT_PASS: '${RABBITMQ_PASSWORD:-password}'
-
Declare volume
sail-rabbitmq. -
.env(example keysâmatch your packageâs docs):
RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672
RABBITMQ_USER=sail
RABBITMQ_PASSWORD=password
RABBITMQ_VHOST=/
QUEUE_CONNECTION=rabbitmq
-
Install and configure the queue driver package; publish its config; run
sail artisan config:clear. -
Run a worker inside Sail:
sail artisan queue:work rabbitmq
Use the management UI on port 15672 to inspect queues and dead letters while developing.
Switch from MySQL to PostgreSQL
1. Replace the DB service in docker-compose.yml: remove or disable mysql, add pgsql using the template from a fresh sail:install --with=pgsql project or Sailâs default compose fragments.
2. laravel.test depends_on: point to pgsql instead of mysql.
3. .env:
DB_CONNECTION=pgsql
DB_HOST=pgsql
DB_PORT=5432
DB_DATABASE=laravel
DB_USERNAME=sail
DB_PASSWORD=password
4. Remove MySQL-specific session or test config if any.
5. Rebuild / up: sail down -v (â ď¸ destroys volumes) or migrate data properly; then sail up -d, sail artisan migrate:fresh for a clean dev DB.
6. PHP extension: Sailâs PostgreSQL runtime already includes pdo_pgsql. If you use custom Dockerfiles, ensure the extension is installed.
MongoDB (container + Laravel)
1. Add MongoDB to docker-compose.yml (official mongo image, persistent volume, port forward if you want Compass from host).
2. Laravel usually uses mongodb/laravel-mongodb (or similar maintained package)ânot core Eloquent drivers. Follow that packageâs Sail/Docker notes: you often must install the MongoDB PHP extension in the Sail Dockerfile (pecl install mongodb + docker-php-ext-enable mongodb) and rebuild.
3. .env: connection string or DB_HOST/DB_PORT style per packageâuse hostname mongo (service name) from laravel.test.
4. Queues / transactions: MongoDB behaves differently from SQL; migrations are not Schema in the same wayâread the package docs for indexes, replicas, and testing (in-memory Mongo or Docker service in CI).
Queues: connections, workers, and Sail
-
QUEUE_CONNECTION=syncruns jobs inlineâgood for debugging, bad for realistic async behavior. -
database/redis: start a worker in the app container:sail artisan queue:work --tries=3For multiple queues:
queue:work redis --queue=high,default. -
Horizon (Redis):
sail artisan horizonin dev; in production use supervisor or a managed worker. -
Restart after code changes: workers cache bootstrapped code; use
queue:restartor run workers with--max-jobs=1during heavy TDD (or restart container). -
RabbitMQ: see Add RabbitMQ; failure modes (NACK, TTL, DLX) differ from Redis listsâtest retry and timeout explicitly.
Mail and debugging (Mailpit)
Sail often ships Mailpit (or Mailhog in older setups). Point SMTP to the mailpit host and appropriate ports from docker-compose.yml (MAIL_HOST, MAIL_PORT, etc.). Open the web UI on the forwarded port to read outbound mail without sending real email.
Volumes, performance (WSL2 / macOS)
- Bind mounts of the whole project can be slow on macOS and on WSL2 when files live on Windows drives. Prefer Linux filesystem for the repo on WSL2.
- Named volumes for
mysql,redis, etc. preserve data acrosssail down;sail down -vdeletes themâuse consciously. - Optional delegated/cached mount flags are platform-specific; search current Docker docs for your OS.
Xdebug and debugging
Sailâs PHP images support Xdebug toggled via env (see Sail README for your version), e.g. SAIL_XDEBUG_MODE=debug,develop. Configure your IDE to listen on the correct port and map server path to host path. For step debugging of queue workers, attach to the same container that runs queue:work.
Customizing docker-compose.yml
- Keep service names stableâyour
.envuses them as hostnames. - Prefer environment variables in compose referencing
.env(${VAR}) so teammates do not commit secrets. - For one-off tools (Adminer, ngrok sidecar), add services on the same
sailnetwork solaravel.testcan reach them by name. - After structural changes:
sail build && sail up -d.
Environment files: local, Docker-only, teams
.env: local secrets; never commit. Often gitignored with.env.examplecommitted..env.example: safe defaults and documentation for required keys (DB, Redis, queue, mail).- Docker-only values: some teams use
.env.dockerloaded viadocker-composeenv_file:âkeep overlap clear to avoid âworks in Sail, fails on hostâ confusion. - CI: inject env in the pipeline; same
docker composefile can run tests with a test.env.testingandAPP_ENV=testing. - Per-developer overrides: optional
.env.local(if your bootstrap loads it) or shell exports for portsFORWARD_*to avoid clashes when multiple projects run Sail.
Forward ports: FORWARD_DB_PORT, FORWARD_REDIS_PORT, etc. in .env prevent collisions when many stacks run on one machine.
Local vs dev/staging/production
| Topic | Sail (local) | Typical server |
|--------|----------------|----------------|
| Process model | sail up, ad-hoc artisan | php-fpm + nginx, or Octane |
| Queues | Manual queue:work / Horizon in terminal | Supervisor, systemd, or cloud worker |
| SSL | HTTP on localhost | TLS termination, real certs |
| Secrets | .env file | Vault, parameter store, sealed secrets |
| Scale | Single container per service | Replicas, load balancers, managed Redis/DB |
| Mail | Mailpit | SMTP relay, SES, etc. |
Do not assume âit worked in Sailâ means production is configuredâespecially queue workers, scheduler (schedule:run cron), OPcache, and file storage (local storage/ vs S3).
Troubleshooting checklist
- Permission errors on
storage//bootstrap/cache/:sail artisan cache:clearand fix ownership (oftensail root-shell+chown -R sail:sail storage bootstrap/cache). - âConnection refusedâ to DB/Redis: wrong
DB_HOST/REDIS_HOST(must be service name, not127.0.0.1, from insidelaravel.test). - Stale containers after Dockerfile edits:
sail build --no-cache. - Port already allocated: change
FORWARD_*variables in.env. - Composer inside vs host: run
sail composer installso extensions and platform match the container.
Closing thoughts
Sail shines when the whole team shares one Compose file and the same PHP extensions and service topology. Invest once in a clean docker-compose.yml, documented .env.example, and a short README for âfirst cloneâ steps (cp .env.example .env, sail up -d, sail artisan migrate). Keep production concernsâmonitoring, backups, queue supervision, secretsâseparate from Sail, and mirror only what you need for realistic local behavior.