Hands-on Lab: Build and Deploy Portable Applications Using Docker

Version 18

    Learn how to customize a Docker container image and use it to instantiate application instances across different Linux servers. This article describes how to create a Dockerfile, how to allocate runtime resources to containers, and how to establish a communication channel between two containers (for example, between web server and database containers).

     

    Introduction

     

    This hands-on lab is designed to help you get a taste for how you can use Docker to simplify application provisioning on Linux. It demonstrates how to create a Docker container on Oracle Linux, modify it, and use it to instantiate multiple application instances. It also describes how to allocate system CPU and memory resources to a Docker container, how to set up a database container, and how other containers can connect to that database container.

     

    Getting Started

     

    You can download a pre-built VM template for Oracle VM VirtualBox for this lab: Oracle Linux VM Images for Hands-On Lab

     

    Once you have downloaded and imported the VM template into VirtualBox, log to the VM as holuser with the password oracle.

     

    To use Docker, the first step is to download and install the Docker Engine RPM packages. Oracle publishes Docker Engine RPMs for Oracle Linux 6 and Oracle Linux 7 on the public yum site and on the Unbreakable Linux Network (ULN).

     

    Up-to-date Instructions for installing the Docker Engine are available in the Oracle Linux 6 Docker User's Guide or the Oracle Linux 7 Docker User's Guide.

     

    The initial steps are pretty simple: first enable the ol7_addons channel in /etc/yum.repos.d/public-yum-ol*.repo by using the yum-config-manager tool and then run yum install docker.

     

    In addition, the hands-on lab image has been pre-configured with a btrfs-based filesystem for /var/lib/docker. The instructions above detail how to create and format a btrfs filesystem. The following commands assume you are logged in as the holuser user and have opened a Terminal window:

     

    [holuser@hol10328 ~]$ sudo yum-config-manager --enable ol7_addons
    [holuser@hol10328 ~]$ sudo yum install docker-engine
    [holuser@hol10328 ~]$ sudo usermod -a -G docker holuser
                  

     

    You'll notice that the last command adds the holuser to the docker group. This allows the holuser to run the Docker tool and manipulate containers. In order for the group membership to take effect, please log out and then log back into the graphical user desktop.

     

    Once Docker has been installed, we need to enable and start it using the systemd tool:

     

    [holuser@hol10328 ~]$ sudo systemctl status docker
    docker.service - Docker Application Container Engine
      Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled)
      Drop-In: /etc/systemd/system/docker.service.d
              └─docker-sysconfig.conf
      Active: inactive (dead)
        Docs: https://docs.docker.com
                  

     

    Here we can see that the Docker Engine has not been started automatically after installation. First, we need to enable the Docker Engine so that it is started automatically whenever the system boots:

     

    [holuser@hol10328 ~]$ sudo systemctl enable docker
    ln -s '/usr/lib/systemd/system/docker.service' '/etc/systemd/system/multi-user.target.wants/docker.service'
                  

     

    Next, we need to start the Docker Engine and verify that it has started correctly:

     

    [holuser@hol10328 ~]$ sudo systemctl start docker
    [holuser@hol10328 ~]$ sudo systemctl status docker
    docker.service - Docker Application Container Engine
      Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled)
      Drop-In: /etc/systemd/system/docker.service.d
              └─docker-sysconfig.conf
      Active: active (running) since Sun 2015-10-04 14:15:47 PDT; 8s ago
        Docs: https://docs.docker.com
    Main PID: 2427 (docker)
      CGroup: /system.slice/docker.service
              └─2427 /usr/bin/docker -d --selinux-enabled
    Oct 04 14:15:22 hol10328.oracleworld.com systemd[1]: Starting Docker Application Container Engine...
    ...
    Oct 04 14:15:47 hol10328.oracleworld.com systemd[1]: Started Docker Application Container Engine.
    Hint: Some lines were ellipsized, use -l to show in full.
                     

     

    Now that the Docker Engine has started, we can check the version of both the engine and the Docker client:

     

    [root@hol10328 ~]# docker version
    Client:
    Version:      1.8.2
    API version:  1.20
    Go version:  go1.4.2
    Git commit:  390110e
    Built:        Fri Sep 25 10:49:31 UTC 2015
    OS/Arch:      linux/amd64
    Server:
    Version:      1.8.2
    API version:  1.20
    Go version:  go1.4.2
    Git commit:  390110e
    Built:        Fri Sep 25 10:49:31 UTC 2015
    OS/Arch:      linux/amd64
                     

     

    We can also view information about the currently running engine, including which storage and execution drivers are active:

     

    [root@hol10328 ~]# docker info
    Containers: 0
    Images: 0
    Storage Driver: btrfs
    Build Version: Btrfs v3.16.2
    Library Version: 101
    Execution Driver: native-0.2
    Logging Driver: json-file
    Kernel Version: 3.8.13-98.2.2.el7uek.x86_64
    Operating System: Oracle Linux Server 7.1
    CPUs: 1
    Total Memory: 3.861 GiB
    Name: hol10328.oracleworld.com
    ID: D4FU:RJUB:65CH:NBLJ:UFN6:NWWY:U2CP:6PS6:4KB6:F6GX:YJSJ:6ZZL
                     

     

    The Docker Hub Registry is a public cloud store of images hosted by Docker, Inc. that can be used to build running containers. Repositories provide a mechanism for Docker image distribution and sharing. Docker, Inc. also hosts private repositories for a monthly fee. Alternatively, you can create a private Docker Registry behind your own corporate firewall, implementing protection mechanisms, such as SSL encryption and HTTP authentication, to restrict and protect access.

     

    The following commands pull the Oracle Linux 6 and Oracle Linux 7 images from the public Docker Hub Registry, as well as the official MySQL image from Oracle, downloading them to the local machine:

     

    [holuser@hol10328 ~]$ docker pull oraclelinux
    [holuser@hol10328 ~]$ docker pull oraclelinux:6
    [holuser@hol10328 ~]$ docker pull mysql/mysql-server
                     

     

    Images can include a specific tag, which indicates a particular build of that image. In the commands above, you'll notice we pulled oraclelinux first, which always points to the latest version of Oracle Linux on the Docker Hub (currently 7.1). The second command specified the specific tag "6", which indicates that we want the latest Oracle Linux 6 build. To view all the available tags, visit the image page on the Docker Hub Registry.

     

    During the download process, you'll notice that Docker tells us when a particular filesystem layer already exists. This is because Docker creates the final filesystem for each container by layering changes. This allows Docker to optimize both downloads and cloning operations to only operate on the layers that have changes.

     

    Next, we can view all the available images on our local machine:

     

    [holuser@hol10328 ~]$ docker images
    REPOSITORY          TAG                IMAGE ID            CREATED            VIRTUAL SIZE
    mysql/mysql-server  latest              cf18a41d54bb        9 days ago          286 MB
    oraclelinux          6                  5ef381c513c6        3 weeks ago        156.2 MB
    oraclelinux          latest              a8fd27de55f5        3 weeks ago        189.6 MB
                     

    Customizing a Container for Application Provisioning

     

    Suppose that I want to provision multiple, identical web servers across multiple Linux servers in my data center. Docker makes it easy to create a preconfigured, cookie-cutter environment in a container image. We can then use this prebuilt image and deploy it across one or many Linux hosts.

     

    To build a customized container image, we must first build a guest container, install the web server, and configure it to deliver web server content. We can use the docker run command to run an Oracle Linux 7 base container and execute a bash shell in the guest container:

     

    [holuser@hol10328 ~]$ docker run -t -i --name guest oraclelinux /bin/bash
                     

     

    The Docker Engine assigns an image ID to every running container instance. Because we used the arguments -i and -t, the bash shell runs interactively, and the prompt reflects the first 12 characters (f85d55a6893f) of my running container's image ID. The --name argument specifies a name for the running container instance. If you choose not to enter a name, the Docker Engine generates a random string that incorporates the name of a notable scientist, inventor, or developer, such as evil_jones; sad_ritchie; sleepy_curie and prickly_mestorf.

     

    Open another shell and use the docker ps command to show information about the running guest container, including the shortened form of the image ID, the base image used to create this container, and the container name:

     

    [holuser@hol10328 ~]$ docker ps
    CONTAINER ID        IMAGE              COMMAND            CREATED            STATUS              PORTS              NAMES
    b761e9371d83        oraclelinux        "/bin/bash"        7 seconds ago      Up 6 seconds                            guest
                     

     

    Back in the original shell and in the newly-created Docker guest, we install the httpd and perl RPM packages using yum, just as you would on any other physical or virtual server. We also use yum clean all to remove cache files that yum creates during package installation, because that will ultimately save some space in the exported container image:

     

    [root@a1f338b303f7 /]# yum install -y httpd perl && yum clean all
    ol7_UEKR3                                                                                                                                                                                  | 1.2 kB  00:00:00
    ol7_latest                                                                                                                                                                                  | 1.4 kB  00:00:00
    (1/5): ol7_latest/x86_64/updateinfo                                                                                                                                                        | 425 kB  00:00:00
    (2/5): ol7_UEKR3/x86_64/updateinfo                                                                                                                                                          |  48 kB  00:00:00
    (3/5): ol7_latest/x86_64/group                                                                                                                                                              | 652 kB  00:00:01
    (4/5): ol7_UEKR3/x86_64/primary                                                                                                                                                            |  12 MB  00:00:02
    (5/5): ol7_latest/x86_64/primary                                                                                                                                                            |  11 MB  00:00:02
    ol7_UEKR3                                                                                                                                                                                              300/300
    ol7_latest                                                                                                                                                                                        10321/10321
    Resolving Dependencies
    ...
    Installed:
      httpd.x86_64 0:2.4.6-31.0.1.el7_1.1                                                                        perl.x86_64 4:5.16.3-285.el7
    ...
    Complete!
    Cleaning repos: ol7_UEKR3 ol7_latest
    Cleaning up everything
                     

     

    At this point, We need to configure some content for the web server to display. For simplicity, let's create a basic opening page in the /var/www/html directory on the guest:

     

    [root@a1f338b303f7 /]# echo "Example Web Server Content" > /var/www/html/index.html
                     

     

    The guest container is now configured with the software environment that we want. Typing exit stops the running guest, returning to the prompt for the Linux host:

     

    [root@b761e9371d83 /]# exit
    exit
    [holuser@hol10328 ~]$
                     

     

    We now need to save this modified guest as an image that can be used to create new containers. To get the ID of the modified container, we use the docker ps command with the --latest and --quiet parameters. This will output just the ID of the latest guest to be created or stopped:

     

    [holuser@hol10328 ~]$ docker ps --latest --quiet
    a1f338b303f7
                     

     

    The docker ps command lists currently running containers by default. If we run it now, there are no results (because we have no running containers at the moment):

     

    [holuser@hol10328 ~]$ docker ps
    CONTAINER ID        IMAGE              COMMAND            CREATED            STATUS              PORTS              NAMES
                     

     

    We can add the --all parameter to list both running and stopped containers:

     

    [holuser@hol10328 ~]$ docker ps --all
    CONTAINER ID        IMAGE              COMMAND            CREATED            STATUS                    PORTS              NAMES
    b761e9371d83        oraclelinux        "/bin/bash"        10 minutes ago      Exited (0) 3 minutes ago                      guest
                     

     

    We can combine the output from the docker ps --latest --quiet command with the docker commit command to save the last-running guest as a new image:

     

    [holuser@hol10328 ~]$ docker commit -m "OL7-httpd" `docker ps -l -q` hol10328/httpd:r1
    910864a7dca537ca9e44a350bfd6b2cae2fa157d63ae51728d10b37c2d852fb6
                     

     

    Let's break down this command. To save an image, we use docker commit with a message (-m) parameter. We're then embedding the output of the docker ps -l -q command (using the short versions of the --latest and --quiet parameters) and finally providing a namespace, image name and tag for the image. We're saving our image into the hol10328 namespace with the image name of httpd and the tag, r1. If you want to save an image to the Docker Hub, you will need to use the namespace you created when you signed up.

     

    If we take a look at the images now, we'll see that our image is now available:

     

    [holuser@hol10328 ~]$ docker images
    REPOSITORY          TAG                IMAGE ID            CREATED            VIRTUAL SIZE
    hol10328/httpd      r1                  910864a7dca5        26 seconds ago      248.6 MB
    mysql/mysql-server  latest              cf18a41d54bb        2 days ago          286 MB
    oraclelinux          6                  5ef381c513c6        2 weeks ago        156.2 MB
    oraclelinux          latest              a8fd27de55f5        2 weeks ago        189.6 MB
                     

    Deploying the Configured Docker Image

     

    We can deploy any number of web servers now using the new Docker image as a template. The following docker run commands run the container image hol10328/httpd, creating the containers guest1, guest2, and guest3 and executing httpd in each one:

     

    [holuser@hol10328 ~]$ docker run -d --name guest1 -p 8081:80 hol10328/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    f3f762543aa3a26f0cfe3aeada980fd2ebe1975e00ceedfa174821633a5891e6
    [holuser@hol10328 ~]$ docker run -d --name guest2 -p 8082:80 hol10328/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    ca8bebb99f09b8e6c0718f06f7c34c0acfc8ee6cadc17ac16c3423c434c3ecdc
    [holuser@hol10328 ~]$ docker run -d --name guest3 -p 8083:80 hol10328/httpd:r1 /usr/sbin/httpd -D FOREGROUND
    4d3fe9c35884764b0fa0a0e1a791dfbfb9e1c00bd4d8963d8829dfa620c71f69
                     

     

    The -p argument maps port 80 in each guest to ports 8080, 8081, or 8082 on the host. Because we didn't use the -i or -t parameters, all three containers have started in the background. We can use the docker ps command to show the running guests:

     

    [holuser@hol10328 ~]$ docker ps
    CONTAINER ID        IMAGE              COMMAND                  CREATED            STATUS              PORTS                  NAMES
    87adc4cf1510        hol10328/httpd:r1  "/usr/sbin/httpd -D F"  2 seconds ago      Up 1 seconds        0.0.0.0:8083->80/tcp  guest3
    61fe9a593431        hol10328/httpd:r1  "/usr/sbin/httpd -D F"  6 seconds ago      Up 6 seconds        0.0.0.0:8082->80/tcp  guest2
    8a8bdc289370        hol10328/httpd:r1  "/usr/sbin/httpd -D F"  11 seconds ago      Up 11 seconds      0.0.0.0:8081->80/tcp  guest1
                     

     

    The default IP address value of 0.0.0.0 means that the port mapping applies to all network interfaces on the host. Using a web browser or curl, we can test the web server running in each guest:

     

    [holuser@hol10328 ~]$ curl http://hol10328:8081
    Example Web Server Content
    [holuser@hol10328 ~]$ curl http://hol10328:8082
    Example Web Server Content
    [holuser@hol10328 ~]$ curl http://hol10328:8083
    Example Web Server Content
                     

    Using a Dockerfile

     

    Now that you've seen how to create and manipulate Docker containers using the command line, the preferred way to build and customize containers is actually using Dockerfiles. A Dockerfile is a small text file that contains the instructions required to construct a container. When a Dockerflle is built, each instruction adds a layer to the container in a step-by-step process. The build creates a container, runs the next instruction in that container, and then commits the container. Docker then runs the committed image as the basis for adding the next layer. The benefit of this layered approach is that Dockerfiles with the same initial instructions reuse layers.

     

    Dockerfiles also create an easily readable and modifiable record of the steps used to create a Docker image. Publishing a Dockerfile to the public Docker Hub or to an internal repository is generally the preferred method of creating and sharing Docker images.

     

    To create a Dockerfile, first create a directory for it:

     

    [holuser@hol10328 ~]$ mkdir ~/dockerfile-httpd
    [holuser@hol10328 ~]$ cd ~/dockerfile-httpd/
                     

     

    In that directory, use a text editor to create a file called Dockerfile that contains the following contents:

     

    # Dockerfile for creating a Docker image for OL 7 and httpd and perl
    FROM oraclelinux
    MAINTAINER E. Jones <ejones@email-address.com>
    RUN yum install -y httpd perl && yum clean all
    RUN echo "Example Web Server Content" > /var/www/html/index.html
    EXPOSE 80
    CMD /usr/sbin/httpd -D FOREGROUND
                     

     

    This Dockerfile reflects the same steps as the previous exercise in which we manually built the Docker image for my web server: it configures a new container from a base Oracle Linux 7 image, installs the httpd and perl RPMs, and adds placeholder content for the opening web page.

     

    The docker build command constructs a new Docker image from this Dockerfile, creating and removing temporary containers as needed during its step-by-step build process:

     

    [holuser@hol10328 ~]$ docker build -t hol10328/httpd:r2 ~/dockerfile-httpd/.
    Sending build context to Docker daemon 2.048 kB
    Step 0 : FROM oraclelinux
    ---> a8fd27de55f5
    Step 1 : MAINTAINER E. Jones <ejones@email-address.com>
    ---> Running in 5e47026bd9ac
    ---> 6c954581d8d7
    Removing intermediate container 5e47026bd9ac
    Step 2 : RUN yum install -y httpd perl && yum clean all
    ---> Running in 9e7de7675e9f
    Resolving Dependencies
    --> Running transaction check
    ---> Package httpd.x86_64 0:2.4.6-31.0.1.el7_1.1 will be installed
    ...
    ---> Package perl.x86_64 4:5.16.3-285.el7 will be installed
    ...
    --> Running transaction check
    ...
    Installed:
      httpd.x86_64 0:2.4.6-31.0.1.el7_1.1        perl.x86_64 4:5.16.3-285.el7
    ...
    Complete!
    Cleaning repos: ol7_UEKR3 ol7_latest
    Cleaning up everything
    ---> 216c72f21d9b
    Removing intermediate container 9e7de7675e9f
    Step 3 : RUN echo "Example Web Server Content" > /var/www/html/index.html
    ---> Running in 099d2d0420c9
    ---> a4b3da2df721
    Removing intermediate container 099d2d0420c9
    Step 4 : EXPOSE 80
    ---> Running in 4381004b4686
    ---> c81647c23fb5
    Removing intermediate container 4381004b4686
    Step 5 : CMD /usr/sbin/httpd -D FOREGROUND
    ---> Running in bd6df764edef
    ---> 2e6f94af4d8f
    Removing intermediate container bd6df764edef
    Successfully built 2e6f94af4d8f
                     

     

    The docker images command lists the new hol10328/httpd:r2 image created from the Dockerfile:

     

    [holuser@hol10328 ~]$ docker images
    REPOSITORY          TAG                IMAGE ID            CREATED            VIRTUAL SIZE
    hol10328/httpd      r2                  2e6f94af4d8f        52 seconds ago      248.6 MB
    hol10328/httpd      r1                  910864a7dca5        12 minutes ago      248.6 MB
    mysql/mysql-server  latest              cf18a41d54bb        2 days ago          286 MB
    oraclelinux          6                  5ef381c513c6        2 weeks ago        156.2 MB
    oraclelinux          latest              a8fd27de55f5        2 weeks ago        189.6 MB
                     

    Limiting Runtime Resources

     

    A Docker container uses underlying control group (cgroup) technologies, creating a cgroup for each Docker container. Cgroups provide useful capabilities, such as collecting metrics for CPU, memory, and block I/O usage and providing resource management capabilities. For each Docker container, you can find metrics in the host's corresponding cgroup hierarchy; for example, on an Oracle Linux 7 host running the Unbreakable Enterprise Kernel Release 3 (UEKR3), a memory.stat file in the directory /sys/fs/cgroup/memory/system.slice/docker-<container ID> lists memory-related metrics for that container. (See the Docker blog article "Gathering LXC and Docker containers metrics.")

     

    The docker run command provides options that enable runtime limits for memory and relative CPU allocations. These options provide a degree of resource control when executing container images. The -m or --memory option limits the amount of physical and swap memory available to processes within a container. For example, the following command places a limit of 256 MB for physical memory (and up to 512 MB for memory plus swap):

     

    [holuser@hol10328 ~]$ docker run -d --memory=256m --name guest10 -p 8090:80 hol10328/httpd:r2
    bcbd59911607b8f22e5731b5a174b24513415a9b9e47a2429343524003cb9762
                     

     

    The -c or --cpushares option provides a method of assigning a relative number of CPU shares:

     

    [holuser@hol10328 ~]$ docker run -d --cpu-shares=512 --name guest11 -p 8091:80 hol10328/httpd:r2
    b5eaa43c5e8fdf3caafe69b63cdbeaab94cd90ea5c153ec2b794b4484ff8b051
                     

     

    By default, a container gets 1024 CPU shares, so in this example, the guest2 container gets a relative 50 percent share of the available computing resources. Note that the host kernel applies limits only when there is resource contention. For example, if guest2 is assigned 512 shares (50 percent, as shown above) and guest1 is assigned 1024 shares (100 percent), processes in guest2 will still get 100 percent of the available CPU resources if all guest1 processes are idle.

     

    For more information about options to the docker run command, enter docker run --help or see the Docker run reference guide.

     

    Connecting a Web Server Container to a MySQL Container

     

    Suppose we want to connect my web server container to a container running an instance of MySQL. Docker makes it possible to link containers together, creating a secure channel for one container to access certain information from another.

     

    Previously we pulled the Oracle MySQL image from the Docker Hub using the command docker pull mysql/mysql-server. To run the MySQL image as a container named db, we need to create a location to store the MySQL data and start a new container:

     

    [holuser@hol10328 ~]$ cd ~
    [holuser@hol10328 ~]$ mkdir mysql-datadir
    [holuser@hol10328 ~]$ docker run --name db -d -e MYSQL_ROOT_PASSWORD=Oracle123 -e MYSQL_DATABASE=webdb -e MYSQL_USER=webuser -e MYSQL_PASSWORD=secret -v /home/holuser/mysql-datadir/:/var/lib/mysql mysql/mysql-server
            

     

    Let's break down the run command: we start a container named db in the background using the -d (detached) parameter. We then set a series of environment variables using multiple -e parameters. The MySQL image allows you to customize the database that is created on startup by using environment variables to configure the container. In this example, we are setting the MySQL root password to Oracle123 and creating a new database called webdb. We then create a new MySQL user called webuser with the password of secret. The container automatically assigns the correct permission to the user for the database it creates.

     

    Using the -v or --volume flag is how you can associate database storage (or, in general, any files or folders) that a Docker container needs to change while it's running. Remember that Docker containers are "ephemeral"; that is, they are transient in nature and data does not persist beyond the lifespan of a container's execution. Associating a data volume with a container using the -v flag allows the container to access, modify, and persist data on the host.

     

    Reminiscent of an NFS mount, the -v flag causes the directory /home/holuser/mysql-datadir on the host to be mounted as /var/lib/mysql (the default database location for MySQL) on the container. From an operational standpoint, the ability to mount a database directory as a container volume in this way also provides an easy way to enable database backups.

    The Docker Hub mysql/mysql-server image includes an entrypoint script (entrypoint.sh) that sets up the database server automatically, initializing mysqld. We can view log messages generated from a container using the docker logs command:

     

    [holuser@hol10328 ~]$ docker logs db
    Running mysql_install_db
    2015-10-11 22:21:42 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
    2015-10-11 22:21:42 0 [Note] /usr/sbin/mysqld (mysqld 5.6.27) starting as process 12 ...
    ...
    Finished mysql_install_db
    MySQL init process in progress...
    ...
    2015-10-11 22:21:50 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
    2015-10-11 22:21:50 0 [Note] mysqld (mysqld 5.6.27) starting as process 1 ...
                     

     

    We now have a MySQL container running. Next, we need to create a web server container than contains code to connect to the database and retrieve information.

     

    Let's create a new Dockerfile that configures a web server that includes some Perl scripts:

     

    [holuser@hol10328 ~]$ cd ~
    [holuser@hol10328 ~]$ mkdir dockerfile-linked-httpd && cd dockerfile-linked-httpd
          

     

    Use your favourite text editor to create a Dockerfile with the following content:

     

    FROM oraclelinux:7
    RUN yum install -y httpd perl perl-DBI.x86_64 libdbi-dbd-mysql.x86_64 perl-DBD-MySQL.x86_64 && yum clean all
    ADD version.pl /var/www/cgi-bin/version.pl
    RUN chmod 755 /var/www/cgi-bin/version.pl
    ADD initdb.pl /var/www/cgi-bin/initdb.pl
    RUN chmod 755 /var/www/cgi-bin/initdb.pl
    ADD doquery.pl /var/www/cgi-bin/doquery.pl
    RUN chmod 755 /var/www/cgi-bin/doquery.pl
    EXPOSE 80
    ENTRYPOINT /usr/sbin/httpd -D FOREGROUND
                     

     

    This Dockerfile introduces the new command ADD which allows us to add a file from the host into the container during build. In order to successfully build this container, we need to create the three Perl scripts on the host, in the same directory as the Dockerfile itself.

     

    version.pl:

    #!/usr/bin/perl
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb:host=db",
            "webuser",
            "secret",
            { RaiseError => 1 },
    ) or die $DBI::errstr;
    my $sth = $dbh->prepare("SELECT VERSION()");
    $sth->execute();
    my $ver = $sth->fetch();
    print "Version = ", @$ver, "\n";
    $sth->finish();
    $dbh->disconnect()
                     

     

    initdb.pl

     

    #!/usr/bin/perl
    use strict;
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb:host=db",
        "webuser",
        "secret",
        { RaiseError => 1}
    ) or die $DBI::errstr;
    $dbh->do("DROP TABLE IF EXISTS People");
    $dbh->do("CREATE TABLE People(Id INT PRIMARY KEY, Name TEXT, Age INT) ENGINE=InnoDB");
    $dbh->do("INSERT INTO People VALUES(1,'Alice',42)");
    $dbh->do("INSERT INTO People VALUES(2,'Bobby',27)");
    $dbh->do("INSERT INTO People VALUES(3,'Carol',29)");
    $dbh->do("INSERT INTO People VALUES(4,'Daisy',20)");
    $dbh->do("INSERT INTO People VALUES(5,'Eddie',35)");
    $dbh->do("INSERT INTO People VALUES(6,'Frank',21)");
    my @noerr = ('Rows inserted in People table');
    print @noerr;
    print "\n";
    my $sth = $dbh->prepare( "SELECT * FROM People" );
    $sth->execute();
    
    for ( 1 .. $sth->rows() ) {
        my ($id, $name, $age) = $sth->fetchrow();
        print "$id $name $age\n";
    }
    $sth->finish();
    $dbh->disconnect();
                     

     

    doquery.pl

     

    #!/usr/bin/perl
    use strict;
    use DBI;
    print "Content-type: text/html\n\n";
    my $dbh = DBI->connect(
        "dbi:mysql:dbname=webdb;host=db",
            "webuser",
            "secret",
            { RaiseError => 1 },
    ) or die $DBI::errstr;
    my $sth = $dbh->prepare( "SELECT * FROM People WHERE Age > $ARGV[0]" );
    $sth->execute();
    my $fields = $sth->{NUM_OF_FIELDS};
    my $rows = $sth->rows();
    print "Selected $rows row(s) with $fields field(s)\n";
    for ( 1 .. $rows ) {
        my ($id, $name, $age) = $sth->fetchrow();
        print "$id $name $age\n";
    }
    $sth->finish();
    $dbh->disconnect();
                     

     

    These three scripts will allow us to create a basic MySQL database using initdb.pl, then query the MySQL server using version.pl and finally query the MySQL database we created using doquery.pl.

     

    Before we can run the web server, we need to build a new image using the docker build command. Using what you learned in the previous exercise, build a new Docker image for hol10328/httpd with the tag R3.

     

    Once your new Docker image is created, you can run it:

     

    [holuser@hol10328 dockerfile-linked-httpd]$ docker run -d --name webdb -p 8080:80 --link db:db hol10328/httpd:r3
                     

     

    By linking containers using the --link flag, other application containers can access the MySQL database running in the db container. This simplifies the separation of database, application, and web services, making it easier to isolate services in different containers. Docker automatically configures networking and the /etc/hosts file in each linked container.

     

    Finally, we can run the Perl scripts in the web server container to create and query the database running in the database container

     

    [holuser@hol10328 dockerfile-linked-httpd]$ curl http://hol10328:8080/cgi-bin/version.pl
    Version = 5.6.27
    [holuser@hol10328 dockerfile-linked-httpd]$ curl http://hol10328:8080/cgi-bin/initdb.pl
    Rows inserted in People table
    1 Alice 42
    2 Bobby 27
    3 Carol 29
    4 Daisy 20
    5 Eddie 35
    6 Frank 21
    [holuser@hol10328 dockerfile-linked-httpd]$ curl http://hol10328:8080/cgi-bin/doquery.pl?30
    Selected 2 row(s) with 3 field(s)
    1 Alice 42
    5 Eddie 35
    [holuser@hol10328 dockerfile-linked-httpd]$ curl http://hol10328:8080/cgi-bin/doquery.pl?21
    Selected 4 row(s) with 3 field(s)
    1 Alice 42
    2 Bobby 27
    3 Carol 29
    5 Eddie 35
                     

     

    The Oracle Linux documentation includes a detailed example of how to link a web container with a MySQL database, and it provides sample scripts that configure httpd.conf, create a database, and perform queries. Additional background on linking containers and using data volumes is available there and also in the Docker user guide.