DEV Community

Cover image for Dockerizing Laravel 10 [Ubuntu image + PHP 8.2 FPM + NGINX] 🛳️🛳️
Adnan Babakan (he/him)
Adnan Babakan (he/him)

Posted on • Updated on

Dockerizing Laravel 10 [Ubuntu image + PHP 8.2 FPM + NGINX] 🛳️🛳️

Hi there DEV.to community!

I've been trying to dockerize my Laravel app which acts as an API and run it on my server. While many Dockerfile exists on the internet when you search, they are mostly incomplete or somehow specific to some usage.

I will explain how I've done it and try to complete it to become a great Dockerfile for Laravel. Please let me know of anything better or any way to optimize it or any features missing from an optimal Laravel environment.

Be aware that this article is only about dockerizing a Laravel application and not any other database or filesystem. I will put these as a series procedurally.

TL;DR

Copy the content below inside a file called Docerfile at the root of your project. (Root of a project is where there are files like artisan, composer.json and .env , in case you are confused)



FROM ubuntu:latest AS base

ENV DEBIAN_FRONTEND noninteractive

# Install dependencies
RUN apt update
RUN apt install -y software-properties-common
RUN add-apt-repository -y ppa:ondrej/php
RUN apt update
RUN apt install -y php8.2\
    php8.2-cli\
    php8.2-common\
    php8.2-fpm\
    php8.2-mysql\
    php8.2-zip\
    php8.2-gd\
    php8.2-mbstring\
    php8.2-curl\
    php8.2-xml\
    php8.2-bcmath\
    php8.2-pdo

# Install php-fpm
RUN apt install -y php8.2-fpm php8.2-cli

# Install composer
RUN apt install -y curl
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Install nodejs
RUN apt install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
ENV NODE_MAJOR 20
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt update
RUN apt install -y nodejs

# Install nginx
RUN apt install -y nginx
RUN echo "\
    server {\n\
        listen 80;\n\
        listen [::]:80;\n\
        root /var/www/html/public;\n\
        add_header X-Frame-Options \"SAMEORIGIN\";\n\
        add_header X-Content-Type-Options \"nosniff\";\n\
        index index.php;\n\
        charset utf-8;\n\
        location / {\n\
            try_files \$uri \$uri/ /index.php?\$query_string;\n\
        }\n\
        location = /favicon.ico { access_log off; log_not_found off; }\n\
        location = /robots.txt  { access_log off; log_not_found off; }\n\
        error_page 404 /index.php;\n\
        location ~ \.php$ {\n\
            fastcgi_pass unix:/run/php/php8.2-fpm.sock;\n\
            fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;\n\
            include fastcgi_params;\n\
        }\n\
        location ~ /\.(?!well-known).* {\n\
            deny all;\n\
        }\n\
    }\n" > /etc/nginx/sites-available/default

RUN echo "\
    #!/bin/sh\n\
    echo \"Starting services...\"\n\
    service php8.2-fpm start\n\
    nginx -g \"daemon off;\" &\n\
    echo \"Ready.\"\n\
    tail -s 1 /var/log/nginx/*.log -f\n\
    " > /start.sh

COPY . /var/www/html
WORKDIR /var/www/html

RUN chown -R www-data:www-data /var/www/html

RUN composer install

EXPOSE 80

CMD ["sh", "/start.sh"]


Enter fullscreen mode Exit fullscreen mode

Run the build command from terminal and then run the image:



docker build -t MY_IMAGE .
docker run -p "8000:80" MY_IMAGE


Enter fullscreen mode Exit fullscreen mode

Then open your browser and navigate to "http://localhost:8000/"

Boom! Your Laravel is up!

Dive deeper

If you are in a hurry it is good to just copy/paste the codes above and get your job done. But if you want to know what is happening and how you can customize it based on your needs follow the article. You need a basic understanding of Docker though, so research about Docker for more detailed information.

Base image and stage

In the first line, you can see this line:



FROM ubuntu:latest AS base


Enter fullscreen mode Exit fullscreen mode

This means that we are defining a stage of build called base from the latest Ubuntu image.

Follow the line mentioned above you see an environment variable being set:



ENV DEBIAN_FRONTEND noninteractive


Enter fullscreen mode Exit fullscreen mode

This sets Ubuntu's interface as a non-interactive one. This is done because some commands might ask for user input and since that is not possible to do so and we need our image to build as quickly and as easily as possible, we need to disable input prompts so it does not stop the build process.

We need to update the packages of Ubuntu, hence the line:



RUN apt update


Enter fullscreen mode Exit fullscreen mode

Laravel dependencies

Obviously, every program needs some stuff installed before running and Laravel is not an exception. If you follow the link to Laravel's official deployment documentation you can see the prerequisites to run Laravel.

As Laravel's documentation mentions it needs PHP version 8.1 or higher. We will use PHP 8.2 along with the FPM to run Laravel and NGINX as a reverse proxy.

To install PHP 8.2 on Ubuntu we need to add some repositories since it is not available as a default. A good way to add a repository is to use a tool called add-apt-repository, but again it is not available as a default. The add-apt-repository tool is available in a package called software-properties-common. To install this package we can tell Docker to tell Ubuntu to install it (that seems bizarre LOL):



RUN apt install -y software-properties-common


Enter fullscreen mode Exit fullscreen mode

The flag -y is there to confirm all prompts by default since apt install asks for a confirmation midway through.

After the installation is done we can use add-apt-repository to add the required repository to install php8.2:



RUN add-apt-repository -y ppa:ondrej/php


Enter fullscreen mode Exit fullscreen mode

Then update the list of packages again to ensure everything will be installed from the newly added repositories:



RUN apt update


Enter fullscreen mode Exit fullscreen mode

Along with PHP itself, we will install Laravel's requirements too:



RUN apt install -y php8.2\
    php8.2-cli\
    php8.2-common\
    php8.2-fpm\
    php8.2-mysql\
    php8.2-zip\
    php8.2-gd\
    php8.2-mbstring\
    php8.2-curl\
    php8.2-xml\
    php8.2-bcmath\
    php8.2-pdo


Enter fullscreen mode Exit fullscreen mode

If you wonder what are all those back-slashes (), they are there to tell docker that the new line character should be escaped.

PHP FPM & CLI

So far everything is fine for a Laravel application to run, but we want to give it some more speed and we are going to use PHP FPM. (More info about PHP FPM here)

Here we will install FPM:



RUN apt install -y php8.2-fpm php8.2-cli


Enter fullscreen mode Exit fullscreen mode

PHP CLI also needs to be installed as it is essential to run many commands especially php artisan.

Composer

As you might know, Composer is a package manager for PHP projects and Laravel uses it too. To install Composer we need to download the installer file and run it with PHP. To download something we can use wget or curl. Here we will use curl to download and pipe the result to PHP for installation.

First, install curl:



RUN apt install -y curl


Enter fullscreen mode Exit fullscreen mode

Then, download the file and pip it to PHP:



RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer


Enter fullscreen mode Exit fullscreen mode

Node.js

If you are using Laravel as a full-stack framework (both front-end and back-end), you need Node.js too.

Node's official documentation suggests installing Node on Debian/Ubuntu-based distros (on which our image is based) through Node binary distributions. (Documentation for Debian/Ubuntu

I am not going to dive into full details of the commands since full description is available in the documentation provided above. So once again we will tell Docker to tell ubuntu to run the commands, thus the codes:



RUN apt install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
ENV NODE_MAJOR 20
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt update
RUN apt install -y nodejs


Enter fullscreen mode Exit fullscreen mode

NGINX

We will install and configure as a reverse proxy.

To install NGINX:



RUN apt install -y nginx


Enter fullscreen mode Exit fullscreen mode

Once NGINX is installed, we need to add our website configuration. The default website's configuration is located at /etc/nginx/sites-available/default so we echo our configuration inside this file and overwrite it:



RUN echo "\
    server {\n\
        listen 80;\n\
        listen [::]:80;\n\
        root /var/www/html/public;\n\
        add_header X-Frame-Options \"SAMEORIGIN\";\n\
        add_header X-Content-Type-Options \"nosniff\";\n\
        index index.php;\n\
        charset utf-8;\n\
        location / {\n\
            try_files \$uri \$uri/ /index.php?\$query_string;\n\
        }\n\
        location = /favicon.ico { access_log off; log_not_found off; }\n\
        location = /robots.txt  { access_log off; log_not_found off; }\n\
        error_page 404 /index.php;\n\
        location ~ \.php$ {\n\
            fastcgi_pass unix:/run/php/php8.2-fpm.sock;\n\
            fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;\n\
            include fastcgi_params;\n\
        }\n\
        location ~ /\.(?!well-known).* {\n\
            deny all;\n\
        }\n\
    }\n" > /etc/nginx/sites-available/default


Enter fullscreen mode Exit fullscreen mode

This is the same configuration suggested by Laravel's official documentation with minor tweaks:

  • We don't need server_name since this container is used server only Laravel and should respond to everything.
  • Changed the root to /var/www/html/public as it is the root of where our application is going to be deployed.
  • fastcgi_pass's fpm socket is changed to unix:/run/php/php8.2-fpm.sock as php8.2-fpm's socket in our scenario is placed here.

Custom shell script to run our application

At this step, we will create a custom shell script to prepare the system and run the app each time the container is started.

Here is how our bash script looks:



#!/bin/sh
echo "Starting services..."
service php8.2-fpm start
nginx -g "daemon off;" &
echo "Ready."
tail -s 1 /var/log/nginx/*.log -f


Enter fullscreen mode Exit fullscreen mode

This script starts the FPM service then runs the entry point of nginx and sends it to the background and finally starts printing nginx logs.

To create this file we can echo it inside a file like below:



RUN echo "\
    #!/bin/sh\n\
    echo \"Starting services...\"\n\
    service php8.2-fpm start\n\
    nginx -g \"daemon off;\" &\n\
    echo \"Ready.\"\n\
    tail -s 1 /var/log/nginx/*.log -f\n\
    " > /start.sh


Enter fullscreen mode Exit fullscreen mode

I've named the file start.sh and placed it at the root of the container. You are free to name it anything and place it anywhere you want.

Deploying Laravel

Now that our environment is ready let's move to deploying Laravel.

First, copy the current files to /var/www/html:



COPY . /var/www/html


Enter fullscreen mode Exit fullscreen mode

Then change the working directory of Docker to the same address:



WORKDIR /var/www/html


Enter fullscreen mode Exit fullscreen mode

Since Laravel needs access to this folder as well we set the permission of the directory:



RUN chown -R www-data:www-data /var/www/html


Enter fullscreen mode Exit fullscreen mode

And finally, ask Composer to install the ph packages required by Laravel:



RUN composer install


Enter fullscreen mode Exit fullscreen mode

Note: don't run composer install before setting the permissions. Setting permission of every folder is done recursively and it's better run before all those packages installed since it will take way longer in that case.

Expose port 80

NGINX will try to listen to post 80 and needs to be exposed in order to be bound:



EXPOSE 80


Enter fullscreen mode Exit fullscreen mode

Run the server

Remember the shell script we've created? It is time to run the script when the container is started:



CMD ["sh", "/start.sh"]


Enter fullscreen mode Exit fullscreen mode

And over!

Build and run

To build and runt he image use the commands below where a docker instance is available:



docker build -t MY_IMAGE .
docker run -p "8000:80" MY_IMAGE


Enter fullscreen mode Exit fullscreen mode

Replace MY_IMAGE with the desired name for your Docker image.


I hope this article was helpful. Please let me know of any mistakes or improvements.


BTW! Check out my free Node.js Essentials E-book here:

Feel free to contact me if you have any questions or suggestions.

Top comments (4)

Collapse
 
yuhenobi profile image
Vasilii Borisov

Not a bad way for starting to learn how it can be, but in common the article is a collection of a bad practices:

  • Big image for building a container, take a look at the alpine
  • All applications are in one container, it’s better to split them and run in one network
  • Custom start script (with tail inside, OMG)

BTW, there is no database 🖖

Collapse
 
adnanbabakan profile image
Adnan Babakan (he/him)

Hi there
Yes, alpine is better but since many people are used to working with Ubuntu as their server when deploying manually (without Docker) I thought it is better to use the same principles so using Docker doesn't become a hassle.
Yes, using multiple services in one network is great but here my approach was to use a single Dockerfile and not any composes. And yes database is omitted here as my goal was only to deploy Laravel itself.
In the next article, I will try to make a more elaborate tutorial using composes.
Thanks for your comment and please let me know of anything you think would be good to add.

Collapse
 
yuhenobi profile image
Vasilii Borisov

many people are used to working with Ubuntu as their server

If people are using an Ubuntu on their servers, using Alpine in a container will not be an issue, I’m pretty sure 👌

but here my approach was to use a single Dockerfile

This actually affects the build time. To reduce it and save an approach with a single Dockerfile you can use a multi stage build — make a base image with all necessary software and after that in the second stage add your PHP code ☺️

Thread Thread
 
adnanbabakan profile image
Adnan Babakan (he/him)

Thanks for the reply.
I have prepared the next part and will be released soon. Please read it as well if you have the time and let me know of any improvements.
Thanks for your time again :)