An advanced guide to Laravel Sail

By Cole Shirley

Laravel Sail is a great tool for developing Laravel applications without having to set up services on your local machine. It also helps you avoid issues in your application caused by differences between your development and production environments.

Sail is NOT for production use

A very common question in various Laravel discussion boards is how a developer can set up their Sail application on a production server. Sail is not meant to be used in a production environment. It is strictly a development tool. There are many good reasons why you shouldn't use Sail in a production environment but they usually boil down to these three reasons:

  • The sail containers are bloated. Because Sail needs to support every service generally supported by Laravel, the containers include the PHP extensions for MariaDB, MySQL, PostgreSQL, and MongoDB. Very few projects I've worked on have used all of these database engines at the same time. This makes the containers (relatively) slow compared to what you can set up for a production environment.

  • The Sail containers run on the PHP development server. This means that only a single HTTP request can be processed at a time. A production application hopefully gets more traffic than that.

  • Security. Being a development container, there is very little hardening done to prevent a malicious actor from gaining root access to your container (and by extension your application, database, etc).

Sail Basics

If you don't have PHP installed locally (and you don't want to install it) please read #link-to-i-cant-install-section. Eventually I would recommend installing PHP locally for convenience as you develop your application.

Here are the essential commands for your project:

Installing

php artisan sail:install # link to installing docs
 
php artisan sail:add # link to adding docs
 
php artisan sail:publish # publish the sail files for customization

Adding a shell alias

alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'

"Hidden" features of Laravel Sail

Container port forwarding

For most Sail services there are FORWARD_* environment variables you can define to modify the local ports your containers are bound to. This is useful if you have services already bound to common ports like 80, 3306, 1025, 8025, etc.

Advanced Environment Variables

There are also a number of environment variables you can set:

SUPERVISOR_PHP_COMMAND # allows you to customize the start container command (used when adding laravel octane to sail)
 
SUPERVISOR_PHP_USER # allows you to customize the laravel.test container supervisor user
 
APP_SERVICE # allows you to change the name of the laravel.test container in the docker-compose.yml file
 
# to match the identifier of the local user (this keeps file permissions properly matched between the local machine and the container)
 
WWWUSER # sets the user identifier of the sail user
 
WWWGROUP # sets the group identifier of the sail user
 
SAIL_FILES # allows you to define additional docker-compose files to be merged into the main configuration

The "Sail paradox"

Since Sail allows you to develop Laravel applications in containerized environments, you don't actually need PHP or Composer installed locally on your development machine. However, many developers just getting started with Sail run into this scenario:

1. Clone or create a Laravel project with Sail set up

2. Run sail up (or vendor/bin/sail up if you have not configured a shell alias)

3. You get the error: sh: 0: cannot open vendor/bin/sail: No such file

The sail script is only available after the project's Composer dependencies have been installed. Composer requires PHP to run. Neither tool is installed locally because you want to run your Laravel project in a container. You can't run your Laravel project in a container without starting Sail. The sail script is only available after the project's Composer dependencies have been installed. Composer requires PHP to run. Neither tool is installed ........... error: Xdebug has detected a possible infinite loop, and aborted your script.

Luckily the Laravel team has provided a solution for this problem so that you can install your Composer dependencies without having to install PHP and composer locally with this command:

docker run --rm \
 
-u "$(id -u):$(id -g)" \
 
-v "$(pwd):/var/www/html" \
 
-w /var/www/html \
 
laravelsail/php84-composer:latest \
 
composer install --ignore-platform-reqs

You can also replace laravelsail/phpXX-composer with any PHP version you'd like (81, 82, 83, etc).

Aside: If you are working with enough Laravel applications, I do recommend eventually installing PHP and Composer locally. With Docker and Sail, it's not required, but it does make certain aspects of your development experience smoother.

Advanced Customization

Adding a docker-compose.override.yml file

There are situations where you may want to modify your personal Sail configuration without changing the configuration for the main project. To do this you can utilize the merge features of Docker compose files.

By default, Docker will look for a docker-compose.override.yml in the same directory as the docker-compose.yml file and merge it into your configuration. If you add this file to the project’s .gitignore file, you can modify your local Sail configuration however you like without any of your changes being added to source control.

Two YAML tags that are very important to understand when merging compose files are the !override and !reset tags. The !override tag allows you to replace an attribute in the compose file and the !reset tag can be used to remove an attribute.

For example, if the application you are working on includes the mailpit service, and you don't want to utilize it locally, you can add the following to your docker-compose.override.yml to disable it:

services:
 
laravel.test:
 
depends_on: !override # removes all dependencies of the laravel.test container except mysql
 
- mysql
 
mailpit: !reset [] # disables the mailpit service

Running multiple sail applications concurrently

If you would like to be able to run multiple Sail applications at the same time and have traffic routed through different local domains, you can set up a primary webserver that listens on ports 80 and 443 and reverse proxies the request to your sail applications on different domains.

If you set up Traefik with the Docker provider enabled, this is very easy to use with Laravel Sail. First, create a empty directory called traefik on your local machine and add the following to a docker-compose.yml file in that directory:

Traefik Docker Compose file config:

name: traefik
 
services:
 
traefik:
 
image: traefik:3
 
container_name: "traefik"
 
command:
 
- "--providers.docker=true"
 
- "--providers.docker.exposedbydefault=false"
 
- "--entrypoints.web.address=:80"
 
- "--entrypoints.websecure.address=:443"
 
ports:
 
- "80:80"
 
- "443:443"
 
- "8080:8080"
 
volumes:
 
- "/var/run/docker.sock:/var/run/docker.sock:ro"
 
restart: unless-stopped
 
networks:
 
- traefik
 
networks:
 
traefik:
 
external: true

Before starting this Traefik container, make sure you create the Traefik Docker network by running docker network create traefik

Next, run docker compose up -d to start this container. This container will always turn on when Docker does. If the container does not automatically start for some reason, you can always run docker compose up -d again from your traefik directory.

In your Sail project, create a docker-compose.override.yml with the following content:

services:
 
laravel.test:
 
networks:
 
- traefik
 
ports: !override
 
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
 
labels:
 
- "traefik.enable=true"
 
- "traefik.http.routers.{site}-laravel-test.rule=Host{domain}.localhost)"
 
- "traefik.http.routers.{site}-laravel-test.entrypoints=web"
 
- "traefik.http.services.{site}-laravel-test.loadbalancer.server.port=80"
 
- "traefik.docker.network=traefik"
 
networks:
 
traefik:
 
external: true

- Replace {site} with a unique string identifier for this project (such as myapp). This identifier should not be the same as any other identifier on your local machine.

- Replace {domain} with whatever you'd like your local domain to be.

By overriding the ports in your docker-compose.override.yml file, you make it so that your application does not collide with the Traefik ports and you make it so that the Sail application is now only accessible through the Traefik reverse proxy.

After you have configured your override file start your containers with sail up -d.

If you replaced {domain} with myapp then your application should now be accessible at http://myapp.localhost.

Cole Shirley
Software Developer
Author Image

Interested in speaking with a developer?

Connect with us.
©2025 Kirschbaum Development Group LLC Privacy Policy Terms of Service