php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Getting Up And Running With Laravel Sail

Posted by on February 14, 2024

Docker is one of the best ways to have a consistent environment across multiple computers, but for new developers (and even not-so-new developers), it can be hard to understand and set up. Thankfully, the Laravel team has created Laravel Sail to make Docker a much easier tool to use when we’re working on Laravel projects.

In this article, we’ll discuss what Laravel Sail is, how to install and configure it, and then go through some common tasks.

What is Laravel Sail?

The Laravel Sail library provides a lightweight command-line interface for interacting with Docker development environments in a Laravel project. Sail provides a starting point for developing a Laravel application using PHP, MySQL, and Redis without requiring us to learn Docker or most of its complex processes.

Sail provides a docker-compose.yml file and the sail script. The sail script provides a command line interface (CLI) with methods for interacting with the Docker containers defined in the docker-compose.yml file.

How To Install Laravel Sail

To demo how to use Sail, we’re going to create a new Laravel project. This will require you to have Docker and Docker Compose installed or Docker Desktop. You’ll also need composer installed either locally or globally. If you have questions about this, please see our articles about docker and composer, respectively.

If this is an existing project you’re adding Sail to, it’s done using composer require laravel/sail --dev.

We’re going to create a new project using php composer.phar create-project laravel/laravel example-app and then cd example-app.

Either way, we need to install the sail into our project using php artisan sail:install

As part of the process, we’ll get to pick what services we want to use for development. In this case, we’re just going to select MySQL, but we could have used a different database provider, a key/value store like Memcached or Redis, and several others. Sail will then build our Docker images.

The output of this is too long to post, but what changed in our project?

The first thing is that there is now a docker-compose.yml file at the root of our project.

services:
    laravel.test:
        build:
            context: ./vendor/laravel/sail/runtimes/8.2
            dockerfile: Dockerfile
            args:
                WWWGROUP: '${WWWGROUP}'
        image: sail-8.2/app
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        ports:
            - '${APP_PORT:-80}:80'
            - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        environment:
            WWWUSER: '${WWWUSER}'
            LARAVEL_SAIL: 1
            XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
            XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
            IGNITION_LOCAL_SITES_PATH: '${PWD}'
        volumes:
            - '.:/var/www/html'
        networks:
            - sail
        depends_on:
            - mysql
    mysql:
        image: 'mysql/mysql-server:8.0'
        ports:
            - '${FORWARD_DB_PORT:-3306}:3306'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: '%'
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - 'sail-mysql:/var/lib/mysql'
            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
        networks:
            - sail
        healthcheck:
            test:
                - CMD
                - mysqladmin
                - ping
                - '-p${DB_PASSWORD}'
            retries: 3
            timeout: 5s
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local

This sets up all of the containers, networks, and volumes that we need.

If you have this directory underversion control like I do, you’ll also see that the phpunit.xml file was edited to change the database information.

git diff
diff --git a/phpunit.xml b/phpunit.xml
index f112c0c..7fb08b5 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -21,8 +21,7 @@
         <env name="APP_ENV" value="testing"/>
         <env name="BCRYPT_ROUNDS" value="4"/>
         <env name="CACHE_DRIVER" value="array"/>
-        <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
-        <!-- <env name="DB_DATABASE" value=":memory:"/> -->
+        <env name="DB_DATABASE" value="testing"/>
         <env name="MAIL_MAILER" value="array"/>
         <env name="QUEUE_CONNECTION" value="sync"/>
         <env name="SESSION_DRIVER" value="array"/>

Create an alias

Now to interact with sail we need to run ./vendor/bin/sail but that’s a lot of typing so we’re going to set up an alias to make it easier to work with. This is taken directly from the Sail documentation

alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'

“Up”ing Our Environment

We’re finally ready to create our containers and see how they work. To get the environment up we’re going to run sail up

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# sail up
Creating network "example-app_sail" with driver "bridge"
Creating volume "example-app_sail-mysql" with local driver
Creating example-app_mysql_1 ... done
Creating example-app_laravel.test_1 ... done
Attaching to example-app_mysql_1, example-app_laravel.test_1
...
laravel.test_1  | 
laravel.test_1  |    INFO  Server running on [http://0.0.0.0:80]. 
laravel.test_1  | 
laravel.test_1  |   Press Ctrl+C to stop the server

Should be able to access the site now.

Use ctl+c to cause Sail to exit.

What if we didn’t want to keep the shell open?

We can run Sail using sail up -d to run in detached mode so it will continue to run in the background so we can do other tasks using the same terminal.

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# sail up -d
Creating network "example-app_sail" with driver "bridge"
Creating example-app_mysql_1 ... done
Creating example-app_laravel.test_1 ... done

To shut it down we’ll use sail down.

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# sail down
Stopping example-app_laravel.test_1 ... done
Stopping example-app_mysql_1        ... done
Removing example-app_laravel.test_1 ... done
Removing example-app_mysql_1        ... done
Removing network example-app_sail

Running Artisan Commands Inside The Container

Because we’re running our Laravel application inside of a container, we need to run our artisan commands inside the same container. To do so, we can run sail artisan <command> which will open a connection to the container and run the artisan command.

For example, we can run artisan migrate:

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# sail artisan migrate

   INFO  Preparing database. 

  Creating migration table .................................................................. 61ms DONE

   INFO  Running migrations. 

  2014_10_12_000000_create_users_table ...................................................... 45ms DONE
  2014_10_12_100000_create_password_reset_tokens_table ...................................... 52ms DONE
  2019_08_19_000000_create_failed_jobs_table ................................................ 49ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ..................................... 69ms DONE

Accessing the Running Container

Eventually, you’re going to need to access the running container to perform some kind of troubleshooting. To do this we have two options. The first is sail shell which will create a shell for us logged in as the “sail” user. We can also use sail root-shell to create a shell logged in as the root user.

Customizing the Docker Image

If you use sail shell to access the container you might quickly notice that it’s a little devoid of tools that would help to troubleshoot problems and you might need additional packages for your application

sail@621f8b751f57:/var/www/html$ vi index.php
bash: vi: command not found
sail@621f8b751f57:/var/www/html$ vim index.php
bash: vim: command not found
sail@621f8b751f57:/var/www/html$ nano index.php
bash: nano: command not found

To resolve this we need to publish the sail files using sail artisan sail:publish

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# sail artisan sail:publish

   INFO  Publishing [sail-docker] assets. 

  Copying directory [vendor/laravel/sail/runtimes] to [docker] ................................... DONE

   INFO  Publishing [sail-database] assets. 

  Copying directory [vendor/laravel/sail/database] to [docker] ................................... DONE

This creates several files in the “docker” directory.

root@ubuntu-s-1vcpu-1gb-amd-nyc3-01:~/sail-example/example-app# tree docker
docker
├── 8.0
│   ├── Dockerfile
│   ├── php.ini
│   ├── start-container
│   └── supervisord.conf
├── 8.1
│   ├── Dockerfile
│   ├── php.ini
│   ├── start-container
│   └── supervisord.conf
├── 8.2
│   ├── Dockerfile
│   ├── php.ini
│   ├── start-container
│   └── supervisord.conf
├── 8.3
│   ├── Dockerfile
│   ├── php.ini
│   ├── start-container
│   └── supervisord.conf
├── mysql
│   └── create-testing-database.sh
└── pgsql
    └── create-testing-database.sql

If we look in our docker-compose.yml we can see that we’re currently running version 8.2.

services:
    laravel.test:
        build:
            context: ./docker/8.2
            dockerfile: Dockerfile

To add additional packages to our container, we’re going to edit “docker/8.2/Dockerfile”. This file is again not worth publishing the entire contents of, but there will be a large group of commands that start with RUN apt-get update \ and are strung together using “&&” and “\”. We need to edit the section that starts with && apt-get install -y and add our packages.

I’m going to add Vim.

Before:

    && apt-get install -y php8.2-cli php8.2-dev \
       php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \

After:

    && apt-get install -y php8.2-cli php8.2-dev \
        vim \
        php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \

Now we need to rebuild our containers using sail build.

After that has completed we sail up -d and sail shell to get back into the container.

Then we can use vim.

Trying PHP 8.3

One of the amazing parts about using docker containers is how easy it is to swap out one container for another. Sail comes with Dockerfiles for 8.0, 8.1, 8.2, and 8.3, and switching between them is fairly simple.

To do so we just need to edit our docker-compose.yml file and change “context: ./docker/8.2” to “context: ./docker/8.3”. Then we can run sail build and sail up to quickly switch our laravel.test container to use 8.3.

We can even check our website to see if it is running 8.3 and not 8.2

Maybe we’re not yet ready to update our servers, so to swap back, we just edit our docker-compose.yml file and change it back to “context: ./docker/8.2”. Running sail build takes almost no time because the operations are still cached so we can quickly switch back and forth.

What You Need to Know

  • The Laravel Sail provides a light-weight CLI for interacting with Docker development environment
  • Highly customizable
  • Allows us to quickly spin up a development environment

Tags: ,
 

Leave a comment

Use the form below to leave a comment: