How to Setup LEMP Stack on Docker

In this tutorial we will setup a LEMP stack inside a docker. We can easily pull and setup LEMP image inside a docker in a couple of minutes. But for better understanding of the stack, we will setup each of the images NGINX, MySQL ,PHP/PHPMyAdmin one by one. Once you grasp the core idea of LEMP stack in a docker then you can try to setup the stack in a docker as a single image.

To install/setup LEMP in a docker , we need to install docker and docker compose at first. Execute the following command to install these two.

$ apt-get install docker docker-compose

Start the docker service

$ service docker start

Folder Setup

We will use the following folder structure for our LEMP stack.

  1. Create a root directory by the name LEMP-Docker and a single file docker-compose.yml inside it
  2. For NGINX logs, create a directory by the name "logs" inside LEMP-Docker and create two files nginx-access.log and nginx-error.log inside logs directory.
  3. For NGINX config file, create a directory by the name "sites-available" inside LEMP-Docker and create a default config file.
  4. At last for your PHP files create a directory by the name public_html inside LEMP-Docker. We will place PHP files in this directory at later stage.

So our directory structure looks like the following-

├── docker-compose.yml
├── logs
│   ├── nginx-access.log
│   └── nginx-error.log
├── public_html
│   └── index.php
└── sites-available
└── default

Let us create the above directory structure and files quickly.

$ cd ~
$ mkdir LEMP-Docker
$ cd LEMP-Docker/
$ mkdir logs
$ touch logs/nginx-access.log logs/nginx-error.log
$ mkdir sites-available
$ touch sites-available/default
$ mkdir public_html
$ touch docker-compose.yml

Next we will edit the NGINX default config "default" and add the following. In the server_name directive, place the IP address of the docker. To find it use the command $ ifconfig  and look for the line inet addr: in the row docker*

$ vi sites-available/default

server {
listen  80;
# this path MUST be exactly as docker-compose.fpm.volumes,
# even if it doesn't exists in this dock.
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html$is_args$args;

Install NGINX

To setup Nginx container, we will add the following to the docker-compose.yml file

$ vi docker-compose.yml
    image: tutum/nginx
      - "80:80"
      - ./sites-available/default:/etc/nginx/sites-available/default
      - ./sites-available/default:/etc/nginx/sites-enabled/default
      - ./logs/nginx-error.log:/var/log/nginx/error.log
      - ./logs/nginx-access.log:/var/log/nginx/access.log

The above docker-compose.yml will pull the NGINX image from tutum/nginx and proxy port 80 from the container to port 80 of docker host.  Then we will mount default NGINX configuration file from nginx/default directory to sites-available and sites-enabled folders of nginx container.  Similary we will link the logs/*.logs from the docker host to logs inside the container.

Now from within the CWD, run the following command to pull and create NGINX container.

$ docker-compose up -d
Pulling nginx (tutum/nginx:latest)...
latest: Pulling from tutum/nginx
faecf96fd5ab: Pull complete
995977506e98: Pull complete
Digest: sha256:69a727916ab40de88f66407fb0115e35b759d7c6088852d901208479bec47429
Status: Downloaded newer image for tutum/nginx:latest

To check that the NGINX container is actually created, execute the following command.

$ docker ps

To create a tunnel into the NGINX container and to get a bash shell, use the following command.

$ docker exec -i -t 75088fe8fe60 bash

Now find the NGINX processes

$ ps -aux | grep NGINX

root         1  0.0  0.6  85892  6572 ?        Ss   07:13   0:00 nginx: master process /usr/sbin/nginx
www-data     6  0.0  0.3  86212  3428 ?        S    07:13   0:00 nginx: worker process
www-data     7  0.0  0.3  86212  3428 ?        S    07:13   0:00 nginx: worker process
www-data     8  0.0  0.3  86212  3428 ?        S    07:13   0:00 nginx: worker process
www-data     9  0.0  0.3  86212  3428 ?        S    07:13   0:00 nginx: worker process

Exit from the NGINX container bash shell

$ exit

Finally open your favorite browser, point it to http://<DOCKER_HOST_IP>. you will be greeted with NGINX welcome page like below.

NGINX home page

Stop and remove the container for the time-being.

$ docker stop 75088fe8fe60
$ docker rm 75088fe8fe60

Install PHP/FPM

In order to PHP work with nginx, we need PHP with FPM module enabled. Therefore we will edit docker-compose.yml and select the image php:fpm specifying ports for FPM, which is 9000 and mount the public_html directory of docker host  to /usr/share/nginx/html directory of PHP-FPM container

     image: php:fpm
       - "9000:9000"
       - ./public_html:/usr/share/nginx/html

We will starts the containers in the background and leaves them running with the following command.

$ docker-compose up -d

Pulling phpfpm (php:fpm)...
fpm: Pulling from library/php
75a822cd7888: Pull complete
e4d8a4e038be: Pull complete
Digest: sha256:01d1d6345bc30f206fb048c5f1afa882f17f54f275ffe7fd5a83524d8294bc79
Status: Downloaded newer image for php:fpm

Check the running container with the following command, php-fpm is running in the port 9000 and mapped to port 9000 of docker host.

$ docker ps -a

Create a tunnel into the PHP-FPM container and to get a bash shell, use the following command.

$ docker exec -i -t 841f20b8e7e3 bash

Check the processes inside the container with the following command.

$ ps -aux

Once  we are sure that PHP-FPM container is running, we will modify the default NGINX configuration file so that it can process the PHP files. Add the following section in the file sites-available/default after location / { } section.

location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

So our final NGINX default configuration file looks like this-

NGINX default config

Next create a file index.php inside public_html and add the following and restart PHP-FPM docker.

root@demohost:~/public_html# vi index.php

echo phpinfo();

Restart php-fpm container.

$ docker restart lempdocker_phpfpm_1

Point your browser to http://<DOCKER-HOST-IP>/index.php , the phpinfo page will be displayed


Install MySQL/PhpMyAdmin

In the last step we will setup MySQL and PhpMyAdmin container inside the docker host. Edit the file docker-compose.yml and add the following section

image: mariadb

image: corbinu/docker-phpmyadmin
restart: always
- mysql:mysql
- 8183:80

In the  mysql section, we will fetch mariadb image and passing an environment variable call MYSQL_ROOT_PASSWORD to the images. The image will take this value as a password when it constructs the MySQL container. In the phpmyadmin section, fetch the image from corbinu/docker-phpmyadmin and link this container with mysql container's mysql database. The docker host's port no 8183 will be proxies to port 80 of container (NGINX). Set the environment of phpmyadmin's user name as root and password as 'password'.

Start the containers in the background and leaves them running with the following command. Don't forget to stop the running containers before executing the following command.

$ docker-compose up -d
Pulling mysql (mariadb:latest)...
latest: Pulling from library/mariadb
75a822cd7888: Already exists
b8d5846e536a: Pull complete

Digest: sha256:0ce9f13b5c5d235397252570acd0286a0a03472a22b7f0384fce09e65c680d13
Status: Downloaded newer image for mariadb:latest

Pulling phpmyadmin (corbinu/docker-phpmyadmin:latest)...
latest: Pulling from corbinu/docker-phpmyadmin
04c460fac791: Pull complete
0a0916b29f3e: Pull complete
b25f4e7a7766: Pull complete
44f124587a1d: Pull complete
Digest: sha256:8ba64f73a8f28f62c00c9515171361b9d5f7a0c6f6fa0aa4418839f9974d098d
Status: Downloaded newer image for corbinu/docker-phpmyadmin:latest

Now list all the running containers with the following docker command.

$ docker ps -a

Create a tunnel into the MySQL container and get the mysql shell by passing username and password

$ docker exec -i -t f63eadc48545 mysql -u root -ppassword
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.1.20-MariaDB-1~jessie binary distribution
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>
MariaDB [(none)]> show databases;
| Database           |
| information_schema |
| mysql              |
| performance_schema |
3 rows in set (0.00 sec)

To get the phpmyadmin login page, point your browser to http://<DOCKER_HOST_IP>:8183/  Type username as root and password as password, you will be redirected to phpmyadmin's homepage.


Let us now try to understand flow within the stack quickly. The IP address of NGINX is set to the docker host and the default config set the web root to /usr/share/nginx/html. Apart from it, the config also mounts volumes from docker host that includes files from sites_available and log directory to container filesystem's /etc/nginx/sites-available and /var/log/nginx directory in volume: section of docker-compose.yml. (NGINX section). The docker.compose.yml also sets up NGINX to understand PHP via PHP-FPM module and the base path for PHP source files are defined which is public_html directory (PHP-FPM section). MySQL will use its default port 3306 and linked to NGINX server so that PHP application knows where to establish a database connection. The phpmyadmin can be accessed through port no 8083 of docker host which is mapped to port no 80 of the container. The link between MySQL and phpmyadmin is done in the links section specifying the the database name as mysql (mysql:mysql).

We have installed LEMP stack as a container one by one. The process is bit time consuming. Once you are confident about the setup that we have finished just now then you can install docker the stack from a single image. Check out dockerhub to explore all images. To do that use the following commands from the terminal.

$ docker search LEMP

$ docker pull <IMAGE-Name>

and then use docker run command passing all the parameters/configs that you need, your LEMP stack inside a container will be up in a few minutes.


Now since we have learn how to containerized NGINX, MySQL, PHP and know how to link them and mount volumes from host's file-system to container file-system, you can now add a CMS of your choice like Joomla, Drupal or WordPress in the container to extend the functionality of the stack by extending docker-compose.yml.

2 Comments... add one

Leave a Comment