How To Setup Laravel, Nginx, And MySQL With Docker Compose:

Barry

Docker has become a frequently used solution for implementing applications, since past few years. It simplifies implementing and running applications in ephemeral containers. When you are using a LEMP application stack, for instance, with MySQL, PHP, Nginx and also Laravel framework, Docker can surely streamline the setup process.

Docker Compose has also simplified the development process by giving the developers access to describe their application services, networks,infrastructures and volumes, in a single file. Docker Compose provides an enormous alternative for accessing multiple “docker” “container” “create” and “docker container run” commands.

JOIN OUR NEWSLETTER
Not Every One Focuses On Your Requirements! Get What You Want- Revenue & Ranking Both. Make Money While Stepping Up The Ladder Of SERPs.
We hate spam. Your email address will not be sold or shared with anyone else.

With the help of this blog , you will know how to build a web application with the help of Laravel framework , also Nginx as the web server and MySQL as the database, everything all together inside the Docker Containers . You will have to define all the stack configuration inside a “docker compose file” including configuration files like PHP , MySQL , and also Nginx.

One Ubuntu 18.04 server, and a non-root user with sudo privileges.

Downloading Laravel and Installing Dependencies

The very first step, you need to take is to get the latest version of Laravel and install the dependencies for the project, including Composer, the application-level package manager for PHP. You will have to install these dependencies with Docker to avoid having to install Composer universally. Firstly you need to check that you are in your home directory and you will have to clone the latest Laravel release to a directory named as laravel-app:

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

Then you need to move into the laravel-app directory.

$ cd ~/laravel-app

Next you will have to use Docker’s Composer image to mount the directories which you will need for your Laravel Project and will have to avoid the overhead of installing composer universally :

docker run --rm -v $(pwd):/app composer install

Using the -v and –rm flags with docker run creates an ephemeral container which will be bind-mounted to your current directory before it is removed which will copy the contents of your ~/laravel-app directory to the container and also ensure that the vendor folder Composer creates inside the container is copied to your current directory. After this as a final step , you will have to set permissions for the project directory so that it can be owned by your non-root user:

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

When you will write the Dockerfile, this will be the most important, for your application image in Step 4, as it will give you the allowance to work with your application code and run processes into your container as a non-root user. Now with your application code in place you can easily move on to describing your services with the Docker Compose.

Create the Docker Compose file:

When you are creating your applications with Docker Compose, it does simplify the process of setting up and versioning your infrastructure. To set up our Laravel application, we will write a docker-compose file that will describe your web server, database, and application services.

Then you will have to open the file :

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

In the Docker compose file, you will have to describe the three services : app, webserver and db. You need to add the following code to the file assuring to replace the root password for MYSQL_ROOT_PASSWORD ,which will be described as an environmental variable under the db service , with a password which should be quite protective and strong and of course of your choice

~/laravel-app/docker-compose.yml
version: '3'
services:

#PHP Service
app:
build:
context: .
dockerfile: Dockerfile
image: digitalocean.com/php
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
networks:
- app-network

#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
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: laravel
MYSQL_ROOT_PASSWORD: your_mysql_root_password
SERVICE_TAGS: dev
SERVICE_NAME: mysql
networks:
- app-network

#Docker Networks
networks:
app-network:
driver: bridge

The above defined includes:

  • App: This particular service by definition contains the Laravel application and runs on a custom Docker image, digitalocean.com/php,. It also sets the working_dir in the container to /var/www.
  • Webserver: This service by description pulls the nginx:alpine image from Docker and exposes ports 80 and 443.
  • db: This service definition pulls the mysql:5.7.22 image from Docker and defines a few environmental variables, including a database called laravel for your application and the root password for the database. You will be then free to name the database whatever you would like, and you must replace your_mysql_root_password with your own prominent password. This service definition also maps port 3306 on the host to port 3306 on the container.

Every container_name property explains a name for the container, which have a close similarity to the name of the service. If you don’t define this particular property, Docker will surely assign a name to each container by mixing a historically famous person’s name and a random word separated by an underscore.

To make an easier communication between containers, the services are connected to a bridge network named as app-network. A bridge network uses a software bridge that allows containers connected to the same bridge network to communicate with each other. The bridge driver automatically installs rules in the host machine so that containers on different bridge networks cannot connect directly with each other. This builds a higher level of security for applications, assuring that only related services can communicate with one another. It also means that you can define multiple networks and services connecting to related functions: front-end application services can use a frontend network, for example, and back-end services can use a backend network.

Now coming to the fact of how you can add volumes and bird mounts to your piece of service definitions to persist your application data.

Persisting Data

Now coming to the fact of how you can add volumes and bird mounts to your piece of service definitions to persist your application data . Docker has got strong and convenient features for persisting data . In your application use the volumes and bind mounts for persisting the database and for the application and configuration files.

What Volumes does is offers enough pliability for backups and persistence beyond a container’s lifecycle, while bind mounts makes it easy to code which changes during the development, making changes to your host files or directories certainly available in your containers. Your setup will make use of both. However one particular thing you might note that with the use of bind mounts you can change the host filesystem through processes running in a container , including creating , modifying or you can also delete important system or directories. Therefore , you need to use the bind mounts with care as with the use of this powerful ability that comes with security implications , can definitely affect or have an impact upon non-Docker processes on the host system. Then in the docker-compose file, you will have to define a volume called dbdata under the db service definition to persist the MySQL database:

~/laravel-app/docker-compose.yml
...
#MySQL Service
db:
  ...
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - app-network
  ...

The named volume dbdata persists the contents of the /var/lib/mysql folder present inside the container. This will allows you to stop and restart the db service without losing data.

At the bottom of the file, add the definition for the dbdata volume:

~/laravel-app/docker-compose.yml
...
#Volumes
volumes:
  dbdata:
    driver: local

With this definition in place, you will be able to use this volume across services, then you will have to add a bind mount to the db service for the MySQL configuration files you will create in Step 7:

~/laravel-app/docker-compose.yml
...
...
#MySQL Service
db:
  ...
    volumes:
      - dbdata:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
  ...

This bind mount binds ~/laravel-app/mysql/my.cnf to /etc/mysql/my.cnf in the container. Then add on the bind mounts to the webserver service, one for your application code and the other for the Nginx configuration definition .

~/laravel-app/docker-compose.yml
#Nginx Service
webserver:
  ...
  volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
  networks:
      - app-network

The first bind mount will bind the application code in the ~/laravel-app directory to the /var/www directory inside the container. The configuration file that you will add to ~/laravel-app/nginx/conf.d/ will also be mounted to /etc/nginx/conf.d/ in the container, allowing you to add or modify the configuration directory’s contents as needed. The final step for you will be to add the following bind mounts to the app service for the application code and configuration files:

~/laravel-app/docker-compose.yml
#PHP Service
app:
  ...
  volumes:
       - ./:/var/www
       - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
  networks:
      - app-network

The app service is bind-mounting the ~/laravel-app folder, which contains the application code, to the /var/www folder in the container. This will speed up the development process, as any changes made to your local application directory will be instantly reflected inside the container. You are also binding your PHP configuration file, ~/laravel-app/php/local.ini,
to /usr/local/etc/php/conf.d/local.ini inside the container. You will have to create the local PHP configuration. After this your docker-compose will look like the following:

~/laravel-app/docker-compose.yml
version: '3'
services:

  #PHP Service
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: digitalocean.com/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/
    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: laravel
      MYSQL_ROOT_PASSWORD: your_mysql_root_password
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    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

Now you will have to save the file and then you will have to exit the editor you use once you are done with editing and now with your docker-compose file written you can create the custom image of your application.

Dockerfile Creation

You can specify the environment inside of an individual containers with the help of Dockerfile allowerd by Docker. A Dockerfile helps you to create custom images that you can use to install the software required by your application and configure settings based on your requirements. You can now push the custom images you create to Docker Hub or any private registry.

Our Dockerfile will be located in our ~/laravel-app directory. Now you will have to create this file : nano ~/laravel-app/Dockerfile. After the successful creation this Dockerfile will set the base image and will specify the important commands and instructions, which will to create the Laravel application image. You now need to add the following code to the file:

~/laravel-app/php/Dockerfile
FROM php:7.2-fpm

# Copy composer.lock and composer.json
COPY composer.lock composer.json /var/www/

# Set working directory
WORKDIR /var/www

# Install dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    mysql-client \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl

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

# Install extensions
RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd

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

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Copy existing application directory contents
COPY . /var/www

# Copy existing application directory permissions
COPY --chown=www:www . /var/www

# Change current user to www
USER www

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

First, the Dockerfile creates an image on top of the php:7.2-fpm  Docker image. This is a Debian-based image that has the PHP FastCGI implementation PHP-FPM installed. The file also installs the prerequisite packages for Laravel: mcrypt, pdo_mysql, mbstring, and imagick with composer. The RUN directive specifies the commands to update, install, and configure settings inside the container, including creating a dedicated user and group called www. The WORKDIR instruction specifies the /var/www directory as the working directory for the application.

Creating a dedicated user and group with restricted permissions mitigates the inherent vulnerability when running Docker containers, which run by default as root. Instead of running this container as root, you can create the www user, who has read/write access to the /var/www folder thanks to the COPY instruction that we are using with the –chown flag to copy the application folder’s permissions and finally, the EXPOSE command exposes a port in the container, 9000, for the php-fpm server. CMDspecifies the command that should run once the container is created. Here, CMD specifies “php-fpm”, which will start the server. Now you will have to save the file and exit your editor when you have finished editing and then move on to define your PHP configuration.

Configure the PHP

You can configure the PHP service to act as a PHP processor for incoming requests from Nginx.

once you have defined your infrastructure in the docker-compose file. You will have to create the local.ini file inside the php folder, in order to configure PHP,and this is the same file file that you bind-mounted to /usr/local/etc/php/conf.d/local.ini inside the container. After you create this file, it will allow you to override the default php.ini file that PHP reads when it will start.

Then you will have to create the php directory:

$ mkdir ~/laravel-app/php,

then you will have to open the local.ini file:

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

In order to demonstrate how to configurePHP, you need to add the following code to set size limitations for the already uploaded files:

~/laravel-app/php/local.ini
upload_max_filesize=40M
post_max_size=40M

The upload_max_filesize and post_max_size directives set the maximum allowed size for uploaded files, and demonstrate how you can set php.ini configurations from your local.ini file. You can also put any PHP-specific configuration that you want to override in the local.ini file and now you can save the file and exit your editor and with your PHP local.ini file in place, you can now move on to configuring Nginx.

Configuring Nginx

After the PHP service is configured, you can now modify the Nginx service to use PHP-FPM as the FastCGI server to serve dynamic content. The Fast CGI server is generally based on a binary protocol for interfacing interactive programs with a web server.

To configure Nginx, you will create an app.conf file with the service configuration in the ~/laravel-app/nginx/conf.d/ folder.

First , create the nginx/conf.d/ directory:

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

The next step is you need to create the 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:

~/laravel-app/nginx/conf.d/app.conf
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;
    }
}

What function does the server block have?

The Server Block defines the configuration for the Nginx web server with the following directives:

  • listen: This directive defines the port on which the server will listen to incoming requests.
  • error_log and access_log: These directives define the files for writing logs.
  • root: This directive sets the root folder path, forming the complete path to any requested file on the local file system.

In the php location block, the fastcgi_pass directive specifies that the app service is listening on a TCP socket on port 9000. This makes the PHP-FPM server listen over the network rather than on a Unix socket. Though a Unix socket has a slight advantage in speed over a TCP socket, it does not have a network protocol and thus skips the network stack. For cases where hosts are located on one machine, a Unix socket may make sense, but in cases where you have services running on different hosts, a TCP socket offers the advantage of allowing you to connect to distributed services. Because our app container is running on a different host from our webserver container, a TCP socket makes the most sense for our configuration. Now you need to save the file and exit your editor when you are done with editing . The bind mount you created, any changes you make inside the nginx/conf.d/ folder will be directly reflected inside the webserver container.

Configuring MySQL

The next step we will look into is at our MySQL settings. After you configure PHP and Nginx, you can enable MySQL to act as the database for your application. To configure MySQL, yiu will have to 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. This bind mount will allows you to override the my.cnf settings as and when required. In order to demonstrate how it works, you need to add settings to the my.cnf file that will ensure the general query log and specially the log file. So first you will,

have to create the mysql directory:

$ mkdir ~/laravel-app/mysql

The next step you need to take, is to make the my.cnf file :

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

In this particular file you will have to add the following code in order to enable the query log and set the log file location:

~/laravel-app/mysql/my.cnf
[mysqld]
general_log = 1
general_log_file = /var/lib/mysql/general.log

This my.cnf file enables logs, defining the general_log setting as 1 to allow general logs. The general_log_file setting specifies where the logs will be stored. And now you need to save the file and exit your editor and then we will have to start the containers.

Running the Containers and Modifying Environment Settings

After you have defined all of your services in your docker-compose file and created the configuration files for these services, you can start the containers. As a final step, though, we will make a copy of the .env.example file that Laravel includes by default and name the copy .env, which is the file Laravel expects to define its environment:

cp .env.example .env

Then you will have to configure the specific details of our setup inthis file once you have started the containers.

Finally with all of your services defined in your docker-compose file , now you just need to issue a single command to start all of the containers, create the volumes , and set up and connect the networks:

docker-compose up –d.

Now when you run docker-compose for the first time, it will download all of the necessary Docker images, which might take a while. Once the images are downloaded and stored in your local machine, Compose will create your containers. The -d flag daemonizes the process, running your containers in the background.

Once the process is complete, use the following command to list all of the running containers:

$ docker ps

You will see the following output with details about your app, webserver, and db containers:

Output
CONTAINER ID  NAMES      IMAGE                  STATUS        
c31b7b3251e0  db         mysql:5.7.22           Up 2 seconds  
ed5a69704580  app        digitalocean.com/php   Up 2 seconds  
5ce4ee31d7c0  webserver  nginx:alpine           Up 2 seconds  

PORTS
0.0.0.0:3306->3306/tcp
9000/tcp
0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp

The CONTAINER ID in this output is a unique identifier for each container, while NAMES lists the service name associated with each. You can use both of these identifiers to access the containers. IMAGE defines the image name for each container, while STATUS provides information about the container’s state: whether it’s running, restarting, or stopped.

Now you can modify the .env file on the app container to include specific details about your setup.

Open the file using docker-compose exec, which allows you to run specific commands in containers. In this case, you are opening the file for editing:

docker-compose exec app nano .env

Now you need to identify the block that specifies DB_CONNECTION and update it to reflect the specifics of your setup. You will modify the following fields:

  • 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, we will use laraveluser.
  • DB_PASSWORD will be the secure password you would like to use for this user account.
/var/www/.env
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laraveluser
DB_PASSWORD=your_laravel_db_password

Now you need to save your changes and then exit your editor.

Next, set the application key for the Laravel application with the php artisan key:generate command. This command will generate a key and copy it to your .env file, ensuring that your user sessions and encrypted data remain secure:

$ docker-compose exec app php artisan key:generate

You now have the environment settings required to run your application. To cache these settings into a file, which will boost your application’s load speed, run:

$ docker-compose exec app php artisan config:cache

After this your configuration settings will be loaded into/var/www/bootstrap/cache/config.php on the container.

As a final step, visit http://your_server_ip in the browser. You will see the following home page for your Laravel application:

Image Source: https://do.co/2AlX6Ok

With your containers running and your configuration information in place, you can move on to configuring your user information for the laravel database on the db container.

Create a User for MySQL

The root administrative account is only created with the default MySQL installation. |It has got unlimited privileges on the database server. However, it is actually better to avoidIn general, it’s better to avoid root administrative account especially when you are interacting with the database. Instead of that you can create a dedicated database user for your own application’s Laravel database.

if you want to create a ne user , you need to execute an communi8cative bash shell on the db container with the help of docker-compose exec:

$ docker-compose exec db bash

Then you need log into the MySQL root administrative account:

root@c31b7b3251e0:/# mysql –u root –p

After this you will be promoted that you have set for the MySQL root account during installation in your docker-compose file.

You need to start by checking on to the database known as laravel, that you have had defined in your docker-compose file. Then run the show databases command to check for existing databases:

mysql> show databases;

Now you will get to see the laravel database that has been listed in the output:

Output
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

The next step you will have to take is to create the user account which will be allowing access to this database. The username will be laravel user, however, you can replace this with another name of your preference (optional). Just be sure that your username and password here match the details you set in your .env file in the previous step:

mysql> GRANT ALL ON laravel.* TO 'laraveluser'@'%' IDENTIFIED BY 'your_laravel_db_password';

Then you will have to flush the privlages to notify the MySQL server of the changes:

mysql> FLUSH PRIVILEGES; 

Now you wil have to exit MySQL:

mysql> EXIT;

finally you can exit the container:

root@c31b7b3251e0:/# exit

Now as you have configurd the user account for your Laravel Application the database are ready for migrating your data and work with the Tinker console.

Working for the Tiniker Console and Migrating Data

With your application running, You can migrate your data and experiment with the tinker command, with your application running which will initiate a  PsySH console with Laravel preloaded. PsySH is a runtime interactive debugger for PHP and developer console, and Tinker is a REPL specifically for Laravel. Using the tinker command will you can interact with your Laravel application from the command line in an interactive shell.

First, test the connection to MySQL by running the Laravel artisan migrate command, which creates a migrations table in the database from inside the container:

$ docker-compose exec app php artisan migrate

This command will help to migrate the default Laravel tables. The output confirming the migration will then look like this:

Output

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

Once the migration is complete, you can now run a query to check if you are properly connected to the database using the tinker command:

$ docker-compose exec app php artisan tinker

Then you will have to test the MySQL connection by fetching the data you just migrated:

>>> \DB::table('migrations')->get();

you will see an output like this:

Output
=> Illuminate\Support\Collection {#2856
     all: [
       {#2862
         +"id": 1,
         +"migration": "2014_10_12_000000_create_users_table",
         +"batch": 1,
       },
       {#2865
         +"id": 2,
         +"migration": "2014_10_12_100000_create_password_resets_table",
         +"batch": 1,
       },
     ],
   }

You can use tinker to interact with your databases and to experiment with services and models.

Now with your Laravel application in place, you are absolutely ready for the further development and experimentation.

Conclusion

You now have a complete idea about LEMP stack application running on your server, which you’ve tested by accessing the Laravel welcome page and creating MySQL database migrations. The best way of this installation is Docker Compose, which helps you to create a group of Docker containers, defined in a single file , with a single command. Key to the simplicity of this installation is Docker Compose, which allows you to create a group of Docker containers, defined in a single file, with a single command.

Header Image Source: https://bit.ly/2SgXRjc

mm

Barry Davis is a Technology Evangelist who is joined to Webskitters for more than 5 years. A specialist in Website design, development & planning online business strategy. He is passionate about implementing new web technologies that makes websites perform better.

Facebooktwittergoogle_pluspinterestlinkedin

Interested in working with us?

We'd love to hear from you
Webskitters LLC
7950 NW 53rd St #337 Miami, Florida 33166
Phone: 732.218.7686