Creating a LEMP stack with Docker Compose

Share:
Source Code

In this tutorial, we’ll set up a LEMP stack via Docker and Docker Compose.  If you need a refresher on what Docker is capable of, I recommend you to checkout the following first:

I will be demonstrating the power of Docker with a local OSX installation of Docker, but you are free to spin up a pre-installed Docker droplet from Digital Ocean.

LEMP stack stands for Linux Nginx Mysql and PHP.  It is a bit different from its popular cousin LAMP (Linux, Apache, Mysql, PHP).  The reason I like Nginx instead of Apache is purely due to my familiarity with it, I’m confident after this tutorial, you’ll be able to switch Nginx for Apache with ease if you choose to.

Set up

We need to set up folder structure for our project.

1. Create a root folder DockerLEMP with a single file: docker-compose.yml  

2. Create a folder in the same directory call logs and place 2 files there: nginx-access.log and nginx-error.log, these will be our server access & log files we will set up through volume.

3. Create an nginx folder with a file name default, this will be our default nginx config file.

(Note: about server_name line, if you use docker for mac, you can get your IP via docker machine’s command: docker-machine ip, otherwise just enter your server IP address)

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.php index.html index.html;
    
    server_name 192.168.99.100;

    location / {
        try_files $uri /index.php$is_args$args;
    }

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

 

4. Create a public folder with a file call index.php, this is where we place our project source code.

<?php
phpinfo();

Your complete folder structure should look like this:

Screen Shot 2016-07-08 at 1.46.14 PM

Linux & Nginx

We first need to set up a Linux and Nginx container. Add the following to your docker-compose.yml file:

nginx:
    image: tutum/nginx
    ports:
        - "80:80"
    links:
        - phpfpm
    volumes:
        - ./nginx/default:/etc/nginx/sites-available/default
        - ./nginx/default:/etc/nginx/sites-enabled/default

        - ./logs/nginx-error.log:/var/log/nginx/error.log
        - ./logs/nginx-access.log:/var/log/nginx/access.log

In this config file, we want Docker to

  • Pull an nginx image from tutum/nginx
  • Proxy port 80 from within the container to port 80 of host
  • Create a link to our PHP container named phpfpm
  • Mount the default nginx server configuration file from our /nginx/default directory to sites-available and sites-enabled folders of nginx container
  • Mount our logs to the appropriate log folder of nginx container

PHP

In order for PHP to work with nginx, we need to use a special version of it with PHP-FPM module enabled, add the following to your docker-compose.yml file:

phpfpm:
    image: php:fpm
    ports:
        - "9000:9000"
    volumes:
        - ./public:/usr/share/nginx/html

In this config file, we want Docker to:

  • Fetch a PHP with FPM module container from Docker Hub
  • Proxy port 9000 from the container to host
  • Mount the content of /public folder into the default root server directory /usr/share/nginx/html

MYSQL

For MySQL, we’ll be using a community maintained fork of MYSQL called mariadbadd the following to your docker-compose.yml file:

mysql:
  image: mariadb
  environment:
    MYSQL_ROOT_PASSWORD: admin

In this config file, we want Docker to:

  • Fetch mariadb image from Docker Hub
  • Pass in an environment variable call MYSQL_ROOT_PASSWORD to the image, the image will take this value as the password when it constructs the mysql container.

PHPMYADMIN

Just for fun, we’ll install PHPMyAdmin to our stack so we can manipulate the database with ease.  Add the following to docker-compose.yml:

phpmyadmin:
  image: phpmyadmin/phpmyadmin
  restart: always
  links:
    - mysql
  ports:
    - 8183:80
  environment:
    MYSQL_USERNAME: admin
    MYSQL_ROOT_PASSWORD: admin
    PMA_ARBITRARY: 1

In this config file, we want Docker to:

  • Fetch phpmyadmin image from Docker hub
  • Set restart to always so it will always restart when something errors out
  • Links this container with our mysql container
  • Proxy port 80 within our contain to port 8183 of our host
  • Set a few environment variables for our phpmyadmin setup

The Final docker-compose.yml

Your final docker-compose.yml should look like this:

nginx:
    image: tutum/nginx
    ports:
        - "80:80"
    links:
        - phpfpm
    volumes:
        - ./nginx/default:/etc/nginx/sites-available/default
        - ./nginx/default:/etc/nginx/sites-enabled/default

        - ./logs/nginx-error.log:/var/log/nginx/error.log
        - ./logs/nginx-access.log:/var/log/nginx/access.log
phpfpm:
    image: php:fpm
    ports:
        - "9000:9000"
    volumes:
        - ./public:/usr/share/nginx/html
mysql:
  image: mariadb
  environment:
    MYSQL_ROOT_PASSWORD: admin
phpmyadmin:
  image: phpmyadmin/phpmyadmin
  restart: always
  links:
    - mysql
  ports:
    - 8183:80
  environment:
    MYSQL_USERNAME: admin
    MYSQL_ROOT_PASSWORD: admin
    PMA_ARBITRARY: 1

The Flow

Now that we have our images definition set up, we need to talk about how all the apps are able to talk to each other.

Nginx has a default server config set up to serve files from /usr/share/nginx/html, it creates a virtual server host with your host IP, in my case my ip is 192.168.99.100 

server_name 192.168.99.100;

The config file also sets up nginx to understand PHP directives via the PHP-FPM module and it defines a base path to serve your website’s source code for PHP.

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

MYSQL is set up to the default port of 3306 and it is LINKED to our nginx server so the PHP application can understand where to make database connection.

Our workflow then becomes extremely nice and simple because we were able to mount volumes from directories we defined in our docker-compose.yml file, so for the files under public under your local directory, it was mounted to the root directory for the server, which means whenever you add/remove/update any file locally, it will also be updated instantly within the server containers! Same goes for nginx config or any other files you defined as volumes for your containers.

Take it for a spin

Let’s see if our hard work paid off, go to the root directory of your project, and run:

docker-compose up -d

If you did everything correctly, you should see the following after running:

docker ps

docker compose up LEMP

Open your browser and head to your server IP, in my case it’s https://192.168.99.100/ and you should see this:

Screen Shot 2016-07-08 at 1.30.09 PM

This is the content of our public/index.php file, which is a simple call to phpinfo(); Sweet!

To access phpmyadmin you just need to head over to https://192.168.99.100:8183/

Screen Shot 2016-07-08 at 1.31.45 PM

and lastly, if you want to access mysql from command line, you can do this in the terminal:

docker-compose exec mysql sh

This will create a tunnel into the mysql container and runs the bash command, next you run:

mysql -u root -p

and enter the password defined in docker-compose.yml file and you will be able to get in to mysql!

Screen Shot 2016-07-08 at 1.34.08 PM

Neato! You can do this with any of your containers should you need to do any command line work.

That’s it, you now have a LEMP stack set up for development, testing and deployment with a single command!

I hope you find this lesson useful, and it was my honor to walk you through Docker and the game changing technology it brought to the developer community! Drop me a line at @whyzhi if you have any comments and questions.

Comments Or Questions? Discuss In Our Discord

If you enjoyed this tutorial, make sure to subscribe to our Youtube Channel and follow us on Twitter @pentacodevids for latest updates!

More from PentaCode