The original post was also uploaded here: Laravel Application on Docker
If you are a django developer, you can find a really comprehensive tutorial here: django on Docker
Now Shall We?
Docker, the new sheriff in town whose new gold boots is getting everyone excited. Why? because Docker is development bliss since it ensures consistency on both your development and production cycles therefore standardizing your environment
I assume you're as excited about docker as the rest of us and hence explains why you have stumbled upon this post. I'll be honest, docker at first, without any prior knowledge is a nightmare, as any other new concept without prior knowledge, but once you do get the hang of it, it'll save you tonnes of time you'll have otherwise invested during deployment.
This post is intended mostly for beginners who probably have little or no knowledge about docker. The motivation behind this post lies on the fact that I wasn't able to find any comprehensive Laravel Docker tutorials online. I was only able in the end to accomplish this task through continuous reading of multiple blog posts and combining all this into a massive comprehensive series of steps that I'll attempt to document in this post.
Now Onto The Good Stuff..
Before I start, I assume that you already have your Laravel Application ready with you. If you don't, you can jump on to the Laravel Documentation Page and build yourself an app, then head back here and continue reading.
I also assume that you already have Docker installed on your machine. In case you don't, you have the following options:
- Windows 10 Pro Users: Docker Desktop
- Windows 10 Version that's below Windows 10 Pro Users: Docker Toolbox .This is because of Docker Desktop system requirements. Docker Toolbox leverages on the functionalities of VirtualBox.
- Linux Users: Docker CentOS . You can choose your Linux distribution (if its not CentOS) from the side menu on the screen that comes up and follow those distribution specific instructions.
First Step: Creating your 'docker-compose' file
What is a docker-compose file? This is a file that defines all your multiple docker containers and all these containers can be spawned up by running a relatively simple command like:
docker-compose -f docker-compose.prod.yml up --build
Here, we'll be setting up a development environment (Which can also be used in your production environment, with a few minor changes that I'll document in my next post :-))
Create a file in your root directory and name it: docker-compose.yml
You'll be defining the containers in the next steps in this file.
In our docker-compose file, we define three containers: mysql, nginx and our laravel app.
So, for starters, our laravel app container will be defined as follows:
version: '2'
services:
# The Application
app:
container_name: laravel_app
build:
context: ./
dockerfile: development/app.dockerfile
volumes:
- ./storage:/var/www/storage
env_file: '.env.prod'
environment:
- "DB_HOST=database"
- "REDIS_HOST=cache"
Overview of the Above Code:
- version - Feel free to change this to your choosing
- container_name - You'll use this name to refer to your container. i.e. if you'd want to close your container, you'll use this name to refer to it specifically. Also feel free to change it to your choosing.
-
build - Used to build an image from a Dockerfile. Has the following additional options:
- Context - Docker uses this context (basically, where your laravel files reside) to reference any files within it. In this case, the ./ refers to the root laravel folder assuming that the docker-compose file is stored in your laravel root folder.
-
dockerfile: docker images are built from Dockerfiles, which often contain additional commands that should be run inside the container. In this case, the dockerfile we use to build our app container. Also note that we have used
development/app.dockerfile
.This means that our docker file is located in a 'development' folder on the root of our laravel app.
volumes - Volumes are used by docker containers to share files between the host machine and the docker container that is running. The left hand side of the full colon represents our host machine and the right hand side represents our docker container. In this case, we're sharing all data in the storage folder on our laravel app with the docker container mounted at /var/www/storage
env_file - This defines our laravel's .env file, in our case env.prod that we'll use to input docker container specific environment variables as we'll see later on this post.
environment - This defines the environment variables that will be set on our docker machine. In this case, if we can execute a bash command inside our linux container and reference the environment variables we define here, i.e.
echo $DB_HOST
will print out: database
Our NGINX Container will be defined as follows:
# The Web Server
web:
container_name: nginx_server
build:
context: ./
dockerfile: development/web.dockerfile
volumes:
- ./storage/logs/:/var/log/nginx
ports:
- 8990:80
Overview of the Above Code:
- container_name - Again, the name of your container, which you can choose to change.
- build - Definition same as above. Here you can see that we define this container's dockerfile as web.dockerfile.
- volumes - Definition same as above. Here we share our laravel's logs folder with nginx's logs folder.
- ports - Here, we define the port in the host machine that our docker container will be listening on and the port on the virtual network created by docker during container deployment. This can be easily visualised by understanding that the left side of the colon defines the host machines, therefore ports on the host machine and the right side of the colon the docker container, therefore the ports on the docker container.
Our MySQL Container will be defined as follows:
# The Database
database:
container_name: mysql_database
image: mysql:5.7
volumes:
- dbdata:/var/lib/mysql
environment:
- "MYSQL_DATABASE=Baly"
- "MYSQL_USER=phpmyadmin"
- "MYSQL_PASSWORD=phpmyadmin"
- "MYSQL_ROOT_PASSWORD=finallyJDBC2017."
ports:
- 8991:3306
Overview of the Above Code:
- container_name - Refer to above.
-
image - In this case, we haven't defined a dockerfile to base our container build on, but rather an image. Our docker container will therefore be built from the image we've defined in this case
mysql5:7
image. You can switch this mysql version with the version you're developing with. Remember that, with reference to your laravel application, the newest versions of mysql may not work with your laravel app. This is because the newest versions MySQL use a different technique of authentication that may not be supported by either mysql or pdo php extensions. Therefore, beware when invokingmysql:latest
instead ofmysql:5.7
. -
volumes - Still the same concept, except that now we've defined
dbdata
from our host machine that will map to/var/lib/mysql
on the docker container. -
environment - Same concept as defined above, except that in this case, our mysql database will be initialized with the variables we have set. Therefore, our container after build, will automatically have a database named
database
, a user namedsecret
identified by the passwordsecret
and a root password ofsecret_root
. You can feel free to change these as you please. We define these settings in ourenv.prod
file so as not to collide our current.env
file settings with our container file settings. - ports - same as above, except that our mysql container will be listening on port 8991 on the host machine and 3306 (mysql's default port) on the container's network.
Defining your named volumes
Copy paste the following into your docker-compose.yml file:
volumes:
dbdata:
Ensure that you need to preserve the indenting in your docker-compose.yml file to ensure that docker-compose reads it correctly. In the end, your docker-compose file should look as follows:
version: '2'
services:
# The Application
app:
container_name: laravel_app
build:
context: ./
dockerfile: development/app.dockerfile
volumes:
- ./storage:/var/www/storage
env_file: '.env.prod'
environment:
- "DB_HOST=database"
- "REDIS_HOST=cache"
# The Web Server
web:
container_name: nginx_server
build:
context: ./
dockerfile: development/web.dockerfile
volumes:
- ./storage/logs/:/var/log/nginx
ports:
- 8990:80
# The Database
database:
container_name: mysql_database
image: mysql:5.7
volumes:
- dbdata:/var/lib/mysql
environment:
- "MYSQL_DATABASE=Baly"
- "MYSQL_USER=phpmyadmin"
- "MYSQL_PASSWORD=phpmyadmin"
- "MYSQL_ROOT_PASSWORD=finallyJDBC2017."
ports:
- 8991:3306
# redis
cache:
image: redis:3.0-alpine
volumes:
dbdata:
Second Step: Defining our Dockerfiles.
In this step, we define the dockerfiles for the containers we just defined in our docker-compose file. These dockerfiles will represent a series of commands that we'll want to run inside our docker containers.
Defining our 'app' dockerfile (laravel_app)
Create a folder in your laravel app's root directory and name it development
. Inside the folder you just created, create a file and name it app.dockerfile
(yes, without any extensions). Open this file and copy paste the following code into it:
FROM php:7.2-fpm
COPY composer.lock composer.json /var/www/
COPY database /var/www/database
WORKDIR /var/www
RUN apt-get update && apt-get -y install git && apt-get -y install zip
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php -r "if (hash_file('SHA384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
&& php composer-setup.php \
&& php -r "unlink('composer-setup.php');" \
&& php composer.phar install --no-dev --no-scripts \
&& rm composer.phar
COPY . /var/www
RUN chown -R www-data:www-data \
/var/www/storage \
/var/www/bootstrap/cache
RUN apt-get install -y libmcrypt-dev \
libmagickwand-dev --no-install-recommends \
&& pecl install mcrypt-1.0.2 \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-enable mcrypt
RUN mv .env.prod .env
RUN php artisan optimize
Overview of the Above Code
From php:7.2-fpm - This means will be building our container from an image,
php:7.2-fpm
. Also, you can change this version to meet your development environment needs.COPY - In the first copy command, we copy our
composer.lock
andcomposer.json
from our root folder (in our host machine) to/var/www/
in the docker container. In the second copy command, we copy ourdatabase
folder in the host machine to/var/www/database
folder in the docker container. This is because, one, we'll want to make sure that the dependencies we use in our development environment (in composer.json) will be reflected inside the container when we download dependencies and two, that we can access our migrate files inside the docker container in cases we may need to runmigrate
command.WORKDIR - We set the working directory to
/var/www
which means we don't have tocd
to this folder (move to this folder) in cases we'll need to run bash commands.RUN - Here, we install all the dependencies that will be needed by laravel, including composer and the dependencies needed by composer. Please note the
if(hash_file('SHA384'...
line. The hash value defined there will change with every update, and therefore if your installer fails with the message: installer corrupt, consider getting the correct hash value from: Get Hash Value.COPY . /var/www - At this point we copy all our folder contents into
/var/www
folder in the docker container.RUN - In the final run commands, we clear our application cache and other cache and install the mysql driver that laravel uses to make connections to the database. Afterwards, we rename our
.env.prod
file to.env
since this file will contain the correct environment variables specific to the docker container environment and therefore should be used by laravel. We runphp artisan optimize
to remove the cached version of the.env
file.
Please note that it is unnecessary to copy everything from our root folder (like vendor folder) and docker provides a .dockerignore
file which works pretty much like a .gitignore
file. Our dockeringore file will look as follows:
.git
.idea
.env
node_modules
vendor
storage/framework/cache/**
storage/framework/sessions/**
storage/framework/views/**
development
Save this file in the same folder as your app.dockerfile (development folder).
For your .env.prod
file, copy paste your .env
file and rename it to .env.prod
. In the database settings, change the DB_HOST
to match the name of your mysql container, and the password to match what you defined in your docker-compose.yml
file. If you followed all my steps without changing a thing, then your .env.prod file should resemble the following:
DB_CONNECTION=mysql
DB_HOST=mysql_database
DB_PORT=3306
DB_DATABASE=Baly
DB_USERNAME=phpmyadmin
DB_PASSWORD=phpmyadmin
Defining our 'web' dockerfile
In the same folder you just created (the development folder) create a web.dockerfile
. Copy paste the following to the dockerfile:
FROM nginx:1.10-alpine
ADD development/vhost.conf /etc/nginx/conf.d/default.conf
COPY public /var/www/public
Overview of the Above Code
We build our dockerfile from the image: nginx:1.10-alpine
. We then replace nginx's default.conf
file with the vhost.conf
we'll create in a sec.
We also copy our laravel app's public directory to the public directory of nginx, that will server all our public assets.
Create a vhost.conf file in this same directory (development) and copy paste this into it:
server {
listen 80;
index index.php index.html;
root /var/www/public;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
try_files $uri /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
our php-fpm
container will be listening on port 9000 and hence app:9000
Almost there...
So, to counter-check, you need to already have the following files:
-
Root folder -
docker-compose.yml
and.env.prod
-
development folder:
.dockerignore
app.dockerfile
web.dockerfile
vhost.conf
If so, then you're almost done, but first, some prerequisites:
If you are using Docker Toolbox on Windows and your laravel app folder is in a folder other than C:/users
you will have trouble sharing volumes between your host machine and your docker containers. This is because any other folder that's not C:/users
is not mounted by virtual box when your docker machine starts. Therefore, to fix this, first stop your running docker machine by running:
docker-machine stop
Then open virtualbox, right-click the machine named default and click on settings . Navigate to Shared Folders click on it, and add a new folder that defines the location of your laravel app folder. Remember to check against Auto mount. Afterwards, start your docker machine by running:
docker-machine start default
Drum Rolls...
Assuming you have done everything correctly, go ahead and run the following command:
docker-compose up -d --build database && docker-compose up -d --build app && docker-compose up -d --build web
finally, enter you laravel_app's docker container by executing:
docker exec -it laravel_app bash
And execute the following laravel commands:
php artisan key:generate
php artisan config:cache
php artisan route:cache
Make sure that you are running this command inside the root folder of your laravel app. This command builds your container images and finally starts them. If everything goes according to plan, you should be able to access your laravel app running inside your container at:
0.0.0.0:8990
Replace 8990
with the port you defined in your docker-compose.yml file if you used a different port.
Also, please note that for users using Docker Toolbox, docker creates a virtual network and assigns an IP address to it. You can find this IP address by searching for docker quickstart terminal
and running it. The IP address assigned will be displayed in the terminal that pops up and you'll be able to access your laravel app by going to:
your-docker-machine-ip:8990
And there you have it folks! You have successfully deployed your laravel app on docker! Stay tuned for my next post where I'll be describing on how to deploy your Laravel app on a production environment.
Top comments (28)
Hi Brian, i made all tutorial steps. i am not so good at docker yet, but a i get a error like this:
"Step 2/11 : COPY composer.lock composer.json /var/www/
ERROR: Service 'app' failed to build: COPY failed: stat /var/lib/docker/tmp/docker-builder391229134/composer.lock: no such file or directory"
Can you help me?
Hello there Sylvio. The error indicates that there is no composer.lock file
Please check your project's root folder and confirm that the file 'composer.lock' is there
What worked for me was removing the command in app.dockerfile that validates the composer-setup file hash starting with: "&& php -r "if(hash_file...", or changing it to the correct hash (see the entry under "4. RUN" in the article), and then executing the docker-compose up command from the root directory of your laravel app.
Hi Brian, it take me so long but finally i could make my app work, i only have a doubt, how i can reload changes in my code to the app container without rebuilding the whole thing. Thanks!
Hello there Diego! Well, if you're on your development environment, you could set up a new volume in your docker-compose file that you can link to your project's root directory. That way, any changes you make to code will be reflected automatically in your docker container
Hi brian , can you give me some example to do that ?
Hello there DeUliZeaLand, Highlighting from part of my post: When we were defining the Laravel App container inside our docker-compose file, we defined a volumes entry:
./storage:/var/www/storage
whereby the left side before the colon represents our host machine and the right side after the colon represents our docker machine. So in this case, instead of just using thestorage
folder, you can adjust this entry to become:.:/var/www
which will now link you entire project to your docker container. Again, only do this in your development environment, not your production environment.Hello Brian,
can you elaborate why it isn't good idea to do it in production environment?
Hello there, for starters, we have different configurations for our production and development environment. As such, we only want to persist the necessary files for production in our production environment. Because of this, we'd rather not create a volume that persists our entire project folder, but rather we want the volume to persist only our necessary files. That's why we have used commands like
COPY
so that we only copy necessary files that we have no problem discarding when re-creating our docker container and we've only persisted necessary data like the Storage folder which store user data.Thanks for your explanation Brian. It's very great tutorial. Hope you make some other tutorial again docker
Hi Brian, can you help me to figure it out this error :
Step 11/11 : RUN php artisan optimize
---> Running in 3f514d7b9d8f
Command "optimize" is not defined.
ERROR: Service 'app' failed to build: The command '/bin/sh -c php artisan optimize' returned a non-zero code: 1
I have searched this bug on google but cant find an answer to this problems... I hope you will answer me...
Thank you so much...
Hello there Ludan
As a temporary fix for your error, comment out that line (php artisan optimize) then run docker-compose up then try and running the command after your container has been deployed
thank you so much for this advice <3
Hello there Patrick! I'm thankful for the appreciation! The reason why php artisan commands are run after is because php artisan needs a connection to the database, which is often not set up until the mysql container finishes building and is running. That's why we run these commands when the mysql container is up and running.
Having some issues with the session :/ Authentication is not working. Is there something should be aware about when using docker containers? The user data is okay but the redirect after login doesn't happen.
Brian, you are the best! I have been looking for a complete guide to make my application work with a dockerized Nginx + Laravel + MySQL for a lot of time. I have tried many ones, but none of them really works. They probably were good, but I'm a total novice with Laravel + Docker and VPS. But, at least, I found this. Wow! It's so good! So perfectly explained even for me and completely functional. It has been the base to do my own modifications to adapt it to my needs and make everything work properly in my Droplet at Digital Ocean. You are my HERO!!! You saved me!!! Thanks, thanks, thanks a lot! I'm even thinking about making a Spanish version from your tutorial, of course referencing you, because it is my native language and I didn't find such a really good guide anywhere in Spanish. Please, keep sharing such good content!! I am really really grateful to you!! THANKS!!!!
Hello there Edmond!
I'm glad you enjoyed the post:-)
Anyways, the docket container isn't running because of a certain mis-configuration
Could you run the following command and see if any errors are printed to your console:
docker run -a
Nice Tutorial!
I have following your example for my production docker environment. However when i tried to create another database container for testing it cannot connect to the testing database. I tried to track the problem and it lead me to the environment variable in docker-compose.yml in app container. When i try to remove the enviroment variable the apps cannot connect to the database at all.
Can you explain how the environment variable working ? and can we made that value dynamic (consider my condition i required the apps to connected into different container database)
Hi, great tutorial. Could you show how you add a domain name to this. I have tried a lot but didn't get it working. Eg. example.dev.com.
I have several sites and want to use a domain instead of localhost:8990
Thanks!
Hello there Thomas!
If your multiple sites are on different domains, then you need make sure that your server supports cross-origin requests (since these requests are blocked by default by browsers). You can find a very comprehensive tutorial online.
Meanwhile,
Using our nginx server, go to the vhost.conf file we just created, and edit the server_name directive. Modify it to be your domain name and save. At this point, I assume that you have changed your A records and pointed your domain to your server's IP address. Leave the listen port to be 80 (and 443 if you have ssl). Save your configurations and re-create your docker container.
I got a composer error: [RuntimeException]
Could not scan for classes inside "app/Models" which does not appear to be a file nor a folder
I believe you have an error in the app.dockerfile. the "COPY . /var/www" line should be before the composer install line. Anyway, great intro to dockerizing a Laravel app, thanks!
Thanks Bumasoft for pointing out this. I have actually copied the necessary composer files: composer.lock and composer.json before I ran composer install. Therefore the error being thrown might not be related to the order of COPY . /var/www. Could you provide more details about the error you have?
Some comments may only be visible to logged-in visitors. Sign in to view all comments.