Bartosz Bąbol

Software engineering

YADT: Yet Another Docker Tutorial

These are my docker notes. They might be considered as some kind of tutorial. This content might change. Let’s start.

Prerequisites

I expect you have docker installed. Docker is available on all main platforms. Im working on mac and have docker community edition installed. If you need to install docker on your machine go to instructions on main page here

Introduction

“Docker is an open platform for developing, shipping, and running applications”- according to official documentation. Main idea is to manage application infrastructure in the same way you manage application itself. You want to have easy way of building ISOLATED space (container) where your application works because you hate to hear sentence “works on my machine!”. Virtual machines does exactly what you want but they are considered as ‘heavy’ and slow solution to a problem. Docker containers operates on host OS which means they can share resources so start and work very quickly.

I will skip theoretical part because official docs is a great source of knowledge. I encourage you to read it here. In this link you will (and should) find basic answers to questions like what is:

  • (more) motivation for using docker
  • Docker Engine (architecture of docker)
  • Image
  • Container
  • Image Registry (i.e Docker hub)

Docker building blocks

Container

Intro

Container is isolated space. By isolated space I mean own file system, process tree, networking and user space. Container is considered as operating system virtualization which means it shares host linux kernel.

Idea of containers in Docker is not new or unique. Different implementations of operating system virtualization:

LXC

Oracle solaris zones

FreeBSD Jails

Running first container

Containers are running images. Images might be published as repositories in docker hub You can ‘pull’ them similarly to cloning github repositories. To use published image you need to use docker client. If you want specify version of image you can do it like below:

1
docker pull ubuntu:16.04

If you want to pull the latest version you can skip the number:

1
docker pull ubuntu

Lets invoke second line

images

Now after downloading image you can preview it:

images

alternatively:

1
docker images

One of common problems which virtualization solves is: how to have the 2 different versions of the same library/program. Lets download older ubuntu:

1
docker pull ubuntu:14.04

and to see result:

images

Now you can run specific version:

1
docker run ubuntu:14.04

alternatively image ID (you dont have to specify whole ID, few unique characters is enough):

1
docker run 132b7

or skip the tag/ID and run latest:

1
docker run ubuntu

This is how we ‘instantiate’ docker image. Now we should see running instance of an image- container.

Lets list all containers:

And there is no running container. Why is that? Because default command of image ubuntu id bash so it executes and terminates. We can change this command:

We are listing all files by adding more arguments to docker run. Command

1
ls -l

terminates, prints the files and also executes. To see that this is true run ps command(similar to unix but lists containers instead of processes):

1
docker ps -a

Which will print you all stopped containers

as you see docker assigns some random, fancy names to containers. You can specify your own name with –name option.

1
docker run --name my_ubuntu ubuntu

If you want to get into container and look around by running it in foreground mode. By adding -i we run container in daemon mode. /bin/bash is primary process so it will be run in foreground.

1
docker run -i -t ubuntu

And you cant see bash prompt has changed and by executing command ps inside ubuntu container

1
ps

you can see that bash is primary process:

More about foreground mode here.

Image

Building your own image

Image is blueprint of your working container. It means you can have many working containers build from single image.

Let’s imagine following situation: you are writing app in go lang. That requires having go lang installed on your machine with all dependencies and whatever else your application needs. And you want to provide an image with whole environment set up for user to make sure your code will be executed in unified environment with the same OS and dependencies isolated from environment where it is being run.

Dockerfile

Docker provides command

1
docker build [OPTIONS] PATH | URL |

which is used to build images. As you see in usage above it takes as parameter PATH which is path to directory with file called Dockerfile. This file is specification of an image. It’s set of commands which docker needs to perform in order to build an image. Let’s look at mentioned earlier example which I prepared for this case. Repository is in my github.

This application is simple golang server written in framework called martini. This example is hello world copied from documentation of martini.

server.go
1
2
3
4
5
6
7
8
9
10
11
package main

import "github.com/go-martini/martini"

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}

And the point is there is a huge chance that you dont have go installed in your machine (as me for example) because you dont use this language in your everyday work. This is one of the case for docker. Lets look at the Dockerfile our blueprint for creating image and examine it step by step.

First thing is Dockerfile has to be named exactly like this. Capital D and no spaces or other characters.

1
FROM ubuntu:16.04

First line is mandatory FROM instruction which specifies base image for our new image. In my case I wanted to run my go script in ubuntu with tag 16.04.

1
MAINTAINER Bartek <bbartek91@gmail.com>

Next line is MAINTAINER which specifies who created the image. This line doesnt change functionality of the image at all. Its for informational purposes.

1
ENV GOVERSION 1.8.3

Next instruction is ENV. Which can specify environment variables moreover they can be used as variables in Dockerfile, so here you see example how I’ve extracted goland version to a variable.

1
2
3
4
5
RUN apt-get update && apt-get install --no-install-recommends -y \
  ca-certificates \
    curl \
    git-core
RUN curl -s https://storage.googleapis.com/golang/go${GOVERSION}.linux-amd64.tar.gz | tar -v -C /usr/local -xz

Next lines are RUN. This is the way you pass your commands to image. As you see commands specified in this particular example are necessary instructions to install golang in ubuntu.

Going further:

1
2
3
ENV GOPATH /go
ENV GOROOT /usr/local/go
ENV PATH /usr/local/go/bin:/go/bin:/usr/local/bin:$PATH

This is setting the environment variables in our image. Its required by golang to specify them in order to use language in bash shell.

Next I create directory for my app:

1
RUN mkdir go_server

And move my local application file to docker image using ADD instruction. We can specify file location using relative path as here in example. Second parameter is directory in container and it can be also relative to current working path in container:

1
ADD server.go /go_server

I want to have working directory to be /go_server. In order to achieve that:

1
WORKDIR /go_server

Having my environment fully setup I can finally install martini framework:

1
RUN go get github.com/go-martini/martini

And specify the entrypoint of my image so which process will have PID 1 in working instance of the image -> container

1
ENTRYPOINT ["go", "run", "server.go"]

Having Dockerfile specified we can build the image:

1
docker build -t golang /Users/bartek/projects/docker_blog/

Run

1
docker images

to see image ‘golang’. I hope Advantages of this kind of virtualization are obvious: I can publish this image in docker repository and user can pull it run with command:

1
docker run -it -p 3000:3000 golang

Where

1
-p 3000:3000

-p maps ports my host 3000 to docker 3000 where application works and avoid preparing whole environment. Of course this is a bit tedious because there is already prepared golang image here which you I should use in this case but I wanted to build it from scratch just to play with different Dockerfile options.

Data Volumes

Example with docker container from previous paragraph is example of stateless container. We don’t have any state inside this container it’s simple web server with returning some fixed response. This is perfect case for docker because since dealing with state might be difficult stateless services are portable and easy to scale. But in real world scenarios we want to containerize state also i.e databases.

Containers are meant to be lightweight, easy to stop, start, restart etc. They cannot store inside any state after being stopped. Solution for this particular problem is data volumes.

File system in docker container is UnionFS. This file system is layered and thats the reason why it’s used for docker containers and images. More in docs

And detailed volume docs

Cleaning docker

If you want to remove all unused containers, volumes, networks and images (both dangling and unreferenced):

1
docker system prune

End

This is maybe not extensive tutorial more my notes from learning docker. Check out next post here where I use sbt docker to run multi-container application.

Comments