Using Docker with Laravel
July 21st, 2019
Warning: This post is 5 years old. Some of this information may be out of date.
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 runningdocker-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.