Use Docker to Build a LEMP Stack (Buildfile)
I’ve been reviewing Docker recently. As part of that review, I decided to build a LEMP stack in Docker. I use Vagrant to create an environment in which to run Docker. For this experiment I chose to create Buildfiles to create the Docker container images. I’ll be discussing the following files in this post.
Vagrantfile bootstrap.sh mysql/Dockerfile mysql/mysqlpwdseed nginx/Dockerfile nginx/default nginx/wall.php |
Download the Docker LEMP files as a zip (docker-lemp.zip).
Spin up the Host System
I start with Vagrant to spin up a host system for my Docker containers. To do this I use the following files.
Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "ubuntu/trusty64" config.vm.network "public_network" config.vm.provider "virtualbox" do |v| v.name = "docker LEMP" v.cpus = 1 v.memory = 512 end config.vm.provision :shell, path: "bootstrap.sh" end |
I start with Ubuntu and keep the size small. I create a public network to make it easier to test my LEMP setup later on.
bootstrap.sh
#!/usr/bin/env bash # set proxy variables #export http_proxy=http://proxy.example.com:8080 #export https_proxy=https://proxy.example.com:8080 # bootstrap ansible for convenience on the control box apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" apt-get update apt-get -y install lxc-docker # need to add proxy specifically to docker config since it doesn't pick them up from the environment #sed -i '$a export http_proxy=http://proxy.example.com:8080' /etc/default/docker #sed -i '$a export https_proxy=https://proxy.example.com:8080' /etc/default/docker # enable non-root use by vagrant user groupadd docker gpasswd -a vagrant docker # restart to enable proxy service docker restart |
I’m working in a proxied environment, so I need to provide proxy details to the Vagrant host for subsequent Docker installation steps. Unfortunately Docker doesn’t key off the typical environment variables for proxy, so I have to define them explicitly in the docker configuration. This second proxy configuration allows Docker to download images from DockerHub. Finally I create a docker group and add the vagrant user to it so I don’t need sudo for docker commands.
At this point, it’s super easy to play around with Docker and you might enjoy going through the Docker User Guide. We’re ready to move on to create Docker container images for our LEMP stack.
Build the LEMP Stack
This LEMP stack will be split across two containers, one for MySQL and the other for Nginx+PHP. I build on Ubuntu as a base, which may increase the total size of the image in exchange for the ease of using Ubuntu.
MySQL Container
We’ll start with MySQL. Here’s the Dockerfile.
Dockerfile
# LEMP stack as a docker container FROM ubuntu:14.04 MAINTAINER Daniel Watrous <email> #ENV http_proxy http://proxy.example.com:8080 #ENV https_proxy https://proxy.example.com:8080 RUN apt-get update RUN apt-get -y upgrade # seed database password COPY mysqlpwdseed /root/mysqlpwdseed RUN debconf-set-selections /root/mysqlpwdseed RUN apt-get -y install mysql-server RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf RUN /usr/sbin/mysqld & \ sleep 10s &&\ echo "GRANT ALL ON *.* TO admin@'%' IDENTIFIED BY 'secret' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql -u root --password=secret &&\ echo "create database test" | mysql -u root --password=secret # persistence: http://txt.fliglio.com/2013/11/creating-a-mysql-docker-container/ EXPOSE 3306 CMD ["/usr/bin/mysqld_safe"] |
Notice that I declare my proxy servers for the third time here. This one is so that the container can access package downloads. I then seed the root database passwords and proceed to install and configure MySQL. Before running CMD, I expose port 3306. Remember that this port will be exposed on the private network between Docker containers and is only accessible to linked containers when you start it the way I show below. Here’s the mysqlpwdseed file.
mysqlpwdseed
mysql-server mysql-server/root_password password secret mysql-server mysql-server/root_password_again password secret |
If you downloaded the zip file above and ran vagrant in the resulting directory, you should have the files above available in /vagrant/mysql. The following commands will build and start the MySQL container.
cd /vagrant/mysql docker build -t "local/mysql:v1" . docker images docker run -d --name mysql local/mysql:v1 docker ps |
At this point you should show a local image for MySQL and a running mysql container, as shown below.
Nginx Container
Now we’ll build the Nginx container with PHP baked in.
Dockerfile
# LEMP stack as a docker container FROM ubuntu:14.04 MAINTAINER Daniel Watrous <email> ENV http_proxy http://proxy.example.com:8080 ENV https_proxy https://proxy.example.com:8080 # install nginx RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install nginx RUN echo "daemon off;" >> /etc/nginx/nginx.conf RUN mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak COPY default /etc/nginx/sites-available/default # install PHP RUN apt-get -y install php5-fpm php5-mysql RUN sed -i s/\;cgi\.fix_pathinfo\s*\=\s*1/cgi.fix_pathinfo\=0/ /etc/php5/fpm/php.ini # prepare php test scripts RUN echo "<?php phpinfo(); ?>" > /usr/share/nginx/html/info.php ADD wall.php /usr/share/nginx/html/wall.php # add volumes for debug and file manipulation VOLUME ["/var/log/", "/usr/share/nginx/html/"] EXPOSE 80 CMD service php5-fpm start && nginx |
After setting the proxy, I install Nginx and update the configuration file. I then install PHP5 and update the php.ini file. I then use two different methods to create php files. If you’re deploying an actual application to a Docker container, you may not end up using either of these, but instead install a script that will grab your application from git or subversion.
Next I define two volumes. You’ll see shortly that this makes it straightforward to view logs and manage code for debug. Finally I expose port 80 for web traffic and the CMD references two commands using && to make sure both PHP and Nginx are running in the container.
default
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.php index.html index.htm; server_name localhost; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } } |
This is my default Nginx configuration file.
wall.php
<?php // database credentials (defined in group_vars/all) $dbname = "test"; $dbuser = "admin"; $dbpass = "secret"; $dbhost = "mysql"; // query templates $create_table = "CREATE TABLE IF NOT EXISTS `wall` ( `id` int(11) unsigned NOT NULL auto_increment, `title` varchar(255) NOT NULL default '', `content` text NOT NULL default '', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8"; $select_wall = 'SELECT * FROM wall'; // Connect to and select database $link = mysql_connect($dbhost, $dbuser, $dbpass) or die('Could not connect: ' . mysql_error()); echo "Connected successfully\n<br />\n"; mysql_select_db($dbname) or die('Could not select database'); // create table $result = mysql_query($create_table) or die('Create Table failed: ' . mysql_error()); // handle new wall posts if (isset($_POST["title"])) { $result = mysql_query("insert into wall (title, content) values ('".$_POST["title"]."', '".$_POST["content"]."')") or die('Create Table failed: ' . mysql_error()); } // Performing SQL query $result = mysql_query($select_wall) or die('Query failed: ' . mysql_error()); // Printing results in HTML echo "<table>\n"; while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) { echo "\t<tr>\n"; foreach ($line as $col_value) { echo "\t\t<td>$col_value</td>\n"; } echo "\t</tr>\n"; } echo "</table>\n"; // Free resultset mysql_free_result($result); // Closing connection mysql_close($link); ?> <form method="post"> Title: <input type="text" name="title"><br /> Message: <textarea name="content"></textarea><br /> <input type="submit" value="Post to wall"> </form> |
Wall is meant to be a simple example that shows both PHP and MySQL are working. The commands below will build this new image and start it as a linked container to the already running MySQL container.
cd /vagrant/nginx docker build -t "local/nginx:v1" . docker images docker run -d -p 80:80 --link mysql:mysql --name nginx local/nginx:v1 docker ps |
So that now your Docker environment looks like the image below with an image for MySQL and one for Nginx and two processes running that are linked together.
As shown in the image above, we have mapped port 80 on the container to port 80 on the host system. This means we can discover the IP address of our host (remember the public network in the vagrant file) and load the web page.
Working with Running Containers
True to the single responsibility container design of Docker, these running containers are only running their respective service(s). That means they aren’t running SSH (and they shouldn’t be). So how do we interact with them, such as viewing the log files on the Nginx server or connecting to MySQL? We use Linked containers that leverage the shared volumes or exposed ports.
Connect to MySQL
To connect to MySQL, create a new container from the same MySQL image and link it to the already running MySQL. Start that container with /bin/bash. You can then use the mysql binaries to connect. Notice that I identify the host as ‘mysql’. That’s because when I linked the containers, Docker added an alias in the /etc/hosts file of the new container that mapped the private Docker network IP to ‘mysql’.
docker run -i -t --link mysql:mysql local/mysql:v1 /bin/bash mysql -u admin -h mysql test --password=secret |
View Ngnix Logs
It’s just as easy to view the Nginx logs. In this case I start up a plain Ubuntu container using the –volumes-from and indicate the running nginx container. From bash I can easily navigate to the Nginx logs or the html directory.
docker run -i -t --volumes-from nginx ubuntu /bin/bash |
References
https://docs.docker.com/userguide/dockerlinks/
Good article and helps us to understand how to connect php with mysql in docker two different containers .
Thanks