Getting Started w/ Docker
This post was originally published Jan 9th 2015.
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
#
# -*- 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 suppressed for some commands) Docker for CentOS/RHEL 6 is provided in the epel repos. If you are using 7 then you can just.
$ sudo yum install -y 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
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.
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 probably 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!
You might also be interested in these articles...