Docker Basics: A Practical Starter Guide
The only guide you'll need to get going with Docker.
Join the DZone community and get the full member experience.Join For Free
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.
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.
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:
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.
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.
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.
Let’s 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 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.
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!
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 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.
Once we’ve created a container, we can list them all with the
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. That’s because the
ps command only shows the running containers. If we want to see all of them, we can use the
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.
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 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.
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.
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 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.
* 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 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.
This article was originally published on Conetix by Tim Butler. With his kind permission, we’re sharing it here for Codeship readers.
Published at DZone with permission of Moritz Plassnig, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.