Docker Basics: A Practical Starter Guide

Written by: Tim Butler
10 min read

This article was originally published on Conetix by Tim Butler. With his kind permission, we are sharing it here for Codeship readers.

In my last article, "What is Docker," I covered the basics of what Docker is and why you should use it. This article will be the first part in a three-part series on how you can use it.

This guide is intended to be a simple but practical way to get started with the basics of Docker. Although there’s a lot covered just in the one guide, it barely scratches the surface of what Docker can do and the power it has when it comes to orchestration. We’ll progress through two more articles over the next two weeks to cover all of the major parts of Docker, as well as some real-world examples and uses.

Installing Docker

Docker is currently a Linux-only platform, which means you’ll need access to a Linux-based server or virtual machine (VM). Don’t worry; if you don’t run Linux natively, there are a number of easy-to-set-up tools to suit both Windows and OS X. The easiest of these is Boot2Docker, which supports both Windows and OS X. Here are the links to the installation instructions:

OS X also has Kitematic, which provides a GUI wrapper around many of the commands yet still allows you to run them directly.

Note: This guide will assume that you’ve installed Boot2Docker on your local machine. While nearly all the commands should be the same, there may be some variance in the log output if you're using Docker on a different platform.

One thing to ensure is that you’re using the latest 1.6 release (in fact the latest when this article was written is 1.6.2). As Docker is rapidly evolving, keeping up to date is important to gain access to the latest features.

After you have Boot2Docker installed, you then need to initialize it and start it. Please read through the official installations and carefully ensure you’ve followed the processes. Essentially, there should be three main steps after installation. You’ll need to run boot2docker init, then boot2docker start and boot2docker shellinit to set up and configure the environment perfectly for you.

First Steps

Now that you have Docker installed, lets have a quick look at what’s available. If you’ve come from the web development world, the command structure is similar to Grunt, Bower, npm, and similar, where every command starts with the application name.

For example, let's view the version numbers for the Docker installation. This is also a great way to ensure Docker is installed and running correctly. To do this, run:

docker version

This should give you an output similar to this:

Client version: 1.6.2
Client API version: 1.18
Go version (client): go1.4.2
Git commit (client): 7c8fca2
OS/Arch (client): darwin/amd64
Server version: 1.6.2
Server API version: 1.18
Go version (server): go1.4.2
Git commit (server): 7c8fca2
OS/Arch (server): linux/amd64

As you can see, I’m running the latest release (as of May 2015), which is 1.6.2.

If you don’t see an output similar to this, it probably means you have something wrong with your installation. Make sure you’ve first run the boot2docker init. If this hasn’t resolved the issue, please have read the official Docker Help page with suggestions for further assistance.

Docker Images

Command: docker pull

Note: Pulling an image from the official repository is automatically done when you first try to run a container, but it can be handy if you’re going to be offline when working.

The docker pull command will download the image and associated layers from the Docker Repository. These layers include everything from the base operating system to the end application. You can browse and search the images via the Docker Registry, which has over 13,000 publicly available images.

Lets pull down the latest Python image:

docker pull python

You should then see an output like this:

Docker will automatically pull in the various layers for the Python image and extract them. What you also may have noticed is the version number or the lack thereof. Because we didn’t specify a version, Docker automatically grabbed the “latest” version for us. The versions are controlled by tags, so if we want a different version or want to maintain a specific version, we can specify it.

For Python, we can get a copy of Python 2.7 by running:

docker pull python:2.7
2.7: Pulling from python
1c9639a2194b: Pull complete
80148ca1dc14: Pull complete
034f2f72c1bf: Pull complete
987ff7783988: Pull complete
d833e0b23482: Already exists
3cb35ae859e7: Already exists
41b730702607: Already exists
e66a33f451f4: Already exists
05bacbdfa6eb: Already exists
ecff3a5a9760: Already exists
909f017029da: Already exists
f2d8371e087a: Already exists
python:2.7: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:3f57764184f783a573bd974c93f367eb2678879b1327cae3b3d1b96d5f285ca0
Status: Downloaded newer image for python:2.7

You’ll see that because we’d already previously downloaded Python, there were only a few layers which were different. The time to get this version should have been faster also, so the layered image approach is great for efficiency.

Command: docker images

Let's have a look at what Docker images we have now:

docker images

If you’ve copied my Python examples above, you’ll see an output like this:

python latest 1c03bc124c06 2 weeks ago 757.2 MB
python 2.7 d833e0b23482 2 weeks ago 747.9 MB

There are two important things to note here. First, it doesn’t display the intermediate images, which form the layers. You can view these with docker images -a if you’re really interested, but for the most part you don’t need to worry about them.

Second, the “virtual size” appears to be nearly the same for the two, despite the fact that Docker has the intermediate images in common. This size lists the combination of all the images, but in reality the space taken on disk is significantly less.

Command: docker rmi

You can remove unused images using the rmi command. You can reference the image either by the ID or the name and also the version number. For example, let’s remove the Python 2.7 image:

docker rmi python:2.7

And the output should be similar to this:

Untagged: python:2.7
Deleted: d833e0b2348244ed5f12b08e231ad8aa01d8e381d9746285474dfc413c79fc46
Deleted: 987ff7783988de66ff118ca0b2a747a7884c99bbd911b0c267c308617fdf7a9b
Deleted: 034f2f72c1bfec66d43f5fe30f85ad325e2c6eaf1caf571c4d706a14b5446ed6
Deleted: 80148ca1dc140781b5c90a137972c1d9edc98ccaea3a1fee0b8aa37d1b28c14a
Deleted: 1c9639a2194b30eef4a121fcc1550296b09cdfe5d6ca8790011486816eca3981

If the image is in use, Docker shouldn’t let you delete it. Still, always double check you’re removing the right image before proceeding!

Using Containers

Command: docker run

This is where the fun begins! Before we start, I just want to emphasize that containers provide process isolation, their own file system, and their own networking layer. The speed with which Docker does this is extremely fast, as you’ll see when you try it for yourself.

Let's try by simply running Python:

docker run -t -i python

Because we’ve previously pulled down the image for the latest version of Python, it could create the container and run it nearly instantly. If it was an image which you hadn’t pulled down or used before, Docker will automatically pull down the images as part of the run command.

You should see an output like this:

Python 3.4.3 (default, Apr 30 2015, 05:46:35)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
> > >

Because we ran this with the -t flag, it created a pseudo-TTY in which Python ran. This also needs the -i for interactive mode so that you can send input to the program. For those not familiar with Python, you can simply press Ctrl + D to exit. As it exits, it also shuts down the container since we had it in interactive mode.

Let's start a basic web application as a daemon (specified with the -d flag) and have a look:

docker run -d -p 5050:5000 training/webapp python

You’ll see that in this example, Docker automatically downloaded the images required to run it and then started. The output should look something like this:

latest: Pulling from training/webapp
23f0158a1fbe: Pull complete
0a4852b23749: Pull complete
7d0ff9745632: Pull complete
99b0d955e85d: Pull complete
33e109f2ff13: Pull complete
cc06fd877d54: Pull complete
b1ae241d644a: Pull complete
b37deb56df95: Pull complete
02a8815912ca: Already exists
e9e06b06e14c: Already exists
a82efea989f9: Already exists
37bea4ee0c81: Already exists
07f8e8c5e660: Already exists
Digest: sha256:06e9c1983bd6d5db5fba376ccd63bfa529e8d02f23d5079b8f74a616308fb11d
Status: Downloaded newer image for training/webapp:latest

You’ll note the large ID string at the bottom starting with 0c8688, which is the unique identifier for the container. The -p 5050:5000 maps the network port 5050 on the host to network port 5000 within the container. This is how you make the network of the container accessible to both other containers and to the world.

As this is a basic Flask web application, you should now be able to open the link in a browser. If you're using Boot2Docker, you'll first need to determine the IP of your Docker VM. To do this, run:

boot2docker ip

This should return a local IP which your workstation or laptop can connect to. To test the site, use the IP returned and go to http://<boot2dockerip>:5050/. It’s not much, but you should see hello world displayed to confirm it’s all working as expected.

Command: docker ps

Once we’ve created a container, we can list them all with the ps command.

docker ps

Here’s the output so far:

0c8688ec4cda training/webapp:latest "python" 35 minutes ago Up 35 minutes>5000/tcp jovial_pasteur

We can see the information about container 0c86, including details about when it was created, the image used, network ports, and how long it’s been running.

You’ll also note that there’s only one container listed. That's because the ps command only shows the running containers. If we want to see all of them, we can use the -a flag:

docker ps -a

You should see an output like this:

0c8688ec4cda training/webapp:latest "python" 35 minutes ago Up 35 minutes>5000/tcp jovial_pasteur
33b733231b4d python:latest "python3" 3 minutes ago Exited (0) 3 minutes ago condescending_nobel
f935a7f98d00 python:latest "python3" 4 minutes ago Exited (0) 3 minutes ago elated_swartz

The other part you’ll notice is the odd name. Don’t worry, you haven’t been hacked! Docker uses a random name based on an adjective and then a famous scientist or hacker. It’s better than simply having a long ID to refer to, and we can rename this quite easily.

Command: docker rename

Let's give our test app a better name:

docker rename jovial_pasteur conetix_test

Now if we run ps, we should see:

0c8688ec4cda training/webapp:latest "python" 43 minutes ago Up 43 minutes>5000/tcp conetix_test

Of course, we can specify the name at the time of creation by simply using the –name flag. For example:

docker run -d -P 5051:5000 --name app_test training/webapp python

This will name the instance app_test, which is far more useful when you have more than a handful of containers.

Command: docker stop

If we no longer need our test application, we can stop it by using the docker stop command. We can either use the name or ID of the container to run the command. Here’s the command if we use the name:

docker stop conetix_test

You could also use the ID number:

docker stop 0c8688ec4cda

To confirm, Docker will echo the ID or the container name back to you as the return of the function. As in this example, the time to stop a container may seem a lot longer than the startup time. If the Docker instance doesn’t listen for a standard SIGTERM call (in order for it to cleanly shutdown), it will wait 10 seconds before calling a SIGKILL to terminate the process. As this is a basic training example, it will simply wait the 10 seconds and then kill the process.

Note: You can simply use the beginning abbreviation to refer to the container ID. Docker needs enough information to ensure the name is unique, so generally the first four characters of the ID are fine.

Command: docker start

We can of course start the container again with virtually the same syntax as stopping. You can reference the container by both the ID and the name as before.

For example:

docker start conetix_test

You’ll see a confirmation that the container started if it echoes back the container name or ID, depending on what you called to start it. You can confirm that it’s running again by using the docker ps command and/or by calling the webpage again.

Command: docker logs

  * Running on (Press CTRL+C to quit)
  * Running on (Press CTRL+C to quit)
  * Running on (Press CTRL+C to quit) - - [21/May/2015 01:02:12] "GET / HTTP/1.1" 200 - - - [21/May/2015 01:02:12] "GET /favicon.ico HTTP/1.1" 404 -
  * Running on (Press CTRL+C to quit) - - [21/May/2015 01:04:41] "GET / HTTP/1.1" 200 - - - [21/May/2015 01:04:41] "GET /favicon.ico HTTP/1.1" 404 -

As this is a Flask based test server, these files are straight from the Flask test webserver. You can see that it’s logged the starting of the service multiple times as the stop/start examples above were run. You can also see the calls to display the hello world page (GET /) as well as the browser seeing if the favicon existed.

For more complex systems, there are of course more complex logs. Docker 1.6 also allows you to configure centralized logging (i.e., syslog), but that’s an entire article in itself!


Hopefully if you’ve made it through to the end of this article, you've been able to follow it on your own instance of Docker and should have a better understand of how it works. The takeaway points to note are:

  • the speed of creation

  • the simplicity of the container-running environment

  • the way to access the containers

Even at this basic level, Docker offers quite a powerful system for both development and production deployment. Of course, as a basic intro, this has only just begun to scratch the surface of what’s available in Docker. We'll be publishing further articles, which will cover the more advanced usage of Docker as well as real-world deployments and how we implemented them.

If there are any questions or parts you need assistance with, please feel free to ask in the comments below.

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.