Using Docker with Laravel

July 21st, 2019

Warning: This post is 4 years old. Some of this information may be out of date.
ian-taylor-joqjbvo1p9g-unsplash.jpeg

When developing on a Laravel project I've either used Homebrew for my web server and database, or used a Vagrant box provisioned with Ansible. Whilst these work well most of the time there's an alternative that is quickly becoming the de-facto standard: Docker.

Fortunately, getting Laravel to work with Docker is quite straightforward.

What is Docker?

Docker is a system that uses 'containers' to provide the services your software needs. These containers are very lightweight instances of the services and can be switched on and off at will.

For example, with a traditional Vagrant box you would have a complete installation of an operating system and then you would add the web server and database to it. If you have different projects with different requirements you would need another vagrant box setting up. These boxes are quite large and not easy to switch between.

Docker simplifies this by separating the Operating System from the services. You would install the Docker program on your computer and then define each of the services you need for each of your projects.

Containers are standalone components defined using a Dockerfile. This file contains commands to inform the docker service which base container (called an 'image') you want to use and any additional setup you need.

These containers are tied together with a docker-compose file. This file contains instructions on what containers/dockerfiles should be used in the project and any configuration they require.

However, if you don't need any additional configuration on your base image, you don't need the separate dockerfile's.

How to use Docker with Laravel

First, make sure you have Docker installed and running on your computer.

Using Docker with Laravel is straightforward. A basic Laravel project requires three containers:

  • The web server
  • The database
  • PHP (FPM)

Here's the file structure we'll need for using Docker with Laravel:

# This is inside your Laravel project root
├── app
├── artisan
├── bootstrap
...
├── docker
│   ├── nginx
│   │   └── default.conf
│   └── php-fpm
│       └── Dockerfile
├── docker-compose.yml
...

Here's the docker-compose.yml file, found at the root of our Laravel project.

# docker-compose.yml
version: "3"
services:
    nginx:
        image: nginx:latest
        volumes:
            - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
            - ./:/var/www/html
        ports:
            - 80:80
    phpfpm:
        build: docker/php-fpm
        volumes:
            - ./:/var/www/html
    mysql:
        image: mysql:5.7
        environment:
            MYSQL_ROOT_PASSWORD: root
            MYSQL_USER: homestead
            MYSQL_PASSWORD: secret
            MYSQL_DATABASE: homestead
        volumes:
            - /var/lib/mysql
        ports:
            - 3306:3306

The docker-compose.yml file has three services defined:

The Nginx service

This the web-server service, using the nginx:latest image from https://hub.docker.com/_/nginx.
The volumes node of this service mounts our local docker/nginx/default.conf file to the Nginx configuration file on the Docker container. It also mounts our local directory ./ to the container's /var/www/html directory. Finally, we define the port-mapping between our local machine and the container.

Note that this service forwards the container port 80 with the local port 80. If you are already running a web server on your computer you can change these ports, for example, to access the Docker web service on port 8080:

ports:
    - 8080:80

Here's the docker/nginx/default.conf file:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/html/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass phpfpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

This file sets up the Nginx container to serve files from the /var/www/html/public document root and to pass any requests for PHP files to port 9000.

The php-fpm service

This is the PHP-FPM service. Instead of defining an image we tell it to use the Dockerfile from our local docker/php-fpm directory. The volumes section mounts our local ./ to /var/www/html, just as we did in the nginx section.

Here's the contents of docker/php-fpm/Dockerfile:

FROM php:7.2-fpm

RUN apt-get update && apt-get install -y \
    curl \
    libssl-dev \
    zlib1g-dev \
    libicu-dev \
    libmcrypt-dev
RUN docker-php-ext-configure intl
RUN docker-php-ext-install pdo_mysql mbstring intl opcache

RUN usermod -u 1000 www-data

WORKDIR /var/www/html

CMD ["php-fpm"]

EXPOSE 9000

This tells Docker to use the image from php:7.2-fpm (https://hub.docker.com/_/php) and then installs some additional PHP extensions we need for Laravel development. Finally we expose port 9000 on the container to any parent services.

The MySQL service

This is the MySQL service container. It uses the mysql:5.7 image from https://hub.docker.com/_/mysql. This image will set up a database using the credentials we set in the environment node.

Configuring Laravel

Before we can start using Docker with Laravel there's one more important step we need to do.

By default, Laravel's .env file contains database credentials with the DH_HOST set as localhost. This won't work with our Docker container as Laravel doesn't know the IP address that the Docker container is using.

However, we can tell Laravel to use the same name as we gave our MySQL service in the docker-compose.yml file and Docker will magically make this work for us. So, change your .env file to the following:

# Your project's .env file
...
DB_CONNECTION=mysql # <--- change this to the name of your docker service
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
...

Starting the docker containers

Once the files are configured we can go about starting the services. This is done in two parts. Firstly we need to build the containers which will download the images from Dockerhub and set them up. Secondly we start the services with docker-compose.

Inside your Laravel project root:

docker-compose build

This will take a while to download and configure everything. Once done, start the services with:

docker-compose up -d

The -d flag caused Docker to run the services in the background (daemon services).

To test its all working, browse to http://127.0.0.1. You should see the default Laravel page.

Running Artisan commands

To run an artisan command you need to log in to the php-fpm service and run the artisan command:

docker exec -it example_phpfpm_1 php artisan --version

Note: the example_phpfpm_1 is the name of my php-fpm container. Yours will most probably be different. You can find out your container's name by running docker-compose ps.

To test that our database container is working properly with the rest of our services we can run the default Laravel migrations command:

docker exec -it example_phpfpm_1 php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.11 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.06 seconds)

Finally, let's generate Laravel's built in authorisation and see it in the browser:

docker exec -it example_phpfpm_1 php artisan make:auth
docker exec -it example_phpfpm_1 php artisan migrate

Browse to http://127.0.0.1 and you should see the 'Login' and 'Register' links.

If you have any questions or are having problems using the code above, feel free to comment below.