Local Laravel Dev with Sail and Docker

Khimananda Oli 8 min read DevOps
Local Laravel Dev with Sail and Docker

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.

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.

Host MachineNo PHP/Node InstalledOnly Docker EngineApp ContainerPHP 8.4 + FPMComposer / NodeDatabasePostgreSQL / MySQLCache & QueueRedis / MailpitShared Volume./app:/var/wwwLive Code Syncsail artisanTCP :5432
Sail routes all commands through the app container while syncing code via shared volumes for instant feedback during local Laravel dev with Sail and Docker.

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:5432 to 5433:5432 if running multiple Postgres instances
  • Mailpit: Access at localhost:8025 for email testing without external SMTP
  • Meilisearch: Expose 7700 only 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.

  1. Starting services: sail up -d launches containers in detached mode. Use sail logs -f to tail output when debugging startup issues.
  2. Running migrations: sail artisan migrate:fresh --seed resets your database. Never run php artisan directly unless you have identical PHP/extensions locally.
  3. Installing dependencies: sail composer require spatie/laravel-permission ensures lock files reflect container architecture, not your host OS.
  4. Frontend builds: sail npm run dev starts Vite with correct HMR bindings. Sail auto-configures VITE_HOST=0.0.0.0 so hot reload works across container boundaries.
  5. Testing: sail test runs PHPUnit/Pest in isolation. Parallel testing with sail test --parallel leverages container resources without affecting host processes.
DeveloperTerminalSail CLI./vendor/bin/sailAlias ResolutionUID MappingPHP-FPMArtisan / ComposerNode.jsVite / npmPostgreSQLMigrationsVolumeCode Files
Command routing in local Laravel dev with Sail and Docker: the CLI resolves aliases and maps permissions before dispatching to specialized containers.

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.

CriteriaLaravel SailLaravel ValetRaw Docker Compose
Setup Time< 5 minutes (new project)~15 minutes (global install)2–4 hours (custom Dockerfiles)
Production ParityHigh (matches deploy targets)Low (macOS-only, no containers)Exact (you define everything)
Cross-PlatformLinux, macOS, Windows (WSL2)macOS only (Linux fork exists)All platforms with Docker
Resource UsageModerate (~1–2 GB RAM)Minimal (native processes)Variable (depends on optimization)
Team OnboardingSingle command (sail up)Per-developer setup requiredDocumentation-dependent
Customization CeilingModerate (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 .dockerignore and 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.

Start: Choose ToolProject RequirementsSolo + macOS?Speed > ParityTeam + Containers?Parity > SpeedPolyglot Stack?Non-PHP ServicesUse ValetUse SailRaw Compose
Decision framework for selecting local Laravel dev with Sail and Docker versus alternatives based on team composition and infrastructure targets.

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.

Frequently Asked Questions

Sail is a lightweight Docker wrapper included with Laravel that simplifies container management. It provides preconfigured PHP, Nginx, MySQL, and Redis services without requiring deep Docker knowledge, ensuring consistent local environments across teams in 2026.

Run composer require laravel/sail --dev then php artisan sail:install. Select your desired services when prompted. This publishes the docker-compose.yml file and configures the sail alias for managing containers via simple commands.

No. Sail requires WSL2 on Windows because native Docker Desktop performance is insufficient. Install WSL2 first, then run all Sail commands inside the Linux distribution terminal for acceptable filesystem speeds and proper socket mounting.

Yes. Publish the Dockerfile using php artisan sail:publish, edit the runtime image configuration to add extensions like gd or bcmath, rebuild with sail build --no-cache, and restart containers to apply changes to your local stack.

Docker introduces filesystem overhead through volume mounts. Mitigate this by enabling Mutagen sync in sail up -d, which caches files locally and syncs asynchronously, reducing I/O latency significantly during development in 2026.

Configure Xdebug in docker/8.4/php.ini, set client_host=host.docker.internal, and map port 9003. Enable debugging via SAIL_XDEBUG_CONFIG environment variable and configure your IDE path mappings to match container paths correctly.

Yes. Sail is open-source MIT-licensed software bundled with Laravel. There are no licensing fees, subscription costs, or usage restrictions for personal, commercial, or enterprise local development environments as of 2026.

Sail offers tighter Laravel integration with zero-config defaults. DevContainers provide editor portability but require more setup. DDEV supports multiple frameworks but lacks Sail’s artisan command shortcuts and built-in service toggling for Laravel-specific workflows.

Yes. Each project uses isolated Docker networks and unique container names derived from the APP_NAME variable. Ensure ports don’t conflict by customizing forwarded ports in docker-compose.yml before starting additional instances with sail up.

Run composer update laravel/sail followed by php artisan sail:publish to refresh Dockerfiles. Rebuild containers with sail build --no-cache to apply base image updates, security patches, and new PHP version support available in 2026.

Volume mount ownership mismatches cause this. Fix by running sail root-shell then chown -R sail:sail /var/www/html inside the container, or configure user mapping in docker-compose.yml to match your host UID/GID values.

Yes. During sail:install select PostgreSQL, or manually edit docker-compose.yml to replace the mysql service with postgres:16-alpine. Update DB_CONNECTION and credentials in .env accordingly, then rebuild and migrate your database schema.

Sail isolates services in containers but stores secrets in plaintext .env files. Never commit these files. Use Sail only for development; production deployments require dedicated secret managers like Vault or AWS Secrets Manager.

Data persists in named Docker volumes unless you specify -v flag. Running sail down -v destroys databases and storage permanently. Always backup critical data before removing volumes or pruning unused Docker resources.

Yes. Sail depends on Docker Engine for container orchestration. On macOS and Windows, Docker Desktop is required. Linux users can install Docker CE directly without Desktop, reducing resource overhead while maintaining full Sail compatibility in 2026.