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 multi-part series on how you can use it.
This guide is intended on 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 a number of articles over the next few weeks to cover all of the major parts of Docker, as well as some real world examples and usages.
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 setup tools to suit both Windows and OSX. The easiest of these is Boot2Docker, which supports both Windows and OSX. Here’s the links to the installation instructions:
OSX also has Kitematic, which provides a GUI wrapper around many of the commands yet still allows you to run them directly.
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 initialise 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 setup and configure the environment perfectly for you.
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, lets 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:
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 a read of the official Docker Help page with suggestions for further assistance.
Command: docker pull
Note: pulling an image from the official repository is automatically done when you first try to run a container, but 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 through 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 that various layers for the Python image and extracted them. What you also may have noticed is the version number, or there lack of. 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 then 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 quicker also, so the layered image approach is great for efficiency.
Command: docker images
Lets have a look at what Docker images we have now:
If you’ve copied my Python examples above, you’ll see an output like this:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE python latest 1c03bc124c06 2 weeks ago 757.2 MB python 2.7 d833e0b23482 2 weeks ago 747.9 MB
There’s two important things to note here. Firstly, 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.
Secondly, 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 but of course always double check you’re removing the right image before proceeding!
Command: docker run
This is where the fun begins! Before we start, I just want to emphasise that containers provide process isolation, their own file system and their own networking layer. The speed of which Docker does this is extremely fast, as you’ll see when you try it for yourself.
Lets 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 as we had it in interactive mode.
Lets 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 app.py
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 0c8688ec4cda2d4f54abf5b5d49ff243a0bbf416dfc322b007a1f9c4f99d034d
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:
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.
Here’s the output so far:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0c8688ec4cda training/webapp:latest "python app.py" 35 minutes ago Up 35 minutes 0.0.0.0:5000->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. 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:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0c8688ec4cda training/webapp:latest "python app.py" 35 minutes ago Up 35 minutes 0.0.0.0:5050->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
Lets 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 app.py" 43 minutes ago Up 43 minutes 0.0.0.0:5050->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 app.py
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.
Handy Hint: 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 4 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.
docker start conetix_test
You’ll see a conformation that the container started if it echos 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 http://0.0.0.0:5050/ (Press CTRL+C to quit) * Running on http://0.0.0.0:5050/ (Press CTRL+C to quit) * Running on http://0.0.0.0:5050/ (Press CTRL+C to quit) 172.17.42.1 - - [21/May/2015 01:02:12] "GET / HTTP/1.1" 200 - 172.17.42.1 - - [21/May/2015 01:02:12] "GET /favicon.ico HTTP/1.1" 404 - * Running on http://0.0.0.0:5050/ (Press CTRL+C to quit) 172.17.42.1 - - [21/May/2015 01:04:41] "GET / HTTP/1.1" 200 - 172.17.42.1 - - [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’s of course more complex logs. Docker 1.6 also allows you to configure centralised logging (ie syslog), but that’s an entire article in itself!
Hopefully if you’ve made it through to the end of this article, you 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 is the speed of creation, the simplicity of the container running environment and 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. Each week 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.