Getting to grips with Docker and Laravel Sail (on Apple Silicon/M1)

So I wanted to have a play with Laravel Breeze and Jetstream. In doing so I noticed that the instructions for Breeze (and for Laravel in general) suggest a Docker-based Laravel Sail install by default.

I had not looked into Docker despite a lot of hype around it recently in my tech world. So I used the new Laravel encouragements to give it a shot.

BUT…I have the slight problem that I have an ARM/Apple Silicon/M1 MacBook, and, while a Docker Tech Preview (beta) exists for this, it has issues, and there are some things that aren’t suppored – including MySQL!

This post is a super-quick overview of Sail, the issues I had on Apple Silicon and how I fixed them.

Overview of Docker and Laravel Sail

This is covered in much more detail elsewhere, but Docker is a way of making lightweight virtual machines called “containers”. Rather than running a full blown operating system, these make use of what is already there in the existing operating system and they only add or change what is not there or different.

Olivier Lafleur recommended Scott Hanselman’s excellent video introduction if you want more detail on this.

Laravel Sail is a set of scripts and configurations that allow you to fire up a bunch of containers running services that, together, get a Laravel application up and running. These services include MySQL and PHP but you get others too like Redis and Mailhog.

You may, after digging in a bit, note that there is no web server (Apache/nginx) and this is because Sail uses PHP’s built-in web server. You can see it starting up in the supervisord.conf file of the Laravel container.

It’s also worth noting that the containers are, for the most part, temporary/ephemeral and kinda don’t save their state. They get created. And they get destroyed.

The exception to this is the MySql container which uses a Docker “volume” to store its data.

If you’re not familiar with Docker and aren’t interested in its internals then what you need to know is:

  • Your Laravel project files are mapped into the container. Any changes you make locally are reflected inside the container immediately.
  • Your database is persisted. (I believe Redis state is too, but I’m not sure what the benefit of this is.)
  • Everything else is destroyed when you shut Sail down. Mostly because it doesn’t NEED to be saved. You just recreate everything in its default state the next time you need it.

Yannick‘s awesome post gives an excellent and very detailed overview of both Docker and Sail that I’ve yet to read in full. And the free Laracasts video was also pretty helpful in understanding Sail.

You may be a Valet or MAMP user who’s wondering what the benefits of this approach are. And I think that for smaller projects there probably aren’t many benefits. But for bigger projects there definitely are, such as ensuring the development and live environments are the same.

My own curiosity came from the fact that I’d been thinking about security and how all these node and composer packages could be doing anything on my laptop. Putting them inside a container limited their ability to run amok on my laptop when doing local development. So I wanted to try it out from that point of view.

Issues with Apple Silicon/M1

Summary: Sail doesn’t quite work

Sail, out of the box, doesn’t work with Apple Silicon/M1 computers. But you can make most of it work. If you know how.

Given this, I found it pretty surprising that it’s now the recommended way to get up and running with Laravel. If you’re a beginner then following the installation instructions and immediately hitting a very-complicated-to-resolve failure may put you right off. I’m NOT a beginner, but I’m empathetic and often see different sides of an experience. This would not be a good experience for a new-to-Laravel developer on an M1 Mac.

Sail advertises itself as “a great starting point for building a Laravel application using PHP, MySQL, and Redis without requiring prior Docker experience”, but on my machine, I needed to learn an awful lot of Docker to make it work and understand a few other things too.

You could argue that Docker for the M1 Mac’s is in tech preview/beta, so you should not expect things to work. But I still think that people will want to try it, the documentation around it isn’t good enough, and there are some small changes that would really help.

These instructions assume you have a new Laravel project created with Sail, or you’ve installed Sail into an existing Laravel project.

Fix 1: Remove Meilisearch

Meilisearch is broken. You can build the Docker images with it in place, and you can start Sail up, but when you try to interact with Sail you get an error and Sail shuts down:

$ vendor/bin/sail shell
breeze_meilisearch_1    tini -- /bin/sh -c ./meili ...   Exit 1
Shutting down old Sail processes...
Sail is not running.

Meilisearch feels like a very optional requirement. So I removed it.

Edit the docker-compose.yml in the root of your Laravel project and remove meilisearch from the depends_on section of the laravel.test service:

        depends_on:
            - mariadb
            - redis
            - selenium
            - meilisearch

Then you can delete, or if you’re hopeful of a future fix comment out, the lines for the meilisearch service lower down.

Then rebuild your containers with vendor/bin/sail build

Note: I believe that if you’re installing Sail interactively then you are prompted to choose which services to install. You can just not install Meilisearch at this point and skip this first fix.

Fix 2: Swap MySql for MariaDB

Next you’ll find that the MySql Docker image doesn’t support ARM processors like the M1. This is even documented by Docker with two solutions:

  • Run with the --platform linux/amd64 flag, which I couldn’t figure out how to do.
  • Use MariaDB instead. This seemed like an easier option, and would perform better as it would run native.

To do this you need to modify a couple of things.

First, edit the docker-compose.yml file in the root of your project and replace mysql with mariadb in the depends_on section of the laravel.test service:

        depends_on:
            - mariadb
            - redis
            - selenium

You’ll then want to rename the mysql service as a mariadb service and install the right Docker image (this needs a version number but I don’t know what to give, so I’m just using the latest I guess?)

    mariadb:
        image: 'mariadb'

Leave the rest of the mysql config intact.

Then again, rebuild the containers with vendor/bin/sail build

This installs MariaDB instead of MySQL which will nicely run native, but if you try to do anything with your database at this point you’ll get connection errors:

but you need to configure your app to talk to the right service on the network, so edit your .env and change the DB_HOST from mysql to mariadb

DB_HOST=mariadb

You don’t need to rebuild your containers for this. But this should fix everything!

No fix for sail share / Expose

In theory Sail has a great sharing function that lets you share your local sail-hosted site with the outside world. It makes use of Beyond Code‘s open-source Expose product which sets up tunnels similar to ngrok and the “valet share” command if you’ve ever used that.

But this also fails.

This appears to run and set up a connections, but it seems that something somewhere just isn’t running. Connecting to the URL just does nothing.

I’m not sure if this is an M1-specific problem or not. I’ve copied the command from the Sail script and tried running it manually with the platform flag:

docker run --platform linux/amd64 --init beyondcodegmbh/expose-server share http://host.docker.internal:"$APP_PORT" \
            --server-host=laravel-sail.site \
            --server-port=8080

This gets rid of the platform error, but this still didn’t work, so maybe it’s a wider expose problem. I don’t know.

Where are my volumes/databases?

Finally, there’s this very hidden-away bit of Docker that I really wanted to understand but couldn’t: volumes.

I’d wondered if I could see the database files and maybe run a non-Docker database against them. But for all my hunting I could not find any data files for my application.

The closest I got was running docker inspect <volume name>, which gave me this:

$ docker inspect breeze_sailmysql
[
    {
        "CreatedAt": "2021-03-02T10:53:58Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "breeze",
            "com.docker.compose.version": "1.28.2",
            "com.docker.compose.volume": "sailmysql"
        },
        "Mountpoint": "/var/lib/docker/volumes/breeze_sailmysql/_data",
        "Name": "breeze_sailmysql",
        "Options": null,
        "Scope": "local"
    }
]

And I figured I should look for /var/lib/docker/volumes/breeze_sailmysql/_data, but I couldn’t find this or anything like it anywhere.

Searching my local filesystem for Docker stuff revealed a 60GB file at /Users/rosswintle/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw

This is referenced in the Docker preferences:

So I’m assuming this is some kind of virtual filesystem thing that stores my containers volumes locally. This was kinda confirmed by Yannik also.

Fin

That’s all I got. Let me know what you think of Sail, of if you think I got anything wrong.