A Docker Tutorial For Beginners
A Docker Tutorial For Beginners
A Docker Tutorial For Beginners
Star 5,541
INTRODUCTION
What is Docker?
Wikipedia defines Docker as
VMs are great at providing full process isolation for applications: there are
very few ways a problem in the host operating system can affect the
software running in the guest operating system, and vice-versa. But this
isolation comes at great cost — the computational overhead spent
virtualizing hardware for a guest OS to use is substantial.
https://docker-curriculum.com 3/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
GETTING STARTED
≡
This document contains a series of several sections, each of which explains
a particular aspect of Docker. In each section, we will be typing commands
(or writing code). All the code used in the tutorial is available in the Github
repo.
Note: This tutorial uses version 18.05.0-ce of Docker. If you find any
part of the tutorial incompatible with a future version, please raise an
issue. Thanks!
Prerequisites
There are no specific skills needed for this tutorial beyond a basic comfort
with the command line and using a text editor. This tutorial uses git clone
to clone the repository locally. If you don't have Git installed on your
system, either install it or remember to manually download the zip files
from Github. Prior experience in developing web applications will be
helpful but is not required. As we proceed further along the tutorial, we'll
make use of a few cloud services. If you're interested in following along,
please create an account on each of these websites:
https://docker-curriculum.com 4/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Until a few releases ago, running Docker on OSX and Windows was quite a
≡ hassle. Lately however, Docker has invested significantly into improving the
on-boarding experience for its users on these OSes, thus running Docker
now is a cakewalk. The getting started guide on Docker has detailed
instructions for setting up Docker on Mac, Linux and Windows.
Once you are done installing Docker, test your Docker installation by
running the following:
HELLO WORLD
https://docker-curriculum.com 5/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
The pull command fetches the busybox image from the Docker registry
and saves it to our system. You can use the docker images command to
see a list of all images on your system.
$ docker images
REPOSITORY TAG IMAGE ID CREATED
busybox latest c51f86c28340 4 weeks ago
Docker Run
Great! Let's now run a Docker container based on this image. To do that we
are going to use the almighty docker run command.
Wait, nothing happened! Is that a bug? Well, no. Behind the scenes, a lot of
stuff happened. When you call run , the Docker client finds the image
(busybox in this case), loads up the container and then runs a command in
that container. When we run docker run busybox , we didn't provide a
command, so the container booted up, ran an empty command and then
exited. Well, yeah - kind of a bummer. Let's try something more exciting.
Nice - finally we see some output. In this case, the Docker client dutifully
ran the echo command in our busybox container and then exited it. If
you've noticed, all of that happened pretty quickly. Imagine booting up a
virtual machine, running a command and then killing it. Now you know why
https://docker-curriculum.com 6/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
they say containers are fast! Ok, now it's time to see the docker ps
≡ command. The docker ps command shows you all containers that are
currently running.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
Since no containers are running, we see a blank line. Let's try a more useful
variant: docker ps -a
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED
305297d7a235 busybox "uptime" 11 minutes ago
ff0a5c3750b9 busybox "sh" 12 minutes ago
14e5bd11d164 hello-world "/hello" 2 minutes ago
So what we see above is a list of all containers that we ran. Do notice that
the STATUS column shows that these containers exited a few minutes ago.
You're probably wondering if there is a way to run more than just one
command in a container. Let's try that now:
Running the run command with the -it flags attaches us to an interactive
tty in the container. Now we can run as many commands in the container
as we want. Take some time to run your favorite commands.
Before we move ahead though, let's quickly talk about deleting containers.
We saw above that we can still see remnants of the container even after
we've exited by running docker ps -a . Throughout this tutorial, you'll run
docker run multiple times and leaving stray containers will eat up disk
space. Hence, as a rule of thumb, I clean up containers once I'm done with
them. To do that, you can run the docker rm command. Just copy the
container IDs from above and paste them alongside the command.
On deletion, you should see the IDs echoed back to you. If you have a
bunch of containers to delete in one go, copy-pasting IDs can be tedious. In
that case, you can simply run -
This command deletes all containers that have a status of exited . In case
you're wondering, the -q flag, only returns the numeric IDs and -f filters
output based on conditions provided. One last thing that'll be useful is the
--rm flag that can be passed to docker run which automatically deletes
https://docker-curriculum.com 8/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
the container once it's exited from. For one off docker runs, --rm flag is
≡ very useful.
Lastly, you can also delete images that you no longer need by running
docker rmi .
Terminology
In the last section, we used a lot of Docker-specific jargon which might be
confusing to some. So before we go further, let me clarify some
terminology that is used frequently in the Docker ecosystem.
https://docker-curriculum.com 9/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Docker Client - The command line tool that allows the user to interact
≡ with the daemon. More generally, there can be other forms of clients
too - such as Kitematic which provide a GUI to the users.
Docker Hub - A registry of Docker images. You can think of the registry
as a directory of all available Docker images. If required, one can host
their own Docker registries and can use them for pulling images.
Static Sites
Let's start by taking baby-steps. The first thing we're going to look at is how
we can run a dead-simple static website. We're going to pull a Docker
image from Docker Hub, run the container and see how easy it is to run a
webserver.
Let's begin. The image that we are going to use is a single-page website
that I've already created for the purpose of this demo and hosted on the
registry - prakhar1989/static-site . We can download and run the image
directly in one go using docker run . As noted above, the --rm flag
automatically removes the container when it exits and the -it flag
specifies an interactive terminal which makes it easier to kill the container
with Ctrl+C (on windows).
https://docker-curriculum.com 10/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Well, in this case, the client is not exposing any ports so we need to re-run
the docker run command to publish ports. While we're at it, we should
also find a way so that our terminal is not attached to the running
container. This way, you can happily close your terminal and keep the
container running. This is called detached mode.
In the above command, -d will detach our terminal, -P will publish all
exposed ports to random ports and finally --name corresponds to a name
we want to give. Now we can see the ports by running the docker port
[CONTAINER] command
You can also specify a custom port to which the client will forward
connections to the container.
https://docker-curriculum.com 11/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
To stop a detached container, run docker stop by giving the container ID.
In this case, we can use the name static-site we used to start the
container.
I'm sure you agree that was super simple. To deploy this on a real server
you would just need to install Docker, and run the above Docker command.
Now that you've seen how to run a webserver inside a Docker image, you
must be wondering - how do I create my own Docker image? This is the
question we'll be exploring in the next section.
https://docker-curriculum.com 12/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ Docker Images
We've looked at images before, but in this section we'll dive deeper into
what Docker images are and build our own image! Lastly, we'll also use
that image to run our application locally and finally deploy on AWS to
share it with our friends! Excited? Great! Let's get started.
$ docker images
REPOSITORY TAG IMAGE ID CREAT
prakhar1989/catnip latest c7ffb5626a50 2 hou
prakhar1989/static-site latest b270625a1631 21 ho
python 3-onbuild cf4002b2c383 5 day
martin/docker-cleanup-volumes latest b42990daaca2 7 wee
ubuntu latest e9ae3c220b23 7 wee
busybox latest c51f86c28340 9 wee
hello-world latest 0a6ba66e537a 11 wee
The above gives a list of images that I've pulled from the registry, along
with ones that I've created myself (we'll shortly see how). The TAG refers to
a particular snapshot of the image and the IMAGE ID is the corresponding
unique identifier for that image.
For simplicity, you can think of an image akin to a git repository - images
can be committed with changes and have multiple versions. If you don't
provide a specific version number, the client defaults to latest . For
example, you can pull a specific version of ubuntu image
To get a new Docker image you can either get it from a registry (such as the
Docker Hub) or create your own. There are tens of thousands of images
https://docker-curriculum.com 13/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
available on Docker Hub. You can also search for images directly from the
≡ command line using docker search .
Base images are images that have no parent image, usually images
with an OS like ubuntu, busybox or debian.
Child images are images that build on base images and add additional
functionality.
Then there are official and user images, which can be both base and child
images.
User images are images created and shared by users like you and me.
They build on base images and add additional functionality. Typically,
these are formatted as user/image-name .
https://docker-curriculum.com 14/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ This should be cloned on the machine where you are running the
docker commands and not inside a docker container.
The next step now is to create an image with this web app. As mentioned
above, all user images are based on a base image. Since our application is
written in Python, the base image we're going to use will be Python 3.
Dockerfile
A Dockerfile is a simple text file that contains a list of commands that the
Docker client calls while creating an image. It's a simple way to automate
the image creation process. The best part is that the commands you write
in a Dockerfile are almost identical to their equivalent Linux commands.
This means you don't really have to learn new syntax to create your own
dockerfiles.
The application directory does contain a Dockerfile but since we're doing
this for the first time, we'll create one from scratch. To start, create a new
blank file in our favorite text-editor and save it in the same folder as the
flask app by the name of Dockerfile .
We start with specifying our base image. Use the FROM keyword to do that
-
FROM python:3.8
The next step usually is to write the commands of copying the files and
installing the dependencies. First, we set a working directory and then copy
all the files for our app.
https://docker-curriculum.com 15/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ COPY . .
# install dependencies
RUN pip install --no-cache-dir -r requirements.txt
The next thing we need to specify is the port number that needs to be
exposed. Since our flask app is running on port 5000 , that's what we'll
indicate.
EXPOSE 5000
The last step is to write the command for running the application, which is
simply - python ./app.py . We use the CMD command to do that -
FROM python:3.8
# install dependencies
RUN pip install --no-cache-dir -r requirements.txt
https://docker-curriculum.com 16/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Now that we have our Dockerfile , we can build our image. The docker
≡ build command does the heavy-lifting of creating a Docker image from a
Dockerfile .
The section below shows you the output of running the same. Before you
run the command yourself (don't forget the period), make sure to replace
my username with yours. This username should be the same one you
created when you registered on Docker hub. If you haven't done that yet,
please go ahead and create an account. The docker build command is
quite simple - it takes an optional tag name with -t and a location of the
directory containing the Dockerfile .
If you don't have the python:3.8 image, the client will first pull the image
and then create your image. Hence, your output from running the command
will look different from mine. If everything went well, your image should be
ready! Run docker images and see if your image shows.
The last step in this section is to run the image and see if it actually works
(replacing my username with yours).
https://docker-curriculum.com 17/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
The command we just ran used port 5000 for the server inside the
container and exposed this externally on port 8888. Head over to the URL
with port 8888, where your app should be live.
Docker on AWS
What good is an application that can't be shared with friends, right? So in
this section we are going to see how we can deploy our awesome
application to the cloud so that we can share it with our friends! We're
going to use AWS Elastic Beanstalk to get our application up and running in
https://docker-curriculum.com 18/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
a few clicks. We'll also see how easy it is to make our application scalable
≡ and manageable with Beanstalk!
Docker push
The first thing that we need to do before we deploy our app to AWS is to
publish our image on a registry which can be accessed by AWS. There are
many different Docker registries you can use (you can even host your own).
For now, let's use Docker Hub to publish the image.
If this is the first time you are pushing an image, the client will ask you to
login. Provide the same credentials that you used for logging into Docker
Hub.
$ docker login
Login in with your Docker ID to push and pull images from Docker Hub. If you d
Username: yourusername
Password:
WARNING! Your password will be stored unencrypted in /Users/yourusername/.doc
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/credential-store
Login Succeeded
Once that is done, you can view your image on Docker Hub. For example,
here's the web page for my image.
Note: One thing that I'd like to clarify before we go ahead is that it is
not imperative to host your image on a public registry (or any registry)
https://docker-curriculum.com 19/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
in order to deploy to AWS. In case you're writing code for the next
≡ million-dollar unicorn startup you can totally skip this step. The
reason why we're pushing our images publicly is that it makes
deployment super simple by skipping a few intermediate
configuration steps.
Now that your image is online, anyone who has docker installed can play
with your app by typing just a single command.
If you've pulled your hair out in setting up local dev environments / sharing
application configuration in the past, you very well know how awesome this
sounds. That's why Docker is so cool!
Beanstalk
https://docker-curriculum.com 20/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
https://docker-curriculum.com 21/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Now we need to upload our application code. But since our application
is packaged in a Docker container, we just need to tell EB about our
container. Open the Dockerrun.aws.json file located in the flask-
app folder and edit the Name of the image to your image's name. Don't
worry, I'll explain the contents of the file shortly. When you are done,
click on the radio button for "Upload your Code", choose this file, and
click on "Upload".
Now click on "Create environment". The final screen that you see will
have a few spinners indicating that your environment is being set up. It
typically takes around 5 minutes for the first-time setup.
https://docker-curriculum.com 22/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
While we wait, let's quickly see what the Dockerrun.aws.json file contains.
≡ This file is basically an AWS specific file that tells EB details about our
application and docker configuration.
{
"AWSEBDockerrunVersion": "1",
"Image": {
"Name": "prakhar1989/catnip",
"Update": "true"
},
"Ports": [
{
"ContainerPort": 5000,
"HostPort": 8000
}
],
"Logging": "/var/log/nginx"
}
The file should be pretty self-explanatory, but you can always reference the
official documentation for more information. We provide the name of the
image that EB should use along with a port that the container should open.
Hopefully by now, our instance should be ready. Head over to the EB page
and you should see a green tick indicating that your app is alive and
kicking.
https://docker-curriculum.com 23/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Go ahead and open the URL in your browser and you should see the
application in all its glory. Feel free to email / IM / snapchat this link to your
friends and family so that they can enjoy a few cat gifs, too.
Cleanup
Once you done basking in the glory of your app, remember to terminate
the environment so that you don't end up getting charged for extra
resources.
https://docker-curriculum.com 24/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
In the next (and final) part of the tutorial, we'll up the ante a bit and deploy
an application that mimics the real-world more closely; an app with a
persistent back-end storage tier. Let's get straight to it!
MULTI-CONTAINER ENVIRONMENTS
In the last section, we saw how easy and fun it is to run applications with
Docker. We started with a simple static website and then tried a Flask app.
Both of which we could run locally and in the cloud with just a few
commands. One thing both these apps had in common was that they were
running in a single container.
https://docker-curriculum.com 25/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
In particular, we are going to see how we can run and manage multi-
container docker environments. Why multi-container you might ask? Well,
one of the key points of Docker is the way it provides isolation. The idea of
bundling a process with its dependencies in a sandbox (called containers)
is what makes this so powerful.
Just like it's a good strategy to decouple your application tiers, it is wise to
keep containers for each of the services separate. Each tier is likely to have
different resource needs and those needs might grow at different rates. By
separating the tiers into different containers, we can compose each tier
using the most appropriate instance type based on different resource
needs. This also plays in very well with the whole microservices movement
which is one of the main reasons why Docker (or any other container
technology) is at the forefront of modern microservices architectures.
SF Food Trucks
The app that we're going to Dockerize is called SF Food Trucks. My goal in
building this app was to have something that is useful (in that it resembles
a real-world application), relies on at least one service, but is not too
complex for the purpose of this tutorial. This is what I came up with.
SF Food Trucks
The app's backend is written in Python (Flask) and for search it uses
Elasticsearch. Like everything else in this tutorial, the entire source is
https://docker-curriculum.com 26/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
available on Github. We'll use this as our candidate application for learning
≡ out how to build, run and deploy a multi-container environment.
The flask-app folder contains the Python application, while the utils
folder has some utilities to load the data into Elasticsearch. The directory
also contains some YAML files and a Dockerfile, all of which we'll see in
greater detail as we progress through this tutorial. If you are curious, feel
free to take a look at the files.
Now that you're excited (hopefully), let's think of how we can Dockerize the
app. We can see that the application consists of a Flask backend server and
an Elasticsearch service. A natural way to split this app would be to have
two containers - one running the Flask process and another running the
Elasticsearch (ES) process. That way if our app becomes popular, we can
scale it by adding more containers depending on where the bottleneck lies.
https://docker-curriculum.com 27/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
https://docker-curriculum.com 28/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ Note: If your container runs into memory issues, you might need to
tweak some JVM flags to limit its memory consumption.
$ docker container ls
CONTAINER ID IMAGE COMM
277451c15ec1 docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/u
https://docker-curriculum.com 29/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Now, lets try to see if can send a request to the Elasticsearch container. We
≡ use the 9200 port to send a cURL request to the container.
$ curl 0.0.0.0:9200
{
"name" : "ijJDAOm",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "a_nSV3XmTCqpzYYzb-LhNw",
"version" : {
"number" : "6.3.2",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
Sweet! It's looking good! While we are at it, let's get our Flask container
running too. But before we get to that, we need a Dockerfile . In the last
section, we used python:3.8 image as our base image. This time, however,
apart from installing Python dependencies via pip , we want our
application to also generate our minified Javascript file for production. For
this, we'll require Nodejs. Since we need a custom build step, we'll start
from the ubuntu base image to build our Dockerfile from scratch.
Note: if you find that an existing image doesn't cater to your needs,
feel free to start from another base image and tweak it yourself. For
most of the images on Docker Hub, you should be able to find the
corresponding Dockerfile on Github. Reading through existing
Dockerfiles is one of the best ways to learn how to roll your own.
https://docker-curriculum.com 30/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
# expose port
EXPOSE 5000
# start app
CMD [ "python3", "./app.py" ]
Quite a few new things here so let's quickly go over this file. We start off
with the Ubuntu LTS base image and use the package manager apt-get to
install the dependencies namely - Python and Node. The yqq flag is used
to suppress output and assumes "Yes" to all prompts.
We then use the ADD command to copy our application into a new volume
in the container - /opt/flask-app . This is where our code will reside. We
also set this as our working directory, so that the following commands will
be run in the context of this location. Now that our system-wide
dependencies are installed, we get around to installing app-specific ones.
First off we tackle Node by installing the packages from npm and running
the build command as defined in our package.json file. We finish the file
off by installing the Python packages, exposing the port and defining the
CMD to run as we did in the last section.
https://docker-curriculum.com 31/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Finally, we can go ahead, build the image and run the container (replace
≡ yourusername with your username below).
In the first run, this will take some time as the Docker client will download
the ubuntu image, run all the commands and prepare your image. Re-
running docker build after any subsequent changes you make to the
application code will almost be instantaneous. Now let's try running our
app.
Oops! Our flask app was unable to run since it was unable to connect to
Elasticsearch. How do we tell one container about the other container and
get them to talk to each other? The answer lies in the next section.
Docker Network
Before we talk about the features Docker provides especially to deal with
such scenarios, let's see if we can figure out a way to get around the
problem. Hopefully, this should give you an appreciation for the specific
feature that we are going to study.
$ docker container ls
CONTAINER ID IMAGE COMM
277451c15ec1 docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/u
https://docker-curriculum.com 32/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
es = Elasticsearch(host='es')
To make this work, we need to tell the Flask container that the ES container
is running on 0.0.0.0 host (the port by default is 9200 ) and that should
make it work, right? Unfortunately, that is not correct since the IP 0.0.0.0
is the IP to access ES container from the host machine i.e. from my Mac.
Another container will not be able to access this on the same IP address.
Okay if not that IP, then which IP address should the ES container be
accessible by? I'm glad you asked this question.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c2c695315b3a bridge bridge local
a875bec5d6fd host host local
ead0e804a67b none null local
The bridge network is the network in which containers are run by default.
So that means that when I ran the ES container, it was running in this
bridge network. To validate this, let's inspect the network.
https://docker-curriculum.com 33/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
"Options": null,
≡ "Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943
"Name": "es",
"EndpointID": "5c417a2fc6b13d8ec97b76bbd54aaf3ee2d48f328c3f727
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
You can see that our container 277451c15ec1 is listed under the
Containers section in the output. What we also see is the IP address this
container has been allotted - 172.17.0.2 . Is this the IP address that we're
looking for? Let's find out by running our flask container and trying to
access this IP.
https://docker-curriculum.com 34/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Although we have figured out a way to make the containers talk to each
other, there are still two problems with this approach -
The good news that Docker has a great answer to our questions. It allows
us to define our own networks while keeping them isolated using the
docker network command.
https://docker-curriculum.com 35/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ $ docker network ls
NETWORK ID NAME DRIVER SCOPE
c2c695315b3a bridge bridge local
0815b2a3bb7a foodtrucks-net bridge local
a875bec5d6fd host host local
ead0e804a67b none null local
Now that we have a network, we can launch our containers inside this
network using the --net flag. Let's do that - but first, in order to launch a
new container with the same name, we will stop and remove our ES
container that is running in the bridge (default) network.
$ docker container rm es
es
https://docker-curriculum.com 36/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
"IPAM": {
≡ "Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673
"Name": "es",
"EndpointID": "29ba2d33f9713e57eb6b38db41d656e4ee2c53e4a2f7cf6
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
As you can see, our es container is now running inside the foodtrucks-
net bridge network. Now let's inspect what happens when we launch in
our foodtrucks-net network.
"build_type" : "tar",
≡ "build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py node_modules package.json requirements.txt static templates webpa
root@53af252b771a:/opt/flask-app# python3 app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded: 733
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
$ docker container ls
CONTAINER ID IMAGE COMM
852fc74de295 yourusername/foodtrucks-web "pyt
13d6415f73c8 docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/u
$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT
Head over to http://0.0.0.0:5000 and see your glorious app live! Although
that might have seemed like a lot of work, we actually just typed 4
https://docker-curriculum.com 38/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
#!/bin/bash
And that's it! If you ask me, I find this to be an extremely awesome, and a
powerful way of sharing and running your applications!
Docker Compose
Till now we've spent all our time exploring the Docker client. In the Docker
ecosystem, however, there are a bunch of other open-source tools which
play very nicely with Docker. A few of them are -
https://docker-curriculum.com 39/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
In this section, we are going to look at one of these tools, Docker Compose,
and see how it can make dealing with multi-container apps easier.
The first comment on the forum actually does a good job of explaining
what Fig is all about.
https://docker-curriculum.com 40/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
It turns out that a lot of people using docker agree with this sentiment.
≡ Slowly and steadily as Fig became popular, Docker Inc. took notice,
acquired the company and re-branded Fig as Docker Compose.
So what is Compose used for? Compose is a tool that is used for defining
and running multi-container Docker apps in an easy way. It provides a
configuration file called docker-compose.yml that can be used to bring up
an application and the suite of services it depends on with just one
command. Compose works in all environments: production, staging,
development, testing, as well as CI workflows, although Compose is ideal
for development and testing environments.
$ docker-compose --version
docker-compose version 1.21.2, build a133471
Now that we have it installed, we can jump on the next step i.e. the Docker
Compose file docker-compose.yml . The syntax for YAML is quite simple
and the repo already contains the docker-compose file that we'll be using.
version: "3"
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: es
environment:
- discovery.type=single-node
ports:
- 9200:9200
https://docker-curriculum.com 41/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
volumes:
≡ web:
- esdata1:/usr/share/elasticsearch/data
image: yourusername/foodtrucks-web
command: python3 app.py
depends_on:
- es
ports:
- 5000:5000
volumes:
- ./flask-app:/opt/flask-app
volumes:
esdata1:
driver: local
Let me breakdown what the file above means. At the parent level, we
define the names of our services - es and web . The image parameter is
always required, and for each service that we want Docker to run, we can
add additional parameters. For es , we just refer to the elasticsearch
image available on Elastic registry. For our Flask app, we refer to the image
that we built at the beginning of this section.
Great! Now the file is ready, let's see docker-compose in action. But before
we start, we need to make sure the ports and names are free. So if you
https://docker-curriculum.com 42/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
have the Flask and ES containers running, lets turn them off.
≡
$ docker stop es foodtrucks-web
es
foodtrucks-web
$ docker rm es foodtrucks-web
es
foodtrucks-web
$ docker-compose up
Creating network "foodtrucks_default" with the default driver
Creating foodtrucks_es_1
Creating foodtrucks_web_1
Attaching to foodtrucks_es_1, foodtrucks_web_1
es_1 | [2016-01-11 03:43:50,300][INFO ][node ] [Comet] ve
es_1 | [2016-01-11 03:43:50,307][INFO ][node ] [Comet] i
es_1 | [2016-01-11 03:43:50,366][INFO ][plugins ] [Comet] lo
es_1 | [2016-01-11 03:43:50,421][INFO ][env ] [Comet] u
es_1 | [2016-01-11 03:43:52,626][INFO ][node ] [Comet] i
es_1 | [2016-01-11 03:43:52,632][INFO ][node ] [Comet] st
es_1 | [2016-01-11 03:43:52,703][WARN ][common.network ] [Comet] p
es_1 | [2016-01-11 03:43:52,704][INFO ][transport ] [Comet] p
es_1 | [2016-01-11 03:43:52,721][INFO ][discovery ] [Comet] e
es_1 | [2016-01-11 03:43:55,785][INFO ][cluster.service ] [Comet] ne
es_1 | [2016-01-11 03:43:55,818][WARN ][common.network ] [Comet] p
es_1 | [2016-01-11 03:43:55,819][INFO ][http ] [Comet] p
es_1 | [2016-01-11 03:43:55,819][INFO ][node ] [Comet] st
es_1 | [2016-01-11 03:43:55,826][INFO ][gateway ] [Comet] re
es_1 | [2016-01-11 03:44:01,825][INFO ][cluster.metadata ] [Comet] [
es_1 | [2016-01-11 03:44:02,373][INFO ][cluster.metadata ] [Comet] [
es_1 | [2016-01-11 03:44:02,510][INFO ][cluster.metadata ] [Comet] [
es_1 | [2016-01-11 03:44:02,593][INFO ][cluster.metadata ] [Comet] [
es_1 | [2016-01-11 03:44:02,708][INFO ][cluster.metadata ] [Comet] [
es_1 | [2016-01-11 03:44:03,047][INFO ][cluster.metadata ] [Comet] [
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Head over to the IP to see your app live. That was amazing wasn't it? Just a
few lines of configuration and we have two Docker containers running
https://docker-curriculum.com 43/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
successfully in unison. Let's stop the services and re-run in detached mode.
≡
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Killing foodtrucks_web_1 ... done
Killing foodtrucks_es_1 ... done
$ docker-compose up -d
Creating es ... done
Creating foodtrucks_web_1 ... done
$ docker-compose ps
Name Command State Port
-----------------------------------------------------------------------------
es /usr/local/bin/docker-entr ... Up 0.0.0.0:9200->9200
foodtrucks_web_1 python3 app.py Up 0.0.0.0:5000->5000
First off, let us stop the services from running. We can always bring them
back up in just one command. Data volumes will persist, so it’s possible to
start the cluster again with the same data using docker-compose up. To
destroy the cluster and the data volumes, just type docker-compose down -
v.
$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es ... done
Removing foodtrucks_web_1 ... done
Removing es ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1
While we're are at it, we'll also remove the foodtrucks network that we
created last time.
≡ c2c695315b3a
a875bec5d6fd
bridge
host
bridge
host
local
local
ead0e804a67b none null local
Great! Now that we have a clean slate, let's re-run our services and see if
Compose does its magic.
$ docker-compose up -d
Recreating foodtrucks_es_1
Recreating foodtrucks_web_1
$ docker container ls
CONTAINER ID IMAGE COMMAND CREA
f50bb33a3242 yourusername/foodtrucks-web "python3 app.py" 14
e299ceeb4caa elasticsearch "/docker-entrypoint.s" 14
$ docker network ls
NETWORK ID NAME DRIVER
c2c695315b3a bridge bridge local
f3b80f381ed3 foodtrucks_default bridge local
a875bec5d6fd host host local
ead0e804a67b none null local
You can see that compose went ahead and created a new network called
foodtrucks_default and attached both the new services in that network
so that each of these are discoverable to the other. Each container for a
service joins the default network and is both reachable by other containers
on that network, and discoverable by them at a hostname identical to the
container name.
$ docker ps
CONTAINER ID IMAGE COMM
8c6bb7e818ec docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/u
7640cec7feb7 yourusername/foodtrucks-web "pyt
https://docker-curriculum.com 45/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ "Name": "foodtrucks_default",
"Id": "f3b80f381ed3e03b3d5e605e42c4a576e32d38ba24399e963d7dad848b3b4fe
"Created": "2018-07-30T03:36:06.0384826Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"7640cec7feb7f5615eaac376271a93fb8bab2ce54c7257256bf16716e05c65a5
"Name": "foodtrucks_web_1",
"EndpointID": "b1aa3e735402abafea3edfbba605eb4617f81d94f1b5f8f
"MacAddress": "02:42:ac:13:00:02",
"IPv4Address": "172.19.0.2/16",
"IPv6Address": ""
},
"8c6bb7e818ec1f88c37f375c18f00beb030b31f4b10aee5a0952aad753314b57
"Name": "es",
"EndpointID": "649b3567d38e5e6f03fa6c004a4302508c14a5f2ac086ee
"MacAddress": "02:42:ac:13:00:03",
"IPv4Address": "172.19.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "default",
"com.docker.compose.project": "foodtrucks",
"com.docker.compose.version": "1.21.2"
}
https://docker-curriculum.com 46/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ ]
Development Workflow
Before we jump to the next section, there's one last thing I wanted to cover
about docker-compose. As stated earlier, docker-compose is really great
for development and testing. So let's see how we can configure compose
to make our lives easier during development.
Let's see how we can make a change in the Foodtrucks app we just ran.
Make sure you have the app running,
$ docker container ls
CONTAINER ID IMAGE COMM
5450ebedd03c yourusername/foodtrucks-web "pyt
05d408b25dfe docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/u
Now let's see if we can change this app to display a Hello world!
message when a request is made to /hello route. Currently, the app
responds with a 404.
$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
https://docker-curriculum.com 47/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Why does this happen? Since ours is a Flask app, we can see app.py (link)
for answers. In Flask, routes are defined with @app.route syntax. In the file,
you'll see that we only have three routes defined - / , /debug and /search .
The / route renders the main app, the debug route is used to return some
debug information and finally search is used by the app to query
elasticsearch.
$ curl 0.0.0.0:5000/debug
{
"msg": "yellow open sfdata Ibkx7WYjSt-g8NZXOEtTMg 5 1 618 0 1.3mb 1.3mb\n",
"status": "success"
}
Given that context, how would we add a new route for hello ? You guessed
it! Let's open flask-app/app.py in our favorite editor and make the
following change
@app.route('/')
def index():
return render_template("index.html")
$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 30 Jul 2018 15:34:38 GMT
https://docker-curriculum.com 48/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Oh no! That didn't work! What did we do wrong? While we did make the
≡ change in app.py , the file resides in our machine (or the host machine), but
since Docker is running our containers based off the
yourusername/foodtrucks-web image, it doesn't know about this change.
To validate this, lets try the following -
What we're trying to do here is to validate that our changes are not in the
app.py that's running in the container. We do this by running the
command docker-compose run , which is similar to its cousin docker run
but takes additional arguments for the service (which is web in our case).
As soon as we run bash , the shell opens in /opt/flask-app as specified in
our Dockerfile. From the grep command we can see that our changes are
not in the file.
Lets see how we can fix it. First off, we need to tell docker compose to not
use the image and instead use the files locally. We'll also set debug mode
to true so that Flask knows to reload the server when app.py changes.
Replace the web portion of the docker-compose.yml file like so:
version: "3"
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: es
environment:
- discovery.type=single-node
ports:
- 9200:9200
volumes:
- esdata1:/usr/share/elasticsearch/data
web:
build: . # replaced image with build
https://docker-curriculum.com 49/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ environment:
- DEBUG=True # set an env var for flask
depends_on:
- es
ports:
- "5000:5000"
volumes:
- ./flask-app:/opt/flask-app
volumes:
esdata1:
driver: local
With that change (diff), let's stop and start the containers.
$ docker-compose down -v
Stopping foodtrucks_web_1 ... done
Stopping es ... done
Removing foodtrucks_web_1 ... done
Removing es ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1
$ docker-compose up -d
Creating network "foodtrucks_default" with the default driver
Creating volume "foodtrucks_esdata1" with local driver
Creating es ... done
Creating foodtrucks_web_1 ... done
As a final step, lets make the change in app.py by adding a new route.
Now we try to curl
$ curl 0.0.0.0:5000/hello
hello world
That concludes our tour of Docker Compose. With Docker Compose, you
can also pause your services, run a one-off command on a container and
even scale the number of containers. I also recommend you checkout a few
other use-cases of Docker compose. Hopefully, I was able to show you how
https://docker-curriculum.com 50/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
If you've read this far you are pretty much convinced that Docker is a pretty
cool technology. And you are not alone. Seeing the meteoric rise of Docker,
almost all Cloud vendors started working on adding support for deploying
Docker apps on their platform. As of today, you can deploy containers on
Google Cloud Platform, AWS, Azure and many others. We already got a
primer on deploying single container apps with Elastic Beanstalk and in
this section we are going to look at Elastic Container Service (or ECS) by
AWS.
Luckily for us, ECS has a friendly CLI tool that understands Docker
Compose files and automatically provisions the cluster on ECS! Since we
already have a functioning docker-compose.yml it should not take a lot of
effort in getting up and running on AWS. So let's get started!
https://docker-curriculum.com 51/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
The first step is to install the CLI. Instructions to install the CLI on both Mac
≡ and Linux are explained very clearly in the official docs. Go ahead, install
the CLI and when you are done, verify the install by running
$ ecs-cli --version
ecs-cli version 1.18.1 (7e9df84)
Next, we'll be working on configuring the CLI so that we can talk to ECS.
We'll be following the steps as detailed in the official guide on AWS ECS
docs. In case of any confusion, please feel free to refer to that guide.
The first step will involve creating a profile that we'll use for the rest of the
tutorial. To continue, you'll need your AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY . To obtain these, follow the steps as detailed
under the section titled Access Key and Secret Access Key on this page.
Next, we need to get a keypair which we'll be using to log into the
instances. Head over to your EC2 Console and create a new keypair.
Download the keypair and store it in a safe location. Another thing to note
before you move away from this screen is the region name. In my case, I
have named my key - ecs and set my region as us-east-1 . This is what I'll
assume for the rest of this walkthrough.
https://docker-curriculum.com 52/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
We provide the configure command with the region name we want our
cluster to reside in and a cluster name. Make sure you provide the same
region name that you used when creating the keypair. If you've not
configured the AWS CLI on your computer before, you can use the official
guide, which explains everything in great detail on how to get everything
going.
The last and final step is where we'll use our docker-compose.yml file.
We'll need to make a few minor changes, so instead of modifying the
original, let's make a copy of it. The contents of this file (after making the
changes) look like (below) -
version: '2'
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
cpu_shares: 100
mem_limit: 3621440000
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
logging:
driver: awslogs
options:
awslogs-group: foodtrucks
awslogs-region: us-east-1
awslogs-stream-prefix: es
web:
image: yourusername/foodtrucks-web
cpu_shares: 100
mem_limit: 262144000
ports:
- "80:5000"
https://docker-curriculum.com 54/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
links:
≡ - es
logging:
driver: awslogs
options:
awslogs-group: foodtrucks
awslogs-region: us-east-1
awslogs-stream-prefix: web
Great! Now let's run the final command that will deploy our app on ECS!
$ cd aws-ecs
$ ecs-cli compose up
INFO[0000] Using ECS task definition TaskDefinition=ecscom
INFO[0000] Starting container... container=845e2368-17
INFO[0000] Starting container... container=845e2368-17
INFO[0000] Describe ECS container status container=845e2368-17
INFO[0000] Describe ECS container status container=845e2368-17
INFO[0036] Describe ECS container status container=845e2368-17
INFO[0048] Describe ECS container status container=845e2368-17
INFO[0048] Describe ECS container status container=845e2368-17
INFO[0060] Started container... container=845e2368-17
INFO[0060] Started container... container=845e2368-17
It's not a coincidence that the invocation above looks similar to the one we
used with Docker Compose. If everything went well, you should see a
desiredStatus=RUNNING lastStatus=RUNNING as the last line.
https://docker-curriculum.com 55/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
Go ahead and open http://54.86.14.14 in your browser and you should see
the Food Trucks in all its black-yellow glory! Since we're on the topic, let's
see how our AWS ECS console looks.
We can see above that our ECS cluster called 'foodtrucks' was created and
is now running 1 task with 2 container instances. Spend some time
browsing this console to get a hang of all the options that are here.
https://docker-curriculum.com 56/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ Cleanup
Once you've played around with the deployed app, remember to turn down
the cluster -
So there you have it. With just a few commands we were able to deploy our
awesome app on the AWS cloud!
CONCLUSION
And that's a wrap! After a long, exhaustive but fun tutorial you are now
ready to take the container world by storm! If you followed along till the
very end then you should definitely be proud of yourself. You learned how
to setup Docker, run your own containers, play with static and dynamic
websites and most importantly got hands on experience with deploying
your applications to the cloud!
I hope that finishing this tutorial makes you more confident in your abilities
to deal with servers. When you have an idea of building your next app, you
can be sure that you'll be able to get it in front of people with minimal
effort.
https://docker-curriculum.com 57/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
≡ Next Steps
Your journey into the container world has just started! My goal with this
tutorial was to whet your appetite and show you the power of Docker. In
the sea of new technology, it can be hard to navigate the waters alone and
tutorials such as this one can provide a helping hand. This is the Docker
tutorial I wish I had when I was starting out. Hopefully, it served its purpose
of getting you excited about containers so that you no longer have to watch
the action from the sides.
Below are a few additional resources that will be beneficial. For your next
project, I strongly encourage you to use Docker. Keep in mind - practice
makes perfect!
Additional Resources
Awesome Docker
Why Docker
Docker Weekly and archives
Codeship Blog
Give Feedback
Now that the tutorial is over, it's my turn to ask questions. How did you like
the tutorial? Did you find the tutorial to be a complete mess or did you have
fun and learn something?
I would totally love to hear about your experience with this tutorial. Give
suggestions on how to make this better or let me know about my mistakes.
https://docker-curriculum.com 58/59
7/28/24, 6:47 PM A Docker Tutorial for Beginners
I want this tutorial to be one of the best introductory tutorials on the web
≡ and I can't do it without your help.
https://docker-curriculum.com 59/59