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.
Alternately you can also install Docker through Homebrew. This is as easy as executing this command:
brew install docker docker-machine
Docker has different concepts that interact with each other.
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.
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.
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.
Let's see how we can use this different concepts.
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:
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
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 $(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
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.
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
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
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:
This displays a list like this:
$ docker image list REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 104bec311bcd 4 weeks ago 129 MB
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
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:
-i): Keep STDIN open even if not attached
-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
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
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
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 (
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
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]
$ docker image rm -f ubuntu Untagged: ubuntu:latest
Finally, if we really want to delete what we created, we need to delete our
Handling machines is again done with the
docker-machine command, deleting is implemented through the
docker-machine rm [machine name]
$ 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:
$ 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.
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.
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.