Tobias T–

Get back to Howtos I create to explain things

Docker Introduction

Docker is a platform to run software in a Linux environment. It can be used on production servers as well as on development machines to provide a reproducible environment. This how-to describes the concept behind Docker and how you can get started with it.

What is Docker?

Docker is a similar concept to Linux Containers or FreeBSD jails. The idea is to create a sandboxed environment to run services, without changing the main operating system. Docker describes itself like this:

Docker is the … software containerization platform

In Dockers terms software is abstracted into "containers" that will run independently. The services can communicate through public interfaces but installed packages cannot conflict with each other. Installing multiple versions of a service is now a matter of seconds, instead of a manual process of changing individual configurations.

A practical example could be to have two docker containers for different versions of PHP. One container would deliver the scripts through PHP 7 and another one with PHP 5. Both would be possible to run at the same time, on one machine.

Installing Docker

Docker provides an official desktop application for macOS and Windows. This installer will provide all the things you need and use the native Hypervisor.Framework on macOS to virtualize Linux.

Alternately you can also install Docker through Homebrew. This is as easy as executing this command:

brew install docker docker-machine

It is your choice which virtualization software you want to use with it. Docker supports VirtualBox natively, but you can also use it with the Parallels Desktop or VMWare drivers.

Docker terminology

Docker has different concepts that interact with each other.

Machine

As described in the introduction, Docker needs a Linux environment to run in. When you are running docker on Linux, by default your running operating system will be the machine used by Docker.

If you use macOS or Windows you need a virtual machine to run Linux. This virtual machine will then be used by Docker transparently.

Image

Images are a snapshot of an operating system at a given point in time. They are similar to a live CD of a Linux distribution. Once you downloaded an image, you can reuse it as many times as you want to. They should be ready to be run and no additional steps should be necessary to use the provided services.

Container

Containers are a executed instance of an image. This might be successfully executed commands after which a container was stopped automatically, or a running instance of a permanent process. Usually you will start a process in the foreground and the container will run as long as the process runs. Once the process stops, the container will be stopped.

Getting started

Let's see how we can use this different concepts.

Managing Machines

First we start of with creating a machine. Remember that this is only needed if you are running macOS or Windows, and you did not install Docker through the official application for your platform.

To create a new machine we just have to provide a driver that should be used:

docker-machine create --driver [driver] [machine name]

As I have VirtualBox installed, I’ll use the virtualbox driver. The result of the command should be similar to this output:

$ docker-machine create --driver virtualbox default
Running pre-create checks...
Creating machine...
(default) Copying /Users/tobias/.docker/machine/cache/boot2docker.iso to /Users/tobias/.docker/machine/machines/default/boot2docker.iso...
(default) Creating VirtualBox VM...
(default) Creating SSH key...
(default) Starting the VM...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env default

This automatically started the virtual machine with the Boot2Docker image. It will provide us with the Linux system we need to run Docker containers. Next we want to display all the machines we have available to us. For this we use the ls sub command:

docker-machine ls

This should display a list with the machine we just created:

$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
default   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.12.6 

In theory we can create multiple machines with this command. Docker needs to know to which machine we want to talk to. This is handled through environment variables. To display the environment variables for a machine we can use the env command:

docker-machine env [machine name]

Running this command will display a similar output to this one:

$ docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/tobias/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell: 
# eval $(docker-machine env default)

As you can see, this provides a template to define the DOCKER_HOST environment variable. It will be used by Docker as a target for all the commands we execute later.

As just displaying it does not change much, we need to define these variables. We can do that easily with the eval command:

eval $(docker-machine env [machine name])

This will execute the env command, which will display the variables and the eval command will evaluate this output to define the variables. You can verify this by displaying the content of the variable:

$ echo $DOCKER_HOST
tcp://192.168.99.100:2376

There will be the time when you want to stop this machine again to free up some resources for other tasks:

docker-machine stop [machine name]

This will display something like this:

$ docker-machine stop default
Stopping "default"...
Machine "default" was stopped.

As we want to continue to see how Docker works, we better start the machine again. Unsurprisingly there is the start command:

docker-machine start [machine name]

And now we should see this output:

$ docker-machine start default
Starting "default"...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Machine "default" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

Remember that this will not change your environment. If you want to make sure you are talking to this machine, you should listen to Docker and run the env command again.

Managing Images

As we now have a machine to run Docker in, we need to look for images. Docker itself provides a central registry for images in their Docker Hub. We can search for images in there with the search command:

docker search [query]

Let's search for an Ubuntu image that we can use for our test cases:

$ docker search ubuntu
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
ubuntu                            Ubuntu is a Debian-based Linux operating s...   5357      [OK]       
ubuntu-upstart                    Upstart is an event-based replacement for ...   69        [OK]       
rastasheep/ubuntu-sshd            Dockerized SSH service, built on top of of...   65                   [OK]
consol/ubuntu-xfce-vnc            Ubuntu container with "headless" VNC sessi...   37                   [OK]
torusware/speedus-ubuntu          Always updated official Ubuntu docker imag...   27                   [OK]
ubuntu-debootstrap                debootstrap --variant=minbase --components...   27        [OK]       
ioft/armhf-ubuntu                 [ABR] Ubuntu Docker images for the ARMv7(a...   20                   [OK]
nickistre/ubuntu-lamp             LAMP server on Ubuntu                           14                   [OK]
nuagebec/ubuntu                   Simple always updated Ubuntu docker images...   13                   [OK]
nickistre/ubuntu-lamp-wordpress   LAMP on Ubuntu with wp-cli installed            8                    [OK]
nimmis/ubuntu                     This is a docker images different LTS vers...   6                    [OK]
maxexcloo/ubuntu                  Base image built on Ubuntu with init, Supe...   2                    [OK]
admiringworm/ubuntu               Base ubuntu images based on the official u...   1                    [OK]
1and1internet/ubuntu-16           Ubuntu 16 Base Image                            1                    [OK]
darksheer/ubuntu                  Base Ubuntu Image -- Updated hourly             1                    [OK]
jordi/ubuntu                      Ubuntu Base Image                               1                    [OK]
datenbetrieb/ubuntu               custom flavor of the official ubuntu base ...   0                    [OK]
lynxtp/ubuntu                     https://github.com/lynxtp/docker-ubuntu         0                    [OK]
teamrock/ubuntu                   TeamRock's Ubuntu image configured with AW...   0                    [OK]
labengine/ubuntu                  Images base ubuntu                              0                    [OK]
widerplan/ubuntu                  Our basic Ubuntu images.                        0                    [OK]
esycat/ubuntu                     Ubuntu LTS                                      0                    [OK]
vcatechnology/ubuntu              A Ubuntu image that is updated daily            0                    [OK]
webhippie/ubuntu                  Docker images for ubuntu                        0                    [OK]
konstruktoid/ubuntu               Ubuntu base image                               0                    [OK]

The first one is the official one we want. The command to download the image is pull:

docker pull [image name]

You'll get an output like this:

$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
b3e1c725a85f: Pull complete 
4daad8bdde31: Pull complete 
63fe8c0068a8: Pull complete 
4a70713c436f: Pull complete 
bd842a2105a8: Pull complete 
Digest: sha256:7a64bc9c8843b0a8c8b8a7e4715b7615e4e1b0d8ca3c7e7a76ec8250899c397a
Status: Downloaded newer image for ubuntu:latest

To see which images we have downloaded, we use the image list command:

docker images

This displays a list like this:

$ docker image list
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              104bec311bcd        4 weeks ago         129 MB

Managing Containers

Now that we have downloaded an image, we want to use it. To start up a container we can simply run a command inside an image. To do so, we use the run command:

docker run --name [container name] [image name] [command]

Let's keep it simple for this demo and just display the name of the operating system we are running:

$ docker container run --name uname ubuntu uname -a
Linux 481238a21e89 4.4.41-boot2docker #1 SMP Wed Jan 11 03:05:24 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

This did run the command inside the container and stopped it again. We can confirm this by displaying all running containers with the container list command:

docker container list

Running it right now will display… nothing:

$ docker container list
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

We don't see any container because uname displayed the name of our operating system and then exited successfully. To display all containers we have, we change the Docker command slightly to also display not running ones:

$ docker container list -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
481238a21e89        ubuntu              "uname -a"          22 seconds ago      Exited (0) 21 seconds ago                       uname

Let's try to do something that keeps a container running. For this we will execute a command that will not immediately end:

$ docker container run --name bash --interactive --tty ubuntu bash
root@89804be8f530:/# 

The cursor will now await your commands. They will be executed inside our new container, which is based on the Ubuntu image. For example we can now execute the uname again and see the same output as before:

root@89804be8f530:/# uname -a
Linux 89804be8f530 4.4.41-boot2docker #1 SMP Wed Jan 11 03:05:24 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
root@89804be8f530:/# 

Make sure you added the additional parameters to the container run command:

  • --interactive (short -i): Keep STDIN open even if not attached
  • --tty (short -t): Allocate a pseudo-TTY

More information about them is available inside the Docker documentation.

We could now use the standard exit command to exit the bash, which then would terminate the image again. As we want to keep it running we need to "detach" from it. To do so we execute two keyboard shortcuts: Ctrl-p, followed by Ctrl-q.

To confirm that it's still running we can use the container list command again:

$ docker container list
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
89804be8f530        ubuntu              "bash"              31 seconds ago      Up 31 seconds                           bash

Getting back to the container is called re-attaching. We do this by running a command inside a container through container exec:

docker container exec -i -t [container ID] [command]

This will bring us straight back into our container:

$ docker container exec -i -t 89804be8f530 bash
root@89804be8f530:/# 

It's important to note that this is not the bash command we used before. This is a separate process that is now running because we executed it. To confirm this we can check for all running bash commands:

root@89804be8f530:/# ps ax | grep bash
 1 ?        Ss+    0:00 bash
10 ?        Ss     0:00 bash
20 ?        S+     0:00 grep --color=auto bash

We can use this exec command to perform any Linux command we can think of. This can include installing packages, starting more services or deleting files.

Next we want to stop this container again. To do this we need to detach ourselves from the image again like described above. We also could use exit to kill our current bash instance. This will not stop the container as the previous bash process is still running. Then, as with the machines, this is done through the container stop command:

docker container stop [container ID]

Let's do it:

$ docker container stop 89804be8f530
89804be8f530

We can use the container list command again to confirm that it's stopped:

$ docker container list
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

If we want to get back to work within this machine, we use the container start command to get it back running.

docker container start [container ID]

This will look like this:

$ docker container start 89804be8f530
89804be8f530

The container list command will now show that it's running again:

$ docker container list
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
89804be8f530        ubuntu              "bash"              2 minutes ago       Up 8 seconds                            bash

and we can execute commands inside the container again:

$ docker container exec -i -t 89804be8f530 uptime
12:35:32 up 6 min,  0 users,  load average: 0.01, 0.38, 0.28

One more note about names: we could also have used the name of the container in all these commands (bash). I chose to use the container ID as it is better to distinguish from the possible commands we execute. For Docker there is no difference, but for us humans the names are more likely to be remembered. The last command could also have looked like this:

$ docker container exec -i -t bash uptime

Cleaning up

We created a machine, downloaded one image and created some containers. To finish this how-to we have to clean up what we did.

Docker has the system df command (added in Docker 1.13) to show how much space is used. When we execute it, we see all our images can containers:

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              1                   1                   129 MB              0 B (0%)
Containers          2                   1                   9 B                 0 B (0%)
Local Volumes       0                   0                   0 B                 0 B

Let's clean up. First we want to remove the container with the container rm command:

docker container  rm [container ID]

To not manually stop it first we will execute it with the -f parameter, which will do it for us:

$ docker container rm -f 89804be8f530
89804be8f530

Docker only shows us the running images by default. To make sure the "uname" image is also deleted, we need to display all images, and then delete the image manually.

$ docker container list -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
481238a21e89        ubuntu              "uname -a"          4 minutes ago       Exited (0) 4 minutes ago                       uname
$ docker container rm 481238a21e89
481238a21e89

Next we don't need the Ubuntu image anymore. Deleting an image is done by the image rm command:

docker image rm [image name]

Let's see:

$ docker image rm -f ubuntu
Untagged: ubuntu:latest

Finally, if we really want to delete what we created, we need to delete our default machine. Handling machines is again done with the docker-machine command, deleting is implemented through the rm command:

docker-machine rm [machine name]

Remove it:

$ docker-machine rm default
About to remove default
Are you sure? (y/n): y
Successfully removed default

I did show you how to delete them manually to learn how we can delete individual images and containers. In practice we could have executed only this command as it would have deleted the image and the containers as well. Docker 1.13 also added a new command that makes all this cleanup process much easier: system prune:

$ docker system prune
WARNING! This will remove:
    - all stopped containers
    - all volumes not used by at least one container
    - all networks not used by at least one container
    - all dangling images
Are you sure you want to continue? [y/N] y
Deleted Containers:
0bf822f79987a3130cb990598de3154ab34a7b779a1e5255c5e217a118acea6e
2f9f97147034ba6d2517b79c4945700956a2939ea2afc11064c66a5129a52d2e

Total reclaimed space: 9 B

Additionally there are also separate images prune and container prune commands to delete only images or containers.

Summary

We saw how to create machines, download images and execute commands inside containers. The next time you need a service for a project, you could think about installing it inside a Docker container. This (for example) Postgres database would not change the operating system of your working environment and once you don't need it anymore you could easily delete it.

Docker helps you with the dependencies of your project. Now it's up to you to decide when it is the right tool to help you.


Update

On 19. January 2017 Docker 1.13 was released. Relevant changes for this article included grouped command, the prune commands and system df. I updated this article accordingly to use the new best practice and mention the new commands.

Also available at

This section try to explain what I do for a living. If you think I could help you with a project, you can hire me though my little company succont.