Docker: A Quick Look

by John R. Ray (jray@shadow-soft.com)

So I’ve been using Docker for around 6 months now and I wanted to give people an idea of how easy it is to get started with. Today I’m going to show you how to get Docker running and build a simple nginx web server.

 

Before you begin

I’m going to be using Vagrant, for my virtual environment. This is not needed if you are running Docker locally. If you are using vagrant then please remember to forward port 80 traffic to a port on your host. This can be accomplished by adding the following to your Vagrantfile.

s.vm.network "forwarded_port", guest: 80, host: 8080

Installing Docker

First thing, for me, is to bring up my vagrant environment. My Vagrantfile looks like

$ cat Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
#John R. Ray
# 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 = "centos-65-x64-nocm"
  config.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box"

  ## Server
  config.vm.define :server do |s|
    s.vm.provider :virtualbox do |v|
      v.memory  = 1024
      v.cpus = 2
    end
    s.vm.network :private_network,  ip: "10.10.100.100"
    s.vm.network "forwarded_port", guest: 80, host: 8080
    s.vm.hostname = 'server.johnray.io'
    s.vm.provision :hosts
  end
end

And now I’ll bring up my VM.

$ vagrant up server
Bringing machine 'server' up with 'virtualbox' provider...
==> server: Clearing any previously set forwarded ports...
==> server: Clearing any previously set network interfaces...
==> server: Preparing network interfaces based on configuration...
    server: Adapter 1: nat
    server: Adapter 2: hostonly
==> server: Forwarding ports...
    server: 80 => 8080 (adapter 1)
    server: 22 => 2222 (adapter 1)
==> server: Running 'pre-boot' VM customizations...
==> server: Booting VM...
==> server: Waiting for machine to boot. This may take a few minutes...
    server: SSH address: 127.0.0.1:2222
    server: SSH username: vagrant
    server: SSH auth method: private key
    server: Warning: Connection timeout. Retrying...
    server: Warning: Remote connection disconnect. Retrying...
==> server: Machine booted and ready!
==> server: Checking for guest additions in VM...
==> server: Setting hostname...
==> server: Configuring and enabling network interfaces...
==> server: Mounting shared folders...
    server: /vagrant => C:/Users/John/project/nginx
==> server: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> server: to force provisioning. Provisioners marked to run always will still run.

Now I have a CentOS 6 VM. Graciously provided by the folks at Puppet Labs. So now I need to install Docker. (Output supressed for some commands) Docker for CentOS/RHEL 6 is provided in the epel repos. If you are using 7 then you can just.

# yum install docker

Otherwise…

$ vagrant ssh server

Consult the user's guide for more details about POSIX paths:
http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
Last login: Tue Jan  6 11:20:43 2015 from 10.0.2.2
Welcome to your Packer-built virtual machine.

[vagrant@server ~]$ sudo su -
[root@server ~]# yum install -y epel-release
[root@server ~]# yum install -y docker-io

So now I should have docker installed and checking the version should give you something similar to the output below.

[root@server ~]# docker version
Client version: 1.3.2
Client API version: 1.15
Go version (client): go1.3.3
Git commit (client): 39fa2fa/1.3.2
OS/Arch (client): linux/amd64
Server version: 1.3.2
Server API version: 1.15
Go version (server): go1.3.3
Git commit (server): 39fa2fa/1.3.2

Docker Primer

I’m not going to do a command tutorial because the folks at docker have already provided a REALLY good one. Docker Tutorial. Go ahead and give it a try. I will wait.

Building a Dockerfile

Now that you are familiar with the docker commands let’s build a Dockerfile.

[root@server ~]# mkdir /nginx && cd /nginx
[root@server nginx]# vim Dockerfile

A Dockerfile contains all the commands you normally execute to build a Docker image.

FROM debian:jessie

The FROM line tells Docker what image you want to start with. In this case I’m using Debian’s Jessie release. At this point I could execute a docker build and I would have the base debian:jessie image.

The next thing I want to do is install some packages. To execute commands we are going to use the RUN directive.

FROM debian:jessie

RUN apt-get update && apt-get install -y
    curl
    nginx-light
    && apt-get clean

Now I have curl and nginx-light installed in my image. Now let’s hammer out the rest of this Dockerfile.

FROM debian:jessie

RUN apt-get update && apt-get install -y
    curl
    nginx-light
    && apt-get clean

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

EXPOSE 80

CMD ["nginx", "-c", "/etc/nginx/nginx.conf"]

So let’s look at what we have. First we are going to base this image of of debian jessie, we are then going to install curl and nginx light. Next we are going to tell nginx to run in the foreground so our container won’t crash, and we expose port 80 to the host. Finally we tell the image the default command that we want to execute when we launch a container.

Now we can build our image and run containers based on it.

[root@server nginx]# docker build -t yarnhoj/nginx .
Sending build context to Docker daemon 4.608 kB
Sending build context to Docker daemon
Step 0 : FROM debian:jessie
 ---> 835c4d274060
Step 1 : RUN apt-get update && apt-get install -y    curl    nginx-light    && apt-get clean
 ---> Using cache
 ---> 26d8200f733e
Step 2 : RUN echo "daemon off;" >> /etc/nginx/nginx.conf
 ---> Using cache
 ---> 1f416c2eafe1
Step 3 : EXPOSE 80
 ---> Using cache
 ---> bcdfdaa22a04
Step 4 : CMD nginx -c /etc/nginx/nginx.conf
 ---> Using cache
 ---> 3f257ba737b3
Successfully built 3f257ba737b3

Now if you are building this for the first time you are going to see A LOT more information. I’ve built this image before so what I see is cached layers getting reused.

Now take a look at your images.

[root@server nginx]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
yarnhoj/nginx       latest              3f257ba737b3        2 days ago          150 MB
debian              jessie              835c4d274060        8 days ago          122.6 MB

Running a Container

The docker run command takes several options. Let’s look at the complete command and talk about what it’s doing.

[root@server nginx]# docker run -it -d --name web -p 80:80 yarnhoj/nginx
9714909758f650417c6d7e24ff2fea9e1355adc43eb1c178782171a8fe64bdb1
[root@server nginx]# docker ps
CONTAINER ID        IMAGE                  COMMAND                CREATED             STATUS              PORTS                  NAMES
9714909758f6        yanrhoj/nginx:latest   "nginx -c /etc/nginx   2 seconds ago       Up 1 seconds        0.0.0.0:80->80/tcp   web

This command tells docker to do the following:

  • -it -d – Run an keep STDIN open, Allocate a TTY, and run in detached mode.
  • -name web – Name my container web.
  • -p 8080:80 – Map port 80 on the host to port 80 in the container.
  • yarnhoj/nginx – The image name to start from.

You can now run docker ps and see that your container is running.

But is it really?

[root@server nginx]# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on Debian!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx on Debian!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working on Debian. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a></p>

<p>
      Please use the <tt>reportbug</tt> tool to report bugs in the
      nginx package with Debian. However, check <a
      href="http://bugs.debian.org/cgi-bin/pkgreport.cgi?ordering=normal;archive=0;src=nginx;repeatmerged=0">existing
      bug reports</a> before reporting a new bug.
</p>

<p><em>Thank you for using debian and nginx.</em></p>


</body>
</html>

You now have a functioning web server running in a container. Go open a web browser and hit localhost:8080. (That’s what I have vagrant set up to forward traffic to.)

So the default index.html is kinda lame so as a parting shot let’s modify it with a standard hello world.

Customizing nginx

A couple of tasks to complete.

  • Create the index.html in the same directory as our Dockerfile
  • Tell Docker to add that file to our image

Adding your own index.html

[root@server nginx]# echo "Hello World" >> index.html

Modify the Dockerfile

[root@server nginx]# vim Dockerfile
FROM debian:jessie

RUN apt-get update && apt-get install -y
    curl
    nginx-light
    && apt-get clean

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

COPY index.html /var/www/html/ #<------ADD THIS LINE

EXPOSE 80

CMD ["nginx", "-c", "/etc/nginx/nginx.conf"]

The copy command is going to do exactly what you think it will.

Rebuild

[root@server nginx]# docker build -t yarnhoj/nginx .
Sending build context to Docker daemon 4.608 kB
Sending build context to Docker daemon
Step 0 : FROM debian:jessie
 ---> 835c4d274060
Step 1 : RUN apt-get update && apt-get install -y    curl    nginx-light    && apt-get clean
 ---> Using cache
 ---> 26d8200f733e
Step 2 : RUN echo "daemon off;" >> /etc/nginx/nginx.conf
 ---> Using cache
 ---> 1f416c2eafe1
Step 3 : COPY index.html /var/www/html/
 ---> 44d015a0947f
Removing intermediate container 1f8161e6b2f3
Step 4 : EXPOSE 80
 ---> Running in e92a75c7742d
 ---> 8c6a84dcb415
Removing intermediate container e92a75c7742d
Step 5 : CMD nginx -c /etc/nginx/nginx.conf
 ---> Running in e63b3eeae00a
 ---> 50ad4aca5a95
Removing intermediate container e63b3eeae00a
Successfully built 50ad4aca5a95

You will probabaly notice that this build took a fraction longer than you would have thought. That is because when you add/modify a line to a Dockerfile it invalidates the cache below it. What this means is you want to put heavy operations, like package installs, at the top of your Dockerfile.

Let’s stop our running nginx container and then launch the new one.

[root@server nginx]# docker stop web && docker rm web
web
web

Now launch a new container.

[root@server nginx]# docker run -it -d --name web -p 80:80 yarnhoj/nginx
4c094b64d0355ed3962f2e4a158cda5c2d6a3e63890a51f0c60ea98fbe1969c1
[root@server nginx]# docker ps
CONTAINER ID        IMAGE                  COMMAND                CREATED             STATUS              PORTS                NAMES
4c094b64d035        yanrhoj/nginx:latest   "nginx -c /etc/nginx   3 seconds ago       Up 2 seconds        0.0.0.0:80->80/tcp   web 

[root@server nginx]# curl localhost
Hello World

Happy Hacking!

JR|Ray

Interested in learning more how Docker can revolutionize your Development lifecycle? As an authorized Docker partner and integrator, Shadow-Soft has the expertise to explore Docker use cases in your specific environments.  Fill out this form and we’ll be in touch within 24 hours.