
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Setting up a reliable PHP environment on your host machine is a recurring source of friction, especially when juggling multiple projects with conflicting extension or version requirements. Local Laravel dev with Sail and Docker solves this by providing a lightweight, reproducible containerized workflow that isolates dependencies while maintaining native-like performance. This approach eliminates "works on my machine" errors and aligns your development stack directly with production infrastructure.
sail CLI wrapper instead of local binaries, ensuring your development environment matches production exactly without installing PHP or Node.js on your host system.How does local Laravel dev with Sail and Docker differ from traditional setups?
Traditional local development requires installing PHP, Composer, Nginx, MySQL, and Redis directly on your operating system. This creates version drift between projects and makes onboarding new team members slow and error-prone. When you adopt Docker containerization fundamentals, these dependencies move into disposable containers defined as code.
Laravel Sail is not just another Docker wrapper; it is an opinionated abstraction layer designed specifically for Laravel's ecosystem. Unlike writing raw Dockerfiles from scratch, Sail provides sensible defaults for PHP-FPM, Nginx, PostgreSQL, MySQL, Redis, Meilisearch, and Mailpit. It handles volume mounting, user permission mapping (crucial on Linux), and service networking automatically.
The key distinction is ergonomics. Raw Docker Compose requires verbose docker compose exec app php artisan migrate commands. Sail reduces this to sail artisan migrate. More importantly, Sail manages UID/GID mapping so files created inside the container are owned by your host user, preventing the permission nightmares common in DIY Docker setups on Linux hosts.
How do you configure and customize Sail services for your project?
Sail’s default configuration works for most applications, but real projects often need specific extensions, additional system packages, or modified PHP settings. Customization happens in two places: the docker-compose.yml file for service topology, and the published Dockerfile for runtime dependencies.
Publishing and modifying the Dockerfile
Never edit the base image directly. Publish Sail’s Docker assets to your repository so changes are version-controlled and reproducible across your team:
php artisan sail:publish This creates a docker directory containing separate Dockerfiles for each supported PHP version. Edit docker/8.4/Dockerfile to add GD libraries, ImageMagick, or custom PECL extensions. For example, adding PDF generation support:
RUN apt-get update && apt-get install -y \
libmagickwand-dev \
poppler-utils \
&& pecl install imagick \
&& docker-php-ext-enable imagick \
&& apt-get clean && rm -rf /var/lib/apt/lists/* After modification, rebuild without cache to ensure consistency:
sail build --no-cache Adjusting service versions and ports
Your docker-compose.yml defines which database engine, cache driver, and search backend run locally. A common mistake is leaving default port mappings that conflict with other projects. Always namespace ports per project or use Docker’s internal DNS:
- Database: Change
5432:5432to5433:5432if running multiple Postgres instances - Mailpit: Access at
localhost:8025for email testing without external SMTP - Meilisearch: Expose
7700only if debugging directly; otherwise rely on internal networking
For teams working on API-first Laravel applications, consider adding MinIO as an S3-compatible storage backend locally. Add this service block to your compose file:
minio:
image: minio/minio:latest
ports:
- '9000:9000'
- '9001:9001'
environment:
MINIO_ROOT_USER: sail
MINIO_ROOT_PASSWORD: password
volumes:
- 'sail-minio:/data'
command: server /data --console-address ":9001" What are the essential daily workflows and commands in Sail?
Once configured, your entire development workflow flows through the sail binary. Treat it as a drop-in replacement for php, composer, npm, and artisan. The mental model shift is critical: nothing runs on your host except Docker itself.
- Starting services:
sail up -dlaunches containers in detached mode. Usesail logs -fto tail output when debugging startup issues. - Running migrations:
sail artisan migrate:fresh --seedresets your database. Never runphp artisandirectly unless you have identical PHP/extensions locally. - Installing dependencies:
sail composer require spatie/laravel-permissionensures lock files reflect container architecture, not your host OS. - Frontend builds:
sail npm run devstarts Vite with correct HMR bindings. Sail auto-configuresVITE_HOST=0.0.0.0so hot reload works across container boundaries. - Testing:
sail testruns PHPUnit/Pest in isolation. Parallel testing withsail test --parallelleverages container resources without affecting host processes.
A frequent pain point is shell access for debugging. Use sail shell to get an interactive bash session inside the app container with proper environment variables loaded. Avoid docker exec -it manually unless you understand why environment context matters for Laravel’s config caching.
How does Sail compare to Valet, Homestead, and raw Docker Compose?
Choosing the right tool depends on team size, deployment target, and tolerance for configuration overhead. Each option has legitimate trade-offs.
| Criteria | Laravel Sail | Laravel Valet | Raw Docker Compose |
|---|---|---|---|
| Setup Time | < 5 minutes (new project) | ~15 minutes (global install) | 2–4 hours (custom Dockerfiles) |
| Production Parity | High (matches deploy targets) | Low (macOS-only, no containers) | Exact (you define everything) |
| Cross-Platform | Linux, macOS, Windows (WSL2) | macOS only (Linux fork exists) | All platforms with Docker |
| Resource Usage | Moderate (~1–2 GB RAM) | Minimal (native processes) | Variable (depends on optimization) |
| Team Onboarding | Single command (sail up) | Per-developer setup required | Documentation-dependent |
| Customization Ceiling | Moderate (published Dockerfiles) | Low (global PHP version) | Unlimited |
Valet remains excellent for solo developers on macOS who prioritize speed over parity. Raw Docker Compose suits teams building complex multi-service architectures where Sail’s opinions become constraints. Sail occupies the sweet spot for most Laravel teams: enough abstraction to move fast, enough control to handle real-world requirements.
In practice, I recommend Sail as the default for any team deploying to containerized production (ECS, EKS, Cloud Run). The cognitive load saved on environment debugging compounds significantly over months of development. Reserve raw Compose for polyglot stacks where Laravel is one component among many non-PHP services requiring bespoke configurations.
How do you troubleshoot common Sail performance and permission issues?
Performance degradation usually stems from filesystem I/O between host and container. On macOS and Windows, bind mounts traverse a network protocol (gRPC/FUSE), making operations like composer install or large test suites painfully slow. Mitigation strategies include:
- Enable VirtioFS (macOS): In Docker Desktop settings, switch from gRPC to VirtioFS for 3–5x I/O improvement
- Use WSL2 backend (Windows): Never run Sail on NTFS; clone repos inside the WSL2 filesystem (
/home/user/projects) - Exclude vendor/node_modules from sync: Add them to
.dockerignoreand install inside the container only - Leverage BuildKit caching: Sail enables this by default; verify with
DOCKER_BUILDKIT=1 sail build
Permission errors ("Operation not permitted" or root-owned files) indicate UID mapping failure. Sail detects your host UID automatically on Linux, but edge cases occur when switching users or running CI locally. Force correct ownership:
export WWWUSER=$(id -u)
export WWWGROUP=$(id -g)
sail up -d If containers fail to start after upgrades, stale volumes are often the culprit. Nuclear reset (safe for local dev with seeded databases):
sail down -v
sail build --no-cache
sail up -d
sail artisan migrate:fresh --seed For persistent issues, inspect container health directly: sail ps shows status, sail logs app reveals PHP-FPM crashes, and sail exec app php -m confirms loaded extensions match expectations. Debugging containerized environments requires treating the container as the source of truth, not your host assumptions.
Building Consistent Environments That Scale Beyond Localhost
Adopting local Laravel dev with Sail and Docker is fundamentally about reducing variance between development and production. Every hour spent debugging environment-specific bugs is an hour lost building features. Sail’s conventions give you that time back while establishing patterns that transfer directly to CI pipelines and cloud deployments.
Start new projects with Sail from day one rather than retrofitting later. Publish Dockerfiles early, document service dependencies in your README, and treat your local environment as infrastructure-as-code. When your team grows or you onboard contractors, they should be productive within fifteen minutes of cloning the repository. If your setup achieves that, you’ve solved the hardest part of collaborative PHP development.
Ready to extend this foundation beyond localhost? Explore building CI/CD pipelines with GitLab CI for Laravel to carry these same containerized patterns into automated testing and deployment, or review hosting Laravel on AWS EC2 with RDS and S3 when your application graduates to production infrastructure. Need help architecting your team’s development workflow or auditing existing setups? Get in touch to discuss your specific requirements.