Hands-on Lab: Building Development Environments with Oracle Container Runtime for Docker and Software Collections

Version 6

    Introduction

     

    In this Hands-On Lab, you will use a combination of Oracle Container Runtime for Docker and Software Collections provided by Oracle to build a tiered web application stack to host a popular PHP-based application. The lab will step through creating two new container images as well as deploying a pre-built container to run MySQL Server Community Edition.

     

    Enterprise Linux distributions like Oracle Linux are designed to provide version stability throughout their lifespan. While this significantly reduces risk, it also means that software within the base distribution can become out of date from the upstream project. In this Lab, you will take advantage of the Oracle Linux Software Collections Library to provide an updated version of PHP (7.0 instead of the base 5.4 version) and the NGINX web server that doesn't ship in the base Oracle Linux distribution.

     

    The development environment created will mirror a typical advanced production deployment of WordPress, by combining the NGINX web server in one container with PHP 7.0 running in another container. A third container will host the MySQL database. By storing the PHP code outside of the containers and making it available via the use of a Docker VOLUME, it's possible to modify the code without rebuilding or restarting the containers themselves.

     

    Container Layout

     

    The diagram below shows a high-level diagram of the containers you will build in this lab:

     

    Container-Diagram.png

     

     

     

    About Oracle Container Runtime for Docker

     

    The Oracle Container Runtime for Docker allows you to create and distribute applications across Oracle Linux systems and other operating systems that support Docker. Oracle Container Runtime for Docker consists of the Docker Engine, which packages and runs the applications, and the Docker Hub and Docker Store, which share the applications in a Software-as-a-Service (SaaS) cloud. The Docker Engine is designed primarily to run single applications in a similar manner to LXC application containers that provide a degree of isolation from other processes running on a system.

     

    About Software Collections

     

    The software collection library allows you install and use several different versions of the same software at the same time on a system. Software collections are primarily intended for development environments, which often require more recent versions of software components such as Perl, PHP, or Python to gain access to the latest features, but which need to avoid the risk of disrupting other processes on the system that rely on different versions of these components. You use the software collection library scl utility to run the developer tools from the software collections that you have installed under the /opt/rh directory hierarchy. scl isolates the effects of running these tools from other versions of the same software utilities that you have installed.

     

    Pre-requisite knowledge

     

    Attendees are expected to have basic Oracle Linux system administration skills. In particular, you should be familiar with the following Linux concepts and commands:

     

    • Experience with Docker including running containers and creating images using a Dockerfile and docker build.
    • using the Linux terminal
    • using sudo to run commands as root
    • using the yum package management tool
    • using vi or nano to edit files
    • scripting with bash

     

    Initial Login

     

    You should log into the virtual machine as the HOL User (holuser) using the password oracle.

     

    You will need a open a Terminal session to run commands and edit files. Later in the lab you will use the Firefox web browser to configure WordPress and to validate development changes made to the source code.

     

    Step 1: Deploying a MySQL container

     

    WordPress requires a MySQL database to store its content. Before you can build and deploy the containers that will run WordPress, you need to start MySQL. Because the MySQL data needs to persist through the life of the container (create, start, stop, destroy), a Docker VOLUME is used to store the data externally from the container itself by use of the -v parameter. This tells Docker to map the host directory /opt/data/mysql into the container as /var/lib/mysql.

     

    First, in your open Terminal session, create the directory on the host that will store the MySQL data:

     

    sudo mkdir -p /opt/data/mysql

     

    Next, start the MySQL container and provide some initial values that are used by the container to configure the database on startup:

     

    docker run --name mysql \

      -d \

      -e MYSQL_ROOT_PASSWORD=secret-root-pw \

      -e MYSQL_DATABASE=wordpress \

      -e MYSQL_USER=wordpress \

      -e MYSQL_PASSWORD=secret-wordpress-pw \

      -v /opt/data/mysql:/var/lib/mysql \

      mysql/mysql-server:5.7

     

    This command is formatted differently to other code samples to retain the correct whitespace at the end of each line.

    The full documentation for all the options available for the MySQL container can be found at https://hub.docker.com/r/mysql/mysql-server/ .

     

    Ensure that MySQL is running and healthy before continuing with the lab. To do so, check the output of docker ps:

     

    $ docker ps
    CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                    PORTS                 NAMES
    6cd6369430e5        mysql/mysql-server:5.7   "/entrypoint.sh my..."   37 seconds ago      Up 37 seconds (healthy)   3306/tcp, 33060/tcp   mysql
    

     

    Wait for the STATUS field to show (healthy) which indicates that MySQL is configured and running correctly.

     

    Step 2: Download WordPress

     

    Because you're building a development environment, you need to download the latest version of WordPress and unpack it into a directory on the host. This will allow multiple containers to access the same source code and would provide direct access to the code for development purposes.

     

    You've already created the /opt/data directory on the host, so you'll use that to store the WordPress source code as well.

     

    First, grab the latest tarball from the WordPress website:

     

    cd ~ && wget https://wordpress.org/latest.tar.gz

     

    Next, unpack the tarball into the /opt/data directory:

     

    cd /opt/data && sudo tar xvf /home/holuser/latest.tar.gz

     

    Next, create a wpdev user and group on the host that maps to the same uid/gid used by the php-fpm and nginx containers. The uid/gid used by both containers is 48. Then, add the holuser account to the newly-created wpdev group:

     

    sudo groupadd -g 48 wpdev
    sudo useradd -g 48 -u 48 wpdev
    sudo usermod -a -G wpdev holuser
    

     

    You MUST log out of the UI and then log back in again to get this new group membership activated for your user session. If you do not log out and back in, editing the source code in step 7 will fail.

    Finally, change ownership and permissions on the newly unpacked directory and files so that the service user within each container and the members of the wpdev group on the host are able to read and write to the files:

     

    cd /opt/data
    sudo chown -R wpdev:wpdev wordpress/
    sudo chmod -R g+w wordpress/
    

     

    By creating a user and group on the host and adding the holuser to the wpdev group, you have granted the holuser write access to the WordPress source code.

     

    Verify the permissions and ownership are correct by checking the output of the ls -la command for /opt/data:

     

    ls -la /opt/data
    total 0
    drwxr-xr-x  1 root  root   28 Sep 13 15:11 .
    drwxr-xr-x. 1 root  root   62 Sep 13 15:09 ..
    drwxr-xr-x  1    27    27 378 Sep 13 16:09 mysql
    drwxrwxr-x  1 wpdev wpdev 524 Sep 13 15:29 wordpress
    

    Step 3: Create and deploy a PHP 7.0 FPM container

     

    Now that you have prepared your development environment by starting the MySQL container and unpacking the WordPress source code, it's time to start building the Docker images that will be used to run the containers. The first image is the PHP 7.0 image. Usually, PHP runs as an Apache module but in this example you will build a container that runs PHP FastCGI Process Manager instead. PHP-FPM has some particular advantages when running large or busy sites over the default Apache module, including better memory management and superior adaptive process spawning.

     

    To begin, create a directory in your home directory that will store the Dockerfile used to build the PHP-FPM image:

     

    cd ~ && mkdir php-fpm && cd php-fpm
    

     

    Next, use your favourite text editor to create a file named Dockerfile that will build this image:

     

    # Extend the official Oracle Linux 7 slim image. This image is a tiny install of
    # Oracle Linux 7 that has just enough functionality to run yum to install more packages.
    
    FROM oraclelinux:7-slim
    
    # In order to install PHP 7.0 FPM from the Software Collections library, we use the
    # yum-config-manager tool to enable the Software Collections repo which is already
    # configured but disabled by default.
    #
    # Next you use yum to install the required packages to install PHP-FPM with all the PHP
    # options required by WordPress
    #
    # Finally you run yum clean all to remove all the metadata created by yum during the 
    # install process. This ensures the final image is as small as possible
    
    RUN yum-config-manager --enable ol7_software_collections && \
        yum -y install scl-utils postfix rh-php70-php-fpm rh-php70-php-gd rh-php70-php-mbstring \
                       rh-php70-php-opcache rh-php70-php-mysqlnd rh-php70-php-soap && \
        yum clean all
    
    # By default, PHP-FPM is configured to only accept connections from localhost but in this
    # environment, we need it to accept connections from the NGINX container. In order to
    # do that, the configuration is changed to make PHP-FPM listen on all IP addresses and
    # to accept connections from anywhere.
    #
    # PHP-FPM is also configured to send the worker output to stdout which will make it 
    # visible to the "docker logs" command
    
    RUN sed -i '/^listen = /clisten = 0.0.0.0:9000' /etc/opt/rh/rh-php70/php-fpm.d/www.conf && \
        sed -i '/^listen.allowed_clients/c;listen.allowed_clients =' /etc/opt/rh/rh-php70/php-fpm.d/www.conf && \
        sed -i '/^;catch_workers_output/ccatch_workers_output = yes' /etc/opt/rh/rh-php70/php-fpm.d/www.conf
    
    # PHP-FPM listens by default on port 9000 so we use the EXPOSE parameter to document that
    # within the Dockerfile
    EXPOSE 9000
    
    # The WordPress source code is stored on the host and needs to be mapped into the 
    # container using the VOLUME directive. 
    
    VOLUME /var/www
    
    # The WORKDIR parameter tells Docker to change into this directory before running the
    # following directives
    
    WORKDIR /var/www/
    
    # Finally we start PHP-FPM in foreground mode ("-F") and force output to stderr ("-O")
    # This ensures that any errors are visible in the docker logs output
    
    CMD ["/opt/rh/rh-php70/root/usr/sbin/php-fpm", "-F", "-O"]
    

     

    Once you have created the Dockerfile, use Docker to build the image itself:

     

    docker build -t php-fpm .

     

    The -t parameter sets the name and tag for the image using the format <name>:<tag>. In this example, you are creating an image named php-fpm without a specific tag. Docker will automatically use the latest tag for this build. In your own environment it is recommended to use version-specific tags (like 7.0 to match the PHP version within the image) so that you can create and use multiple versions of the same image.

     

    The build process uses the Dockerfile to create an image by running each step in a new container to create a new filesystem layer. Docker uses this layering technique to reduce overall disk space usage by running containers by sharing identical layers between images and containers.

     

    You can view the image created by the build process by running docker images:

     

    $ docker images
    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    php-fpm              latest              a7941607d8f4        9 seconds ago       255 MB
    oraclelinux          7-slim              da5e55a16f7a        24 hours ago        118 MB
    mysql/mysql-server   5.7                 3157d7f55f8d        7 weeks ago         241 MB
    

     

    You should see your php-fpm image alongside the oraclelinux:7-slim image which was pulled automatically by the build process. The mysql/mysql-server:5.7 image was pulled automatically by the docker run command you used in step 1 of this lab.

     

    Once the image has been successfully built, you can run a container based off this image:

     

    docker run --name php-fpm -d \

       --link mysql:mysql \

       -v /opt/data/wordpress:/var/www \

       php-fpm

     

    This command is formatted differently to other code samples to retain the correct whitespace at the end of each line.

     

    The --link parameter tells Docker to link the PHP-FPM container with the MySQL container. This automatically configures the PHP-FPM container to resolve the hostname mysql with the IP address of the container named mysql.  Because Docker automatically assigns IP addresses when the container starts, using --link allows Docker to propagate IP address changes into linked containers.

     

    For more advanced networking options, see the Oracle Linux Docker User's Guide.

     

    By using the -v parameter to map the host /opt/data/wordpress directory into the container, you can store the WordPress source code on the host and make it available to the process running inside the container. This same technique was used with the MySQL container to store the MySQL data externally from the container to ensure it persists if the container is destroyed. In this instance, you store the source code outside of the container so that it can be modified without needing to rebuild or restart the container itself.

     

    You can use the docker ps command to verify the php-fpm container is running successfully. The Dockerfile does not implement a HEALTHCHECK directive so it will not display (healthy). It's sufficient to verify that the container is Up.

     

    Step 4: Create and deploy an NGINX container

     

    Once the PHP-FPM image has been built and the PHP-FPM container has been started from that image, you can move on to creating the NGINX image and container. To begin, create a directory to store the Dockerfile for the NGINX image:

     

    cd ~ && mkdir nginx && cd nginx

     

    Next, use your favourite text editor to create a file named Dockerfile that will create this image:

     

    # Extend the official Oracle Linux 7 slim image. This image is a tiny install of
    # Oracle Linux 7 that has just enough functionality to run yum to install more packages.
    
    FROM oraclelinux:7-slim
    
    # In order to install NGINX from the Software Collections library, we use the
    # yum-config-manager tool to enable the Software Collections repo which is already
    # configured but disabled by default.
    #
    # Next use yum to install the required packages to install NGINX
    #
    # Finally you run yum clean all to remove all the metadata created by yum during the 
    # install process. This ensures the final image is as small as possible
    
    RUN yum-config-manager --enable ol7_software_collections && \
        yum -y install scl-utils rh-nginx110-nginx && \
        yum clean all
        
    # A custom configuration file for NGINX to enable WordPress support is copied into
    # the image during the build process. This configuration is based off the default
    # configuration provided by WordPress at https://codex.wordpress.org/Nginx
    
    ADD wordpress.conf /etc/opt/rh/rh-nginx110/nginx/conf.d/wordpress.conf
    
    # NGINX does not have a built-in method to log directly to stdout or stderr which is
    # required for the logs to be made available to docker. A workaround is to symlink the
    # log files to stdout and stderr so that the NGINX logs are output correctly
    
    RUN ln -sf /dev/stdout /var/opt/rh/rh-nginx110/log/nginx/access.log && \
        ln -sf /dev/stderr /var/opt/rh/rh-nginx110/log/nginx/error.log
    
    # In order for NGINX to serve the static files directly, a VOLUME is used to map the
    # WordPress source code into the container
    
    VOLUME /var/www
    
    # By default, NGINX listens on port 80 (HTTP). The EXPOSE directive is used to document
    # this port
    
    EXPOSE 80
    
    # NGINX uses the SIGTERM signal to shutdown so we configure Docker to send SIGTERM when
    # a docker stop is sent
    
    STOPSIGNAL SIGTERM
    
    # Start NGINX in foreground mode
    CMD ["/opt/rh/rh-nginx110/root/usr/sbin/nginx", "-g", "daemon off;"]
    

     

    Finally, use your favourite text editor to create the NGINX custom wordpress.conf configuration file:

     

    upstream phpfpm_backend {
       server php-fpm:9000;
    }
    
    server {
       listen 80;
       server_name docker.oracleworld.com;
       root /var/www;
    
       index index.php;
    
       # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
       # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
       location ~ /\. {
          deny all;
       }
    
       # Deny access to any files with a .php extension in the uploads directory
       # Works in sub-directory installs and also in multisite network
       # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
       location ~* /(?:uploads|files)/.*\.php$ {
          deny all;
       }
    
       location / {
       # This is cool because no php is touched for static content.
       # include the "?$args" part so non-default permalinks doesn't break when using query string
          try_files $uri $uri/ /index.php?$args;
       }
    
       location ~ \.php$ {
          #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
          include fastcgi.conf;
          fastcgi_intercept_errors on;
          fastcgi_pass phpfpm_backend;
          fastcgi_buffers 16 16k;
          fastcgi_buffer_size 32k;
       }
    
       location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires max;
          log_not_found off;
       }
    
    }
    

     

    Use the same build command to build an image using this Dockerfile:

     

    docker build -t nginx .

     

    And then run a container based off this image:

     

    docker run --name nginx -d \

       --link php-fpm:php-fpm \

       -v /opt/data/wordpress:/var/www \

       -p 80:80 \

       nginx

     

    This command is formatted differently to other code samples to retain the correct whitespace at the end of each line.

    Just like the PHP-FPM container, the --link parameter is used to link the NGINX container with the PHP-FPM container. Note that the NGINX container is not linked to the MySQL container, which means that NGINX cannot communicate directly with the MySQL instance. This adds an additional layer of security by preventing an attacker that gains control of the NGINX container access directly to the MySQL container. In order to gain control of MySQL, an attacker would need to compromise both the NGINX and PHP-FPM containers.

     

    Before continuing, ensure the nginx container is running by using the docker ps command as before.

     

    Step 5: Verify all the images and containers

     

    At this stage in the lab, you should have successfully created two new images for PHP-FPM and NGINX and started three containers. You can verify this by running docker images and docker ps:

     

    $ docker images
    REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
    php-fpm              latest              6058f992f2c1        40 minutes ago      255 MB
    nginx                latest              0c95a11622c7        4 days ago          228 MB
    oraclelinux          7-slim              c0feb50f7527        4 weeks ago         118 MB
    mysql/mysql-server   5.7                 3157d7f55f8d        7 weeks ago         241 MB
    

     

    The docker images command lists the images that exist on your system. You can use the docker ps command to view the running containers:

     

    $ docker ps
    CONTAINER ID        IMAGE                    COMMAND                  CREATED              STATUS                   PORTS                 NAMES
    6ab0a2796afe        nginx                    "/opt/rh/rh-nginx1..."   27 seconds ago       Up 27 seconds            0.0.0.0:80->80/tcp    nginx
    178302c9fdc1        php-fpm                  "/opt/rh/rh-php70/..."   About a minute ago   Up About a minute        9000/tcp              php-fpm
    be9604cce5f9        mysql/mysql-server:5.7   "/entrypoint.sh my..."   4 days ago           Up 4 minutes (healthy)   3306/tcp, 33060/tcp   mysql
    

     

    If your containers are not running, you can use the docker ps -a command to view all containers, running and stopped.

     

    Step 6: Configure WordPress

     

    Open a new tab in Firefox and enter the URL: http://docker.oracleworld.com

     

    On the language screen that appears, accept the default "English (United States)" and then click Continue:

     

    chose-language.png

     

    The next screen provides information about the database configuration requirements to continue the installation process of WordPress. Click the Let's Go button to get started.

     

    Configure the database connection details as follows:

     

    configure-database.png

     

    • Database name: wordpress
    • Username: wordpress
    • Password: secret-wordpress-pw
    • Database Host: mysql
    • Table Prefix: wp_

     

    Remember that you provided the wordpress username and password in step 1 when you created the MySQL container. The Database Host name is the same as the value provided to the --link parameter when you started the php-fpm container and linked it to the mysql container.

     

    Once you've provided the configuration details, click Submit.  WordPress will validate the database configuration and if it can connect successfully, will prompt you to continue. Click Run the install to setup the database for WordPress.

     

    The last step is to provide the base configuration details for WordPress itself:

     

    configure-wordpress.png

     

    You can provide any configuration of your choice. Some sample options are provided in the screenshot above and text below.

     

    • Site Title: WordPress Development with Docker
    • Username: holuser
    • Password: use the auto-generated password. Copy and paste it somewhere safe so you don't lose it. Firefox will prompt you to save the credentials, which you should accept.
    • Your Email: holuser@oracleworld.com

     

    Once you've configured WordPress, click Install WordPress. You should receive a Success! message and a Log in button. Click the Log in button to continue. You should now see the WordPress admin interface. You can click "WordPress Development with Docker" (or your custom WordPress blog title) in the header bar of the WordPress Admin Interface to see the website itself.

     

    Step 7: Modify the source code of WordPress

     

    Remember that you unpacked the source code of WordPress onto the host and used Docker volumes to mount that source code into both the php-fpm and nginx containers. You'll now edit the source code and verify that a code change on the host is automatically picked up by the containers without requiring a rebuild or restart of either the php-fpm or nginx containers.

     

    Switch back to your Terminal session and navigate to the WordPress source code location:

     

    $ cd /opt/data/wordpress
    

     

    Using your favourite text editor, open wp-content/themes/twentyseventeen/index.php and change line 27 as follows:

     

    --- index.php.orig 2017-09-13 15:46:33.433492875 -0700
    +++ index.php 2017-09-13 15:47:29.279064717 -0700
    @@ -24,7 +24,7 @@
      </header>
      <?php else : ?>
      <header class="page-header">
    - <h2 class="page-title"><?php _e( 'Posts', 'twentyseventeen' ); ?></h2>
    + <h2 class="page-title"><?php _e( 'Entries', 'twentyseventeen' ); ?></h2>
      </header>
      <?php endif; ?>
    

     

    If you refresh http://docker.oracleworld.com in Firefox, you should see the "Posts" text underneath the graphic has changed to "Entries".

     

    This lab is adapted with permission from Deploy a PHP web app with Fig, Docker, Nginx, FPM and CentOS SCL by Manuel Vacelet.