Objectives

Understanding container concepts & how to create a docker image using the docker cli.

Containers Concepts

What are Containers?

Containers are self contained, immutable entities that include everything required to run a piece of software via a container runtime. Docker is a container runtime. It can run on different platforms. Containers are instances of a docker image. Docker images can be shared via push and pull commands with the docker cli, similar to git. Images are made up of layers. Each layer builds on the previous layer by modifying the image filesystem or metadata.

Self Contained

Containers run in isolation from each other, and any other processes running on the OS. This happens at the kernel in Linux by sandboxing processes and controlling their resource allocations via control groups (cgroups).

Immutability

Containers are immutable. The have a read-only filesystem. If a container is restarted, the filesystem is the same as the very first time it was run. The files in a container are whatever is included in the docker image.

Cross Platform

The Docker container runtime (also know as the Docker Engine) is available for Linux, macOS & Windows. Linux based containers can be run on any of these platforms. It is possible to run Windows containers on certain versions of Windows.

Layered

Images are made up of 1 or more layers. Different images can have the same layer in common. For example, an nginx and Apache image could both use centos as the base image. When sharing images, it is only necessary to send the layer differences rather than the full image.

Setting up Docker

To test docker is installed & running ok, run this command:

$ docker --version
Docker version 1.12.6, build ae7d637/1.12.6

You can list images on you local machine.

$ docker images

To pull an image from Docker Hub, use the docker pull command e.g.

$ docker pull golang:1.8.1-onbuild
Trying to pull repository docker.io/library/golang ... 
sha256:93fe73ae67ed0f1391b49495bc6a21106617f61f16bbc61a885674d7174d8070: Pulling from docker.io/library/golang
10a267c67f42: Pull complete 
fb5937da9414: Pull complete 
9021b2326a1e: Pull complete 
96109dbc0c87: Pull complete 
57d8fbf72ff8: Pull complete 
38d688423455: Pull complete 
c9ade847cc74: Pull complete 
5139017b86e5: Pull complete 
Digest: sha256:93fe73ae67ed0f1391b49495bc6a21106617f61f16bbc61a885674d7174d8070
Status: Downloaded newer image for docker.io/golang:1.8.1-onbuild

You should now see this image in the list if you run docker images again.

$ docker images
REPOSITORY                                                               TAG                                             IMAGE ID            CREATED             SIZE
docker.io/golang                                                         1.8.1-onbuild                                   3064d3aa065d        3 weeks ago         702.8 MB

Creating an image & running it

Creating the image

A Docker image is essentially a snapshot of a container. When used with the docker run command, they are used to create a running instance of a container. To create a Docker image you need a Dockerfile. A Dockerfile is essentially a set of instructions that Docker uses to create the Docker image. Example: https://github.com/feedhenry/wit-docker-example

To create an image from this example, first clone it.

$ git clone https://github.com/feedhenry/wit-docker-example.git

Then build the image:

$ cd wit-docker-example
$ docker build -t wit/example .
Sending build context to Docker daemon 56.32 kB
Step 1 : FROM golang:1.8.1-onbuild
# Executing 3 build triggers...
Step 1 : COPY . /go/src/app
Step 1 : RUN go-wrapper download
 ---> Running in 3279aec53159
+ exec go get -v -d
Step 1 : RUN go-wrapper install
 ---> Running in 7906d9740f3c
+ exec go install -v
app
 ---> cdd1ecbfd0cd
Removing intermediate container 3279aec53159
Removing intermediate container 7906d9740f3c
Removing intermediate container 64c608365953
Successfully built cdd1ecbfd0cd

The image should now be listed, with the latest tag.

$ docker images
REPOSITORY                                                               TAG                 IMAGE ID            CREATED             SIZE
wit/example                                                              latest              cdd1ecbfd0cd        34 seconds ago      708.7 MB

Dockerfile Reference https://docs.docker.com/engine/reference/builder/

Understanding the build process

The repository contents:

├── Dockerfile
├── main.go
└── README.md

The Dockerfile:

FROM golang:1.8.1-onbuild

The 'parent' Dockerfile with ONBUILD instructions

FROM golang:1.8

RUN mkdir -p /go/src/app
WORKDIR /go/src/app

# this will ideally be built by the ONBUILD below ;)
CMD ["go-wrapper", "run"]

ONBUILD COPY . /go/src/app
ONBUILD RUN go-wrapper download
ONBUILD RUN go-wrapper install

Running

Running & accessing the container

To run the image, use docker run:

$ docker run -it --rm --name wit wit/example:latest
+ exec app
2017/06/06 11:09:02 starting on port 3000

To call the server that's running, we need to find the container IP Address. You can find this out by running this command in a new terminal/console window.

$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' wit

Then curl the /hello endpoint

$ curl <ip address>:3000/hello
hello world

Try curl localhost to see what the response is:

$ curl localhost:3000/hello

Accessing the container on localhost

To access the server from localhost, you need to expose the port.

$ docker run -it --rm --name wit -p 3000:3000 wit/example:latest
+ exec app
2017/06/06 11:09:54 starting on port 3000

Now try curling it on localhost again.

$ curl localhost:3000/hello
hello world

Running containers remotely

Using 'docker machine'

A 'docker machine' is a local or remote instance running docker. More info on setting one up can be found at https://docs.docker.com/machine/. Deploying a container to a remote docker-machine can be done using the docker cli. Instead of all commands running against your local machine, they run against the remote machine. Whenever you build an image, it is being built on the remote docker machine. If you pull an image, it gets pulled onto the remote docker machine.

Using kubernetes or OpenShift

Kubernetes is a system for automating deployment, scaling, and management of containerized applications. It groups containers that make up an application into logical units for easy management and discovery. If you have a kubernetes cluster, you can run docker containers in that cluster via kubernetes templates & the kubectl cli. https://kubernetes.io/docs/user-guide/kubectl-overview/

If you have an OpenShift cluster (based on kubernetes), you can run docker containers in that cluster via openshift templates & the oc cli. https://openshift.io/

Kubernetes & OpenShift templates reference the docker image to use when running a container. The image must already be available on the cluster nodes, or be available to pull down. In order to make our image available, we can push it to a public repository on Docker Hub.

Pushing an image to a remote repository (optional)

You will need a Docker Hub account for this part. First, use docker tag to let docker know what the remote url will be.

$ docker tag wit/example:latest <docker hub username>/example:latest
$ docker push <docker hub username>/example:latest

Running the image in an OpenShift cluster (optional)

Once pushed, you can reference the image in the remote kubernetes/OpenShift cluster

$ oc new-project wit
$ oc run wit --image=<docker hub username>/example --port=3000 
deploymentconfig "wit" created

The pod can be exposed via a service & route

$ oc expose rc wit-1 --port=3000 --target-port=3000 --name=wit
$ oc expose service wit --name=wit

The exposed route can be used to access the running container.

$ curl wit-wit.osm4.skunkhenry.com/hello
$ hello world