How To Install and Set Up Laravel, Nginx, and MySQL With Docker Compose on Ubuntu 20.04

Nwokolo Emmanuel
12 min readFeb 10, 2023

GOALS:

  • How to create a new user with sudo privileges
  • How to install docker & Install docker-compose
  • Cloning the laravel repo
  • How to create a Dockerfile & docker-compose.yml file
  • How to configure NGINX, PHP & MySQL
  • Installing composer & running the container

Holla!!!

in this blog, you are going to learn how to set up Laravel, Nginx,PHP and MySQL with docker-compose

this blog will teach you the fundamentals of how to “Set Up Laravel, Nginx, and MySQL With Docker Compose” by yourself

but if you want a well-detailed video explanation of a zoom mastermind class.

then you should CLICK HERE to reach me. see you soon!

prerequisite:

  • One Ubuntu 20.04 server, it can be created on any cloud provider like digital ocean. you can open an account and learn how to create a VM HERE

when you are done creating the server. you can come back here to continue.

but if you already have these things ready then let’s go!!

1. CREATE A NON-ROOT USER

in this section, we are going to create a non-root user with sudo privileges. we need to switch to our root user. do that using this command:

$ sudo su

NOTE: if you are using digital ocean. you are by default the root user.

now that you are on your root user. you need to create a new user with this command:

$ adduser teddi

the user can be named anything but in this blog, we name it teddi.

NOTE: After creating a user you need to add a password that you must not forget.

and also add the user name then you can leave the remaining path blank if you wish.

also when writing the password it won’t show on the screen.

Add User To Sudo Group

now we will need to add our user to sudo group using this command:

$ usermod -aG sudo teddi

Switch To Newly Created User

$ su teddi

NOTE: make sure you switch to the user created before going into the INSTALLING DOCKER section of this blog

2. INSTALLING DOCKER

in this section which is the second section, we will be installing docker.

First, update your existing list of packages:

$ sudo apt update

Next, install a few prerequisite packages which let apt use packages over HTTPS:

$ sudo apt install apt-transport-https ca-certificates curl software-properties-common

Then add the GPG key for the official Docker repository to your system:

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Add the Docker repository to APT sources:

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"

This will also update our package database with the Docker packages from the newly added repo.

Make sure you are about to install from the Docker repo instead of the default Ubuntu repo:

$ apt-cache policy docker-ce

Finally, install Docker:

$ sudo apt install docker-ce

Docker should now be installed, the daemon started, and the process enabled to start on boot. Check that it’s running:

$ sudo systemctl status docker

you should get an output like this:

Executing the Docker Command Without Sudo (Optional)

If you want to avoid typing sudo whenever you run the docker command, add your username to the docker group:

$ sudo usermod -aG docker ${USER}

To apply the new group membership, log out of the server and back in, or type the following:

$ su - ${USER}

You will be prompted to enter your user’s password to continue.

Confirm that your user is now added to the docker group by typing:

$ groups

If you need to add a user to the docker group that you’re not logged in as, declare that username explicitly using:

$ sudo usermod -aG docker username

3. INSTALLING DOCKER-COMPOSE

in this section we are going to be using a few commands to install docker-compose.

Verify the most recent version in their releases website first.

The most stable version is 1.29.2 as of this writing.

To make this program publicly accessible as docker-compose, run the command below to download the 1.29.2 version and store the executable file to /usr/local/bin/docker-compose:

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Next, set the correct permissions so that the docker-compose command is executable:

$ sudo chmod +x /usr/local/bin/docker-compose

To verify that the installation was successful, you can run:

$ docker-compose --version

You should see output similar to this:

Output
docker-compose version 2.16.0, build 6aecbg4cl

4. CLONING THE LARAVEL REPO

in this section, we will be cloning the laravel repository.

first, you need to check that you are in the home directory to clone laravel and you can do that with this command:

$ cd ~
$ git clone https://github.com/laravel/laravel.git laravel-app

Move into the laravel-app directory:

$ cd ~/laravel-app

As a final step, set permissions on the project directory so that it is owned by your non-root user:

$ sudo chown -R teddi:teddi ~/laravel-app

5. CREATING DOCKER COMPOSE & DOCKERFILE

in this section you will write a docker-compose file that defines your web server, database, and application services.

Open the file:

$ nano ~/laravel-app/docker-compose.yml

You must define three services in the docker-compose file: app, webserver, and database.

Replace the environment variable MYSQL ROOT PASSWORD under the db service with a secure password of your choice before adding the following code to the file:

version: '3'
services:

#PHP Service
app:
build:
args:
user: teddi
uid: 1000
context: .
dockerfile: Dockerfile
image: teddi/php
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
volumes:
- ./:/var/www
- ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- app-network

#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
volumes:
- ./:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/
- ./nginx-logs:/var/log/nginx
networks:
- app-network

#MySQL Service
db:
image: mysql:5.7.22
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_USER: ${DB_USERNAME}
volumes:
- dbdata:/var/lib/mysql/
- ./mysql/my.cnf:/etc/mysql/my.cnf
networks:
- app-network

#Docker Networks
networks:
app-network:
driver: bridge
#Volumes
volumes:
dbdata:
driver: local

NOTE: in the app section where the user is set as teddi. yours should also be set as the name of the user we created in this blog.

save this file and move to the next section.

DOCKERFILE

Your Dockerfile will be located in your ~/laravel-app directory. Create the file:

$ nano ~/laravel-app/Dockerfile

The Laravel application image will be created using the base image specified in this Dockerfile along with the required tasks and instructions.

the file with the following code:

FROM php:8.0.2-fpm

# Arguments defined in docker-compose.yml
ARG user
ARG uid

# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user

6. CONFIGURING PHP, NGINX & MYSQL

PHP

You can set up the PHP service to operate as a PHP processor for incoming requests from Nginx now that your infrastructure has been established in the docker-compose file.

The local.ini file must be created inside the php folder in order to configure PHP.

In Step 3, you bind-mounted this file to the container’s /usr/local/etc/php/conf.d/local.ini directory.

You can override the default php.ini file that PHP reads at startup by creating this file.

setting the PHP directory:

$ mkdir ~/laravel-app/php

Next, open the local.ini file:

$ nano ~/laravel-app/php/local.ini

You’ll add the following code to set size restrictions for uploaded files as an example of how to setup PHP:

#add this to the file

upload_max_filesize=40M
post_max_size=40M

Save the file, then close your editor.

After creating your PHP local.ini file, you may proceed to set up Nginx.

NGINX

You can change the Nginx service to utilize PHP-FPM as the FastCGI server to serve dynamic content after configuring the PHP service.

You must create an app.conf file with the service configuration in the /laravel-app/nginx/conf.d/ folder in order to configure Nginx.

Make the nginx/conf.d/ directory first:

$ mkdir -p ~/laravel-app/nginx/conf.d

Next, create the app.conf configuration file:

$ nano ~/laravel-app/nginx/conf.d/app.conf

Add the following code to the file to specify your Nginx configuration:

server {
listen 80;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
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;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
}

When you’re done editing, save the file and close your editor.

Any modifications you make inside the nginx/conf.d/ subdirectory will instantly take effect inside the webserver container because of the bind mount you set in Step 3.

You will then examine and set up your MySQL settings.

MYSQL

You can enable MySQL to function as the database for your application once PHP and Nginx have been set up.

To configure MySQL, you will create the my.cnf file in the mysql folder. This is the file that you bind-mounted to /etc/mysql/my.cnf inside the container in Step 2.

This bind mount allows you to override the my.cnf settings as and when required.

First, create the mysql directory:

$ mkdir ~/laravel-app/mysql

Next, make the my.cnf file:

$ nano ~/laravel-app/mysql/my.cnf

In the file, add the following code to enable the query log and set the log file location:

[mysqld]
general_log = 1
general_log_file = /var/lib/mysql/general.log

Save the file, then close your editor.

next, we move to containers.

7. Running the Containers and Changing Environment Parameters

You may now start the containers after defining all of your services in your docker-compose file and producing the configuration files for each.

Making a copy of the.env.example file that Laravel includes by default and renaming it to.env — the file Laravel expects to describe its environment — is the last step, though:

$ cp .env.example .env

You may now add specific information about your configuration to the.env file on the app container.

Open the file in nano or your preferred text editor:

$ nano .env

Locate the block that defines DB_CONNECTION and make the necessary changes to match your configuration. The following fields will be changed by you:

  • DB_HOST will be your db database container.
  • DB_DATABASE will be the laravel database.
  • DB_USERNAME will be the username you will use for your database. In this case, you will use teddi.
  • DB_PASSWORD will be the secure password you would like to use for this user account.
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=teddi
DB_PASSWORD=your_laravel_db_password

Save your changes and exit your editor.

Running the Application with Docker Compose

To create the application image and launch the services we specified in our configuration, we’ll now utilize docker-compose instructions.

Use the command below to create the app image:

$ docker-compose build app

This command might take a few minutes to complete. You’ll see output similar to this:

Building app
Sending build context to Docker daemon 377.3kB
Step 1/11 : FROM php:7.4-fpm
---> 8c08d993542f
Step 2/11 : ARG user
---> e3ce3af04d87
Step 3/11 : ARG uid
---> 30cb921ef7df
Step 4/11 : RUN apt-get update && apt-get install -y git curl libpng-dev libonig-dev libxml2-dev zip unzip
. . .
---> b6dbc7a02e95
Step 5/11 : RUN apt-get clean && rm -rf /var/lib/apt/lists/*
---> 10ef9dde45ad
. . .
Step 6/11 : RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
. . .
---> 920e4f09ec75
Step 7/11 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
---> dbbcd44e44af
Step 8/11 : RUN useradd -G www-data,root -u $uid -d /home/$user $user
---> db98e899a69a
Step 9/11 : RUN mkdir -p /home/$user/.composer && chown -R $user:$user /home/$user
---> 5119e26ebfea
Step 10/11 : WORKDIR /var/www
---> 699c491611c0
Step 11/11 : USER $user
---> cf250fe8f1af
Successfully built cf250fe8f1af
Successfully tagged travellist:latest

When the build is finished, you can run the environment in background mode with:

$ docker-compose up -d
Output
Creating db ... done
Creating app ... done
Creating webserver... done

This will run your containers in the background. To show information about the state of your active services, run:

$ docker-compose ps

You should see outputs like this:

  Name                 Command               State                              Ports                            
-----------------------------------------------------------------------------------------------------------------
app docker-php-entrypoint php-fpm Up 9000/tcp
db docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp,:::3306->3306/tcp
webserver /docker-entrypoint.sh ngin ... Up 0.0.0.0:443->443/tcp,:::443->443/tcp,
0.0.0.0:80->80/tcp,:::80->80/tcp

Your environment is now operational, but we still need to perform a few tasks to complete the application setup.

To run tasks in the service containers, such as a ls -l to display comprehensive information about files in the application directory, use the docker-compose exec command:

$ docker-compose exec app ls -l
-rw-rw-r--  1 james james  10452 Feb 10 01:22 CHANGELOG.md
-rw-rw-r-- 1 james james 739 Feb 10 01:46 Dockerfile
-rw-rw-r-- 1 james james 4158 Feb 10 01:22 README.md
drwxrwxr-x 7 james james 4096 Feb 10 01:22 app
-rwxrwxr-x 1 james james 1686 Feb 10 01:22 artisan
drwxrwxr-x 3 james james 4096 Feb 10 01:22 bootstrap
-rw-rw-r-- 1 james james 1817 Feb 10 01:22 composer.json
-rw-r--r-- 1 james james 294130 Feb 10 01:51 composer.lock
drwxrwxr-x 2 james james 4096 Feb 10 01:22 config
drwxrwxr-x 5 james james 4096 Feb 10 01:22 database
-rw-rw-r-- 1 james james 1254 Feb 10 01:29 docker-compose.yml
drwxrwxr-x 3 james james 4096 Feb 10 01:22 lang
drwxrwxr-x 2 james james 4096 Feb 10 01:27 mysql
drwxrwxr-x 3 james james 4096 Feb 10 01:26 nginx
-rw-rw-r-- 1 james james 286 Feb 10 01:22 package.json
drwxrwxr-x 2 james james 4096 Feb 10 01:26 php
-rw-rw-r-- 1 james james 1175 Feb 10 01:22 phpunit.xml
drwxrwxr-x 2 james james 4096 Feb 10 01:22 public
drwxrwxr-x 5 james james 4096 Feb 10 01:22 resources
drwxrwxr-x 2 james james 4096 Feb 10 01:22 routes
drwxrwxr-x 5 james james 4096 Feb 10 01:22 storage
drwxrwxr-x 4 james james 4096 Feb 10 01:22 tests
drwxr-xr-x 39 james james 4096 Feb 10 01:52 vendor
-rw-rw-r-- 1 james james 263 Feb 10 01:22 vite.config.js

now, check if you see anything like composer.lock in the outputs. it there isn’t any then you can move on with the next process.

but in this case, there is so we run this command to remove it:

$ docker-compose exec app rm -rf vendor composer.lock

We’ll now run composer install to install the application dependencies:

$ docker-compose exec app composer install

You should see outputs like this:

Output
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
. . .
Lock file operations: 89 installs, 0 updates, 0 removals
- Locking doctrine/inflector (2.0.4)
- Locking doctrine/instantiator (1.4.1)
- Locking doctrine/lexer (1.2.3)
- Locking dragonmantank/cron-expression (v2.3.1)
- Locking egulias/email-validator (2.1.25)
- Locking facade/flare-client-php (1.9.1)
- Locking facade/ignition (1.18.1)
- Locking facade/ignition-contracts (1.0.2)
- Locking fideloper/proxy (4.4.1)
- Locking filp/whoops (2.14.5)
. . .
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 89 installs, 0 updates, 0 removals
- Downloading doctrine/inflector (2.0.4)
- Downloading doctrine/lexer (1.2.3)
- Downloading dragonmantank/cron-expression (v2.3.1)
- Downloading symfony/polyfill-php80 (v1.25.0)
- Downloading symfony/polyfill-php72 (v1.25.0)
- Downloading symfony/polyfill-mbstring (v1.25.0)
- Downloading symfony/var-dumper (v4.4.39)
- Downloading symfony/deprecation-contracts (v2.5.1)
. . .
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.

Before testing the application, the last step is to create a special application key using the artisan Laravel command-line tool.

Users’ sessions and other sensitive data are encrypted using this key:

$ docker-compose exec app php artisan key:generate
Output
Application key set successfully.

As a final step, visit http://your_server_ip in the browser. The home page for your Laravel application will look like this:

it works!!!!!! you don’t need to add any port to it because we already specified a port 80 in the docker-compose.yml file.

PRO TIPS:

It's better to use a virtual machine provided by any cloud provider like digital ocean.

you can open an account and learn how to create a VM HERE

Reminder

if you want a well-detailed video explanation of the zoom mastermind class.

CHECK HERE

Resources

NOTE: if you have any questions or want to add to this blog. you can message me through E-mail.

I reply faster to people that are subscribed to my newsletter!!

Conclusion

If you loved this blog post give it a like, comment, and don’t forget to click on the follow button.

And if you would love to get an update on the two interesting blogs I will be posting this week then you should sign up for my newsletter right here!!

--

--

Nwokolo Emmanuel

I am a Cloud Engineer, I love sharing easy solutions to problems that I found difficult. Interested in Open Source | twitter: twitter.com/CloudTopG