Docker gives you the ability to run linux containers or “chroot on steroids” which utilize a layered approach using device-mappper or aufs to enable users to create images, build containers off of them, and deploy applications quickly for both development and production (and maintain uniformity!)

Before we start, virtually any major service/application has been “*dockerized*” meaning at least one person has made a docker repo for it! For examples, just do searches like “*docker-nginx*” or “*docker-powerdns*”. Looking at Dockerfile’s other people create can help you understand the best way to run an application.

The following key points will outline the basic structure in working with and using docker:

1. “(optional: docker repo) -> Dockerfile -> Image -> Container” OR “Docker Image from Registry -> Container”
2. A Dockerfile defines what to install, commands to run, and files to add to the final resulting image.

Think of it this way: A Dockerfile is not /etc/rc.local, but rather a set of instructions for building an ISO. A Dockerfile sits in a directory called a “*build directory*” or “*build repo*”. This build repo contains everything needed to build a docker image including sources for an app, a custom rc.local, or anything else you define in the Dockerfile to pull into the built image. Here is an example Dockerfile from my ZNC bouncer container:

FROM ubuntu:14.04

ENV DEBIAN_FRONTEND noninteractive

# install ZNC build prerequisites
RUN apt-get update && \
        apt-get install -yq --force-yes coreutils g++ libssl-dev make

# build and install
ADD znc-1.4 /opt/znc-1.4
##RUN cd /opt && tar xzvf znc-1.4.tar.gz
RUN cd /opt/znc-1.4 && ./configure && make && make install

# clean up after the build
RUN rm -rf znc-1.4*
RUN apt-get remove -yq g++ libssl-dev make
RUN apt-get autoremove -yq && apt-get clean

# run ZNC as an unprivileged user
RUN useradd -m -d /opt/znc znc
ADD run /opt/znc/run
RUN chmod +x /opt/znc/run
RUN chown -R znc:znc /opt/znc

# run thie image as a ZNC server
USER znc
WORKDIR /opt/znc
CMD ["/opt/znc/run"]

Here we see commands to Docker itself that look like “*RUN*” or “*ADD*”. Some are self-explanatory, but there are a lot so you’ll want to keep this with you. In this example, ADD allows us to copy over files into the image from the current working directory, namely the source code.

At the end of each Dockerfile, it is necessary to run some sort of foreground program/command that will continue to run, ending only when you intend for the container to actually stop. Yes, a container doesn’t just stay running by itself, there has to be a continuing process.

3. To build a image named znc, you would ensure you currently reside in the build directory and run a:

docker build -t znc .

The Docker daemon will instantly start building. Don’t worry if you see some red text fly by real quick…Get some cheez-its and come back to see the obvious result of your build. If a problem does occur, you’ll see exactly where docker was forced to stop and what the problem was.

Once you get an image built successfully, run:

docker images

to see the completed image! Now, you can save this docker image to a “*registry*” which is merely a store for docker images. When you see a FROM ubuntu:1404, this merely says “*pull down the ubuntu 14.04 image from the official docker registry and build on top of it the following contents of this dockerfile*”….remember the layered approach:)

4. Now, you can easily create and start a container using the “*docker run*” command. For example, the way I would run the ZNC image we made above:

docker run -d -v /docker/docker-znc/znc_config:/opt/znc/znc_config -p 18000:18000 --name znc znc`

Breaking this down:

a) We utilize a “-d” to tell the container to run in “*detached mode*”. What this means is your current console will not be pulled into the running processes stdin,stdout,and stderr.
b) The “*-v :*” defines a volume on the host we want to mount in our container. In this case, I have my znc configuration directory in my build dir and just mount from there. Note that both the source and dest must be absolute paths and will be mounted read/write!
c) For our “-p 18000:18000?, we are merely defining a <remote>:<local> port mapping. So port 18000 on the container will be directly mapped with port 18000 of the host. By default, docker will automagically make and manage the iptables nat rules to define these relations and push traffic through to your container. (this automation can be turned off)
d) Since its nice to be able to define a name for a container, we will just use simple “*znc*”. If you don’t define a name, one is made up for the container.
e) The last argument, just simply, “*znc*” actually defines the image we want to use. Note that since the image is local, there is no path or anything to use (docker handles all that, see /var/lib/docker if you’re interested). However, lets say you wanted to start a container from an image on the official registry, you could change this to something like mario/docker-znc!

This ends part 1. Part 2 will talk more about general practices with starting containers, saving/backing them up, and more!

Mario Loria is a builder of diverse infrastructure with modern workloads on both bare-metal and cloud platforms. He's traversed roles in system administration, network engineering, and DevOps. You can learn more about him here.