
August 18, 2026
11 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Setting up local Laravel dev with Sail and Docker eliminates the most persistent source of bugs in PHP development: environment drift between your laptop and production server. Instead of installing PHP, MySQL, Redis, and Node directly on your host machine, Sail provides a lightweight Docker abstraction that runs everything in isolated containers while keeping your code editable locally. This approach gives you reproducible environments, instant service switching, and zero pollution of your host OS.
composer require laravel/sail --dev, publish configuration with php artisan sail:install, then start services using ./vendor/bin/sail up -d. All Artisan and Composer commands execute inside the container automatically.How do you set up local Laravel dev with Sail and Docker from scratch?
The initial setup for Laravel developers in Nepal and elsewhere takes under five minutes if you already have Docker Desktop or a compatible container runtime installed. Sail is not a separate virtualization layer; it is a convenience wrapper around standard Docker Compose that ships sensible defaults for Laravel applications.
Prerequisites and version requirements
Before starting, verify these minimum versions on your host machine:
- Docker Engine: 24.x or higher (Docker Desktop 4.28+ on macOS/Windows, or native Docker CE on Linux)
- PHP: 8.2 minimum on host only for the initial
composer create-project; Sail itself runs PHP 8.4 inside the container regardless of your host version - Laravel: 11.x or 12.x (Sail has been included by default since Laravel 8, but 2026 projects should target current majors)
- WSL2: Required on Windows 10/11 for acceptable filesystem performance; never use Sail with legacy Hyper-V backend
Step-by-step installation
- Create a new Laravel project using your host PHP:
composer create-project laravel/laravel my-app - Navigate into the project directory and install Sail as a dev dependency:
cd my-app composer require laravel/sail --dev - Publish the Sail configuration and choose your services interactively:
Select mysql, redis, mailpit, and selenium unless you have specific needs for PostgreSQL, Meilisearch, or Memcached.php artisan sail:install - Start all containers in detached mode:
The first run pulls base images (~1.2 GB total) and builds the application container. Subsequent starts take seconds../vendor/bin/sail up -d - Run migrations and seeders inside the container:
./vendor/bin/sail artisan migrate:fresh --seed - Access your application at
http://localhost(port 80) orhttp://localhost:8000depending on your published configuration.
A critical detail many tutorials omit: the sail binary is just a Bash script that forwards commands to docker compose exec. You can always bypass it and run raw Docker Compose commands when debugging. On a real client project where Sail's alias conflicted with an existing shell function, I simply added alias l='vendor/bin/sail' to the team's .bashrc template and documented both invocation methods.
How does Sail compare to Homestead, Valet, and native installs?
Choosing the right local environment matters for long-term maintainability. Each option has trade-offs that affect onboarding time, parity with production, and resource consumption.
| Criteria | Laravel Sail | Laravel Homestead | Laravel Valet | Native PHP Install |
|---|---|---|---|---|
| Setup Time | 5 minutes | 30–60 minutes | 10 minutes | Variable (OS-dependent) |
| Production Parity | High (containerized) | Medium (Vagrant VM) | Low (macOS-only, no DB) | Very Low |
| Cross-Platform | Linux, macOS, Windows (WSL2) | Linux, macOS, Windows | macOS only (Linux beta) | All platforms |
| Resource Usage | Moderate (~1.5 GB RAM) | Heavy (~3–4 GB RAM) | Minimal (~100 MB) | Minimal |
| Service Isolation | Full per-project | Shared across sites | None (global PHP) | None |
| Database Management | Bundled containers | Bundled in VM | External required | Manual install |
| CI/CD Reusability | Direct (same docker-compose) | Not reusable | Not reusable | Not reusable |
| Best For | Teams, production-matching | Legacy multi-version PHP | Solo macOS quick prototyping | Simple scripts, learning |
In practice, Sail wins for any project that will eventually deploy to a containerized or Linux production environment. Homestead still has niche value when you need PHP 7.4 alongside 8.4 for legacy maintenance, but new projects in 2026 should default to Sail. Valet remains excellent for rapid WordPress or static site work on macOS, but its lack of bundled databases makes it unsuitable for serious Laravel application development.
For teams hiring web developers in Nepal, Sail dramatically reduces onboarding friction. A new developer clones the repository, runs sail up, and has an identical environment within minutes — no wiki pages documenting Homebrew taps or PPA repositories that break six months later.
How do you customize Sail services and optimize performance?
The default docker-compose.yml generated by sail:install is a starting point, not a final configuration. Real projects require adjustments for database tuning, custom PHP extensions, Xdebug integration, and filesystem performance.
Adding custom PHP extensions
Sail's base image includes common extensions, but specialized packages like bcmath, gd, or intl may be missing. Create a custom Dockerfile at docker/8.4/Dockerfile:
FROM ubuntu:24.04
LABEL maintainer="Taylor Otwell"
ARG WWWGROUP
ARG NODE_VERSION=22
ARG POSTGRES_VERSION=17
WORKDIR /var/www/html
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
ENV SUPERVISOR_PHP_USER="sail"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update \
&& mkdir -p /etc/apt/keyrings \
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev dnsutils librsvg2-bin fswatch ffmpeg nano \
&& apt-get install -y php8.4-cli php8.4-dev php8.4-pgsql php8.4-sqlite3 php8.4-gd php8.4-curl php8.4-imap php8.4-mysql php8.4-mbstring php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap php8.4-intl php8.4-readline php8.4-ldap php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole php8.4-memcached php8.4-pcov php8.4-xdebug \
&& pecl install ds \
&& echo "extension=ds.so" > /etc/php/8.4/cli/conf.d/20-ds.ini \
&& apt-get -y autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Then update your docker-compose.yml to build from this custom file instead of pulling the upstream image:
services:
laravel.test:
build:
context: ./docker/8.4
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
# ... rest of service config unchanged Xdebug configuration for step debugging
Enable Xdebug by setting SAIL_XDEBUG_MODE=develop,debug in your .env file. For VS Code, add this launch configuration to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
} A common mistake on Linux hosts: forgetting to set SAIL_XDEBUG_CONFIG="client_host=host.docker.internal". Without this, Xdebug cannot connect back to your IDE because the container's localhost differs from the host's localhost. On WSL2, you may also need to add a firewall rule allowing port 9003.
Filesystem performance on macOS and Windows
Docker's bind mounts are notoriously slow on non-Linux hosts due to filesystem translation overhead. Mitigate this with VirtioFS (Docker Desktop 4.28+) or Mutagen for aggressive caching:
# In docker-compose.yml, replace standard volume mount:
volumes:
- '.:/var/www/html'
# With VirtioFS-enabled mount (Docker Desktop settings must enable VirtioFS first):
volumes:
- type: virtiofs
source: .
target: /var/www/html In my experience working on production Laravel applications with large vendor directories and node_modules, VirtioFS reduced page load times from ~2.5 seconds to ~400ms on macOS. The improvement is less dramatic on Linux hosts where native bind mounts already perform well.
What are the common troubleshooting issues with Sail and how do you fix them?
Even with mature tooling, Sail introduces failure modes that don't exist with native installs. These are the problems I encounter repeatedly when helping teams adopt containerized development.
Port conflicts with existing services
If port 80, 3306, or 6379 is already bound on your host, Sail fails silently or throws cryptic binding errors. Check active ports before starting:
# Find what's using port 80
sudo lsof -i :80
# Or change Sail's mapped ports in .env
APP_PORT=8000
FORWARD_DB_PORT=33060
FORWARD_REDIS_PORT=63790 Never modify ports directly in docker-compose.yml — they'll be overwritten next time you run sail:install. The .env variables are the supported override mechanism.
Permission errors on storage and bootstrap/cache
Container processes run as user sail (UID 1337 by default), which often mismatches your host UID. Fix ownership after cloning:
./vendor/bin/sail root chown -R sail:sail /var/www/html/storage /var/www/html/bootstrap/cache For permanent resolution, set WWWGROUP and WWWUSER in your .env to match your host IDs. Retrieve them with id -u and id -g, then rebuild: ./vendor/bin/sail build --no-cache.
Database connection refused during migrations
The MySQL container takes 10–20 seconds to initialize on first boot. If sail artisan migrate fails immediately, wait and retry. For CI pipelines or scripted setups, add a health check wait loop:
#!/bin/bash
until ./vendor/bin/sail mysqladmin ping -h mysql --silent; do
echo "Waiting for MySQL..."
sleep 2
done
./vendor/bin/sail artisan migrate:fresh --seed Stale containers after branch switching
When switching between branches with different service configurations (e.g., one branch uses PostgreSQL, another uses MySQL), orphaned containers persist. Always tear down completely:
./vendor/bin/sail down -v # Removes volumes too!
./vendor/bin/sail up -d The -v flag destroys database contents. Omit it if you want to preserve data, but understand that schema mismatches between branches will cause migration failures.
How do you integrate Sail with GitLab CI and production deployments?
One of Sail's strongest advantages over Valet or native installs is that your local docker-compose.yml can serve as the foundation for CI pipelines. The same container definitions that power your development environment can run tests, build assets, and validate migrations before deployment.
For teams using Deployer 7 or similar tools (as I do for multiple sister sites sharing infrastructure), the pattern is straightforward:
# .gitlab-ci.yml excerpt
test:
stage: test
image: docker:24.0
services:
- docker:24.0-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker compose -f docker-compose.yml up -d --wait
- docker compose exec -T laravel.test composer install --prefer-dist --no-interaction
script:
- docker compose exec -T laravel.test php artisan test --parallel
- docker compose exec -T laravel.test php artisan migrate:fresh --force
after_script:
- docker compose down -v This approach catches environment-specific bugs that unit tests alone miss: missing PHP extensions, misconfigured queue drivers, or database driver incompatibilities. When building Laravel APIs that depend on precise Redis or PostgreSQL configurations, CI parity with local development prevents an entire category of "works on my machine" failures.
For asset compilation, commit built artifacts rather than running Node inside production containers. Your CI pipeline should execute sail npm run build, copy the output to a release artifact, and let Deployer handle distribution. Production servers shouldn't need Node.js installed — this reduces attack surface and simplifies server provisioning.
Getting started with local Laravel dev with Sail and Docker
Adopting local Laravel dev with Sail and Docker pays dividends immediately through faster onboarding, fewer environment-related bugs, and closer parity with production infrastructure. Start with the default configuration, measure your actual pain points (usually filesystem performance or Xdebug connectivity), then customize incrementally. Avoid over-engineering your Docker setup before you've shipped real features.
If you're evaluating whether to migrate an existing team to Sail, or need help optimizing a containerized Laravel workflow that isn't performing, reach out to discuss your specific setup. I regularly help teams transition from native installs or Homestead to Sail-based workflows that actually improve developer velocity rather than adding operational overhead.

