The /bin/bash Theory


Building Php Application With Docker

Using Docker, deploying application components in production environment can be done in minutes. In this post i will build a simple wordpress PHP application stack using Nginx (front-end container), and two Application servers (back-end containers) which are connected to a MySQL container.

phpstack

Using the link feature in docker, we will be able to link the containers with each other without the need to expose the ports on the host machine, or worry about the IPs of the linked containers.

MySQL Image

First, creating a directory to store the Dockerfile which is used to build the container image:

#mkdir -p /wordpress/mysql/
#cd /wordpress/mysql
#touch startdb.sh
#touch Dockerfile

The Content of the Dockerfile:

#Dockerfile for mysql 5.4
FROM ubuntu:14.04
MAINTAINER [email protected]
ENV CACHED_FLAG 1
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update -qq
RUN apt-get -y upgrade
RUN apt-get -y install mysql-client mysql-server pwgen

#binding all interfaces
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf

ADD startdb.sh /startdb.sh

# Set the environment variables
ENV WPDB wordpress
ENV WPUSER wpuser
ENV WPPASS random

ENTRYPOINT [ "/bin/bash","/startdb.sh" ]

EXPOSE 3306

This Dockerfile uses ubuntu 14.04 image as the base image for the container, it installs MySQL server and client, and it uses sed to bind MySQL service to all network interfaces of the container, then adds startdb.sh script to the container which i will use later to create the wordpress database and user.

I used ENV option to add some environment variables to the container which will be used later in the wordpress configuration file to connect to the MySQL database.

Finally it exposes the default port used by MySQL, then it instructs docker to run the /startdb.sh script when booting up the docker container.

The content of startdb.sh file:

# This script is used to initialize and create wp database and user
# Start mysql
/usr/sbin/mysqld &
sleep 5

# Create the database and user for wordpress
mysql -uroot -e "CREATE DATABASE ${WPDB}"
mysql -uroot -e "GRANT All PRIVILEGES ON ${WPDB}.* TO ${WPUSER} IDENTIFIED BY '${WPPASS}';"
mysql -uroot -e "FLUSH PRIVILEGES;"

# Create admin user
# The password will appear in the log
echo -e "Creating Admin user\n"
ADMIN_UN='admin'
ADMIN_PASS=`pwgen -s 12 1`
mysql -uroot -e "GRANT ALL PRIVILEGES ON *.* TO $ADMIN_UN IDENTIFIED BY '$ADMIN_PASS';"
mysql -uroot -e "FLUSH PRIVILEGES;"
echo -e "DONE .... \n"
echo -e "USERNAME = $ADMIN_UN \n"
echo -e "PASSWORD = $ADMIN_PASS \n"

# Stop and restart mysql in the foreground
mysqladmin shutdown
/usr/sbin/mysqld

The script is very simple, it creates the worpdress database and user to connect to the application, it grants all permissions to this user.

Now building the image using docker build:

# docker build -t husseingalal/mysql .

Application Downloader

The sole purpose of this image is to download the WordPress app and store it in a shared volume to be used by other containers.

Dockerfile:

FROM ubuntu:14.04
ENV chached_FLAG 1

RUN apt-get -yqq update
RUN apt-get -yqq install wget
ADD run.sh /run.sh

VOLUME [ "/var/www/app" ]
WORKDIR /var/www/app

ENTRYPOINT ["/bin/bash","/run.sh"]

The run.sh script:

wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz --strip-component=1

Using docker build to create the downloader image:

# docker build -t husseingalal/downloader .

Creating a container from this image will result in downloading the wordpress application in /var/www/app volume.

PHP-FPM Application Server

Using the same steps to create php-fpm application server which will reuse the /var/www/app volume from the downloader image.

# mkdir -p /wordpress/phpfpm/
# cd /wordpress/phpfpm
# mkdir app
# mkdir config
# touch conf/php-fpm.conf
# touch conf/www.conf
# touch Dockerfile

The Content of the Dockerfile:

FROM ubuntu:14.04
# Install php-fpm
RUN apt-get update -qq && apt-get -y upgrade
RUN apt-get -y -qq install php5-fpm php5-mysql
# Adding the configuration files
ADD conf/www.conf /etc/php5/fpm/pool.d/www.conf
ADD conf/php-fpm.conf /etc/php5/fpm/php-fpm.conf
# Add the run script to run the services and configure db
ADD run.sh /run.sh

#add the volume for the wordpress application
VOLUME /var/www/app

# Expose the port 9000
EXPOSE 9000
# Run the run.sh script
ENTRYPOINT [ "/bin/bash", "/run.sh"]

After exposing port 9000 for the container, i ran the run.sh script to add the database information in the wp-config.php file:

#!/bin/bash

# Script to edit the wp-config.php with the database info
# and run nginx and php5-fpm service
WORDPRESS_DIR='/var/www/app'
WORDPRESS_DB=$DB_ENV_WPDB
WORDPRESS_USER=$DB_ENV_WPUSER
WORDPRESS_PASS=$DB_ENV_WPPASS
WORDPRESS_HOST=$DB_PORT_3306_TCP_ADDR

echo 'Check for the wp-config.php'
if [ -f $WORDPRESS_DIR/wp-config.php ]
then
echo 'No need to edit the config file... starting php-fpm'
else
echo 'Editing wp-config.php...'
echo 'Starting the service'
cp $WORDPRESS_DIR/wp-config-sample.php $WORDPRESS_DIR/wp-config.php
sed -i "s/database_name_here/$WORDPRESS_DB/" /var/www/app/wp-config.php
sed -i "s/username_here/$WORDPRESS_USER/" /var/www/app/wp-config.php
sed -i "s/password_here/$WORDPRESS_PASS/" /var/www/app/wp-config.php
sed -i "s/localhost/$WORDPRESS_HOST/" /var/www/app/wp-config.php
fi
sleep 5

#starting php-fpm in foreground

/usr/sbin/php5-fpm -F

The DB_ENV_WPDB, DB_ENV_WPUSER, and DB_ENV_WPPASS are the environment variables defined in the Dockerfile of MySQL container, they will be reused in this container using the link feature that will be used when running the containers.

Of course you wonder what the heck is $DB_PORT_3306_TCP_ADDR, as you will see later this variable is one of the environment variables created when using the link feature to link the App server with the MySQL database container.

the configuration files are pretty simple:

php-fpm.conf:

[global]
pid = /var/run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
daemonize = no
include=/etc/php5/fpm/pool.d/*.conf
www.conf:

[www]
user = www-data
group = www-data
listen = 0.0.0.0:9000
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

Now building the image:

# docker build -t husseingalal/phpfpm .

Nginx Image

The last piece of the puzzle is the nginx (the load balancer) that will proxy the requests to the app servers.

Setting up first the directory structure of nginx image:

# mkdir /wordpress/nginx/
# cd /wordpress/nginx
# mkdir config
# touch conf/nginx.conf
# touch conf/default.conf
# touch Dockerfile

The Dockerfile:

# dockerfile for nginx/php5-fpm
FROM ubuntu:14.04
MAINTAINER [email protected]
ENV CACHED_FLAG 1

# Install nginx and php-fpm
RUN apt-get update -qq && apt-get -y upgrade
RUN apt-get -y -qq install nginx
VOLUME /var/www/app

# Adding the configuration files
ADD conf/nginx.conf /etc/nginx/nginx.conf
ADD conf/default /etc/nginx/conf.d/

# Expose the port 80
EXPOSE 80

# Run nginx
ENTRYPOINT [ "/usr/sbin/nginx" ]

And the content of the configuration file:

nginx.conf:

user www-data;
worker_processes 1;
pid /run/nginx.pid;
daemon off;

events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*;
}
default configuration file:

upstream backend {
        server appserver1:9000;
}
server {
        listen 80 default_server;
        root /var/www/wordpress;
        index index.php index.html index.htm;
        server_name _;
        location / {
                try_files $uri $uri/ =404;
        }
        location ~ \.php$ {
                fastcgi_pass backend;
                fastcgi_index index.php;
                include fastcgi_params;
        }
}

Note the daemon off; in the nginx.conf file to start nginx in foreground.

Running The Services

From the bottom up, i will run the following:

  • MySQL container.
  • Downloader container to fetch the wordpress app.
  • 2 APP server containers.
  • Nginx load balancer container.

Mysql contianer:

docker run -d --name mysql husseingalal/mysql

Downloader container:

docker run -i -t --name downloader husseingalal/downloader

Application server containers:

docker run -d --name app1 --volumes-from downloader --link mysql:db husseingalal/phpfpm
docker run -d --name app2 --volumes-from downloader --link mysql:db husseingalal/phpfpm

Nginx container:

docker run -d -p 80:80 --name nginx --volumes-from downloader --link app1:app1 --link app2:app2 husseingalal/nginx

Linking Containers

The linking feature allows the linker container (parent) to see information about the linked container (child), this information includes all the environment variables and network information (IP, Port, …).

For example when using (–link mysql:db), this will create a link between the app1 container and MySQL container with link name (db), all environment variables defined in MySQL Dockerfile like WPDB will be included in app1 container and defined with a new name (DB_ENV_WPDB). The link id (db) will be used as a hostname to the linked container and a line will be added to /etc/hosts with the port of the linked container:

# docker exec app1 cat /etc/hosts | grep db
172.17.0.45	db

And to see all the environment variables added from the linking process:

# docker exec app1 env
DB_PORT=tcp://172.17.0.45:3306
DB_PORT_3306_TCP=tcp://172.17.0.45:3306
DB_PORT_3306_TCP_ADDR=172.17.0.45
DB_PORT_3306_TCP_PORT=3306
DB_PORT_3306_TCP_PROTO=tcp
DB_NAME=/app1/db
DB_ENV_WPDB=wordpress
....

Also only containers explicitly linked to MySQL container will be able to connect to the exposed port (3306), which is a great security feature.

Conclusion

In this post, i built a simple PHP application using docker containers and ran each service in a seperate container. The VOLUMES played a big role to construct a shared volume for all the containers, also the linking feature simplified the connections between the containers.