Introduction to Docker Containers
1. Setup
Older version of Docker Desktop
If you had previously installed Docker Desktop on your system, you need to make sure that what you have installed is up to date. The latest Docker Desktop and the accompanying Docker Engine contain many useful tools to help administrating your images and containers.
This material is written on:
- Docker Dekstop version 4.35.1 (173168)
- Docker Engine: 27.3.1
- Docker Compose: v2.29.7-desktop.1
It is possible that by the time that you read this setup, the versions you have will be higher. That would be a good thing!
Links to download and install Docker Desktop
- Select the appropriate link for your computing platform:
- Successful installation and startup of Docker will show the following application
Docker Desktop Terminal
- The most recent version of Docker Desktop comes with a built-in Terminal.
- If you are running the latest Docker Desktop version (4.35.1), this is a default feature available on the lower right cornder of the GUI.
- For earlier versions, this could show up as a beta feature.
- The remainder of this workshop will use the Docker Desktop Terminal app for consistency purpose. All the CLI docker commands can be executed on the standard Linux-based terminal of Mac and Linux platforms.
Docker Hub
- Docker Hub is one of the public repository for Docker images (think GitHub for container images).
- You should register for Docker Hub account at https://hub.docker.com and use it to log into your Docker Desktop environment (similar to how you link your GitHub account to GitHub Desktop, if you use GitHub Desktop).
2. Introduction to Docker
Motivation
- Limit the chaos between computing platforms of
- Development and production
- Developers, Testers, and Users
- Dependencies of various software components across a full-stack infrastructure
- ...
- Inspiration
- Shipping containers
- Ship anything under any forms anywhere
Overview of Docker
- Docker is client-server application.
- Docker daemon (Engine): receives and processes incoming Docker API request and requires root privilege.
- Docker Hub registry: collection of public images (https://hub.docker.com/).
- Docker client : Talks to the Docker daemon via the docker API and the registry API.
Hands-on: Hello world
- Docker
containers
are instantiated from Dockerimages
. - You can check availability of local
images
andcontainers
.
- If you click on the
Images
andContainer
tabs, you can also confirm that there is no exissting container or image on your Docker envrironment.
- We want to issue the following to start a service that will echo
hello world
to the screen.- This requires a Linux container to run the
echo
command.
- This requires a Linux container to run the
- The outcomes of the above commands are shown in the screenshot below
- User want to run a Linux command using an alpine Linux container
- There is no locally available alpine image, therefore Docker goes ahead and download the image.
- Docker uses the image as a blueprint to launch a container, which
then executes the
echo hello, world
command. - The container is shutdown after the execution.
docker run alpine echo hello world
docker
: invoke the container engine.run
: subcommand to run a container.alpine
: name of the image based on which a container will be launched.echo hello, world
: the command to be executed in the container environment.
docker image ls
- Now an image shows up (it has just been downloaded)
- The Image tab should also show the alpine image
docker container ls
- There is no running container listed. Although, you can see from the GUI that there was a container there.
docker container ls --all
- This command shows all containers, including those that finished running.
Hands-on: Interactive container
- We can launch a container and get into the shell of the container.
- You are now in a new prompt: a shell inside the container
- notice the changes in the prompt comparing to the previous prompt where you type in the docker run ... command
- This is important when you are on the terminal to know whether you are inside the container's environment or the host machine's environment.
-it
: combination of-i
and-t
.-i
tells Docker to connect to the container’s stdin for interactive mode-t
tells Docker that we want a pseudo-terminal- Let's attempt to run
figlet
inside the container (#
prompt)
- There will be an error:
bash: figlet: command not found
- The current container does not have the
figlet
program yet. - Let's install
figlet
inside the container (#
prompt)
Exercise: exit container
- Type
exit
to shutdown the container and back to your normal terminal. - Repeat the process of launching an interactive container from start and try
running
figlet
again. - Is the
figlet
program still there?
Hands-on: Background container
- You should have already exited out of the container shell and back to the host machine environment.
- Run the following command
- Press
Ctrl-C
to stop after a few time stamps.
- Press
- Run the following command
- Notice in the screenshot that you are now returned to the host machine's terminal.
- A running container is shown in both the
docker container ls
command and in the GUI's container tab. - The hash string, underlined by the red line, is a unique identifier for containers.
- Use the first four characters of your container ID to view the
log of the running Docker container
- From the above screenshot, it is
cd16
- From the above screenshot, it is
- Use
--tail N
to only look at latestN
lines of the log.
- To kill a running container, you can use
docker kill
with the container ID or click on theStop
orTrash Can
icons on the container's line inside the GUI's Container tab.
3. Docker Images
Docker images
- Image = files + metadata
- The files form the root filesystem of the container
- The metadata describes things such as:
- The author of the image
- The command to execute in container when starting it
- Environment variables to be set
- ...
- Images are made of layers, conceptually stacked on top of each other.
- Each layer can add, change, and remove files and/or metadata.
- Images can share layers to optimize disk usage, transfer times, and memory use.
Example of a Java webapp
- CentOS base layer
- Packages and configuration files added by our local IT
- JRE
- Tomcat
- Our application’s dependencies
- Our application code and assets
- Our application configuration
Containers versus images
- An image is a read-only filesystem.
- A container is an encapsulated set of processes running in a read-write copy of that filesystem.
- To optimize container boot time, copy-on-write is used instead of regular copy.
docker run
starts a container from a given image.
- Object-oriented analogy
- Images are conceptually similar to classes
- Layers are conceptually similar to inheritance
- Containers are conceptually similar to instances
How do we change an image?
- It is read-only, we don’t.
- We create a new container from the image
- We make changes to the container.
- When we are satisfied with the changes, we transform them into a new layer.
- A new image is created by stacking the new layer on top of the old image.
Image namespaces
- Official images (ubuntu, busybox, …)
- Root namespace.
- Small, distro images to be used as bases for the building process.
- Ready-to-use components and services (redis, postgresl …)
- User (and organizations) images:
<registry_name>/<image_name>:[version]
- linhbngo/csc331:latest
- Self-hosted images
- Images hosted by third party registry
URL/<image_name>
Hands-on: listing local images and searching for new images
- At this point, you should have at least two images
downloaded to Docker's local repository:
alpine
andubuntu
.
- The command should show two outputs clearly.
- You should also see the images listed in the
Image
tab of Docker Desktop, on the top portion of the GUI.
- We can search for available images in the public Docker Hub
General steps to create an image
- Create a container using an appropriate base distro
- Inside the container, install and setup the necessary software
- Review the changes in the container
- Turn the container into a new image
- Tag the image
Hands-on: create a container with a base distro
- Launch an interactive bash shell on a running container
- Install
figlet
, test, then exit the container- Remember the hash id of your container
- From the screenshot above, next to the top container (the one that just got
started and stopped 56 seconds ago), you can see the ID
cb2149...
- It is possible to check for the differences between this container and
the base image with the ID and
docker diff
- A: A file or directory was added
- C: A file or directory was changed
- D: A file or directory was deleted
Hands-on: commit changes into a new image
- We are still using the container from the previous example.
- From the first screenshot in the previous example, next to
the top container (the one that just got started and stopped
56 seconds ago), you can see the ID
cb2149...
- From the first screenshot in the previous example, next to
the top container (the one that just got started and stopped
56 seconds ago), you can see the ID
- Run the following commands
- The
docker commit ...
command created a new image namedubuntu_figlet
that is associated with the DockerHub accountlinhbngo
(my DockerHub account). This combination (linhbngo/ubuntu_figlet
) is called a repository, and has the tag1.0
. - The
docker image ls
command shows this image and other images available locally for Docker.
-
In reviewing the previous screenshot, you can se that a new image has been created with a size of 144MB.
- The original Ubuntu image has a size of 101MB.
-
We can examine the layers of the newly committed image by running the command
docker history ...
as shown below.
- The previous screenshot also shows that the new layer making up the new image
has a size of 43.3MB. This, adding to the base ubuntu image, is what giving
the new image the size of 144MB (101 + 43.3).
- Docker will not duplicate the base layer, but will reuse this layer storage when additional new images are created from the same base.
- You can now push the new image up to Docker Hub:
- If you are not logging into Docker Hub from DockerDesktop, or
are running on an external terminal, you will need to run
docker login
first to sign in to your Docker Hub account.
- If you are not logging into Docker Hub from DockerDesktop, or
are running on an external terminal, you will need to run
Automatic image construction: Dockerfile
- A build recipe for a container image.
- Contains a series of instructions telling Docker/Podman how an image is to be constructed.
- The
docker build
command builds an image from a Dockerfile.
Hands on: writing the first Dockerfile
- The following commands are done in the terminal (Ubuntu WSL on Windows/Mac Terminal).
- Type the following contents into the nano editor.
- To save and quit nano, press
Ctrl-X
(orControl-X
for Mac) - Type Y when asked to
Save modified buffer
- Press
Enter
.
- To save and quit nano, press
- Content of the above Dockerfile
FROM
: the base image for the buildRUN
: represents one layer of execution.RUN
commands must be non-interactive.
- To build the image, you can run the following commands:
- Assumption: you are still inside
myimage
- This could be checked with
pwd
.
- Assumption: you are still inside
-t
indicates a tag namedfiglet
will be applied to the image..
indicates that theDockerfile
file is in the current directory.
- Running
docker image ls
and examining the GUI's Image tab, you will see that there are now two tags for the same repository name. If you rundocker push
and check your Docker Hub repo, you will see the second tag now stored in the same repository
Exercise: test new image
- Test run the new
ubuntu_figlet
image by launching an interactive container using this image, then immediately runfiglet hello world
.
4. Infrastructure as Code
Overview
- Containers can be customized to run as programs/services
- Barebone containers with just enough dependencies packages
- Customizable parameteres to feed to containers at start time
- Ephemeral/persistent storages
- Network infrastructures
CMD and ENTRYPOINT
- Both commands enable containers to run a default program or script.
CMD
executes a default program or script with a predefined set of parameters.- To be run if the container is invoked without any command.
ENTRYPOINT
defines a default program or script with a predefined set of parameters, but also allows users to append additional parameters todocker run
call.
Hands on: CMD
- Edit your Dockerfile so that it has the following content
- Rebuild the image with the tag
linhbngo/ubuntu_figlet:3.0
. - Run the following command
Exercise: storage consumption
- Run the following commands
- Did we use any additional storage for this new image?
- Hint: Try running
docker system df
- Hint: Try running
Solution
Hands on: ENTRYPOINT
- Edit
Dockerfile
as follows:
- Rebuild the image with the tag
linhbngo/ubuntu_figlet:4.0
. - Run the followings:
docker build -t linhbngo/ubuntu_figlet:4.0 .
docker run linhbngo/ubuntu_figlet:4.0
docker run linhbngo/ubuntu_figlet:4.0 golden rams
- Notice that the first
docker run
, without any input parameters, does not generate any text. - The second
docker run
takesgolden rams
and feeds it to the figlet command specified byENTRYPOINT
.
Hands on: Using both ENTRYPOINT and CMD
ENTRYPOINT
andCMD
can be used together.- The command line arguments are appended to those parameters.
- Edit
Dockerfile
as follows:
- Rebuild the image with the tag
linhbngo/ubuntu_figlet:5.0
. - Run the followings:
docker build -t linhbngo/ubuntu_figlet:5.0 .
docker run linhbngo/ubuntu_figlet:5.0
docker run linhbngo/ubuntu_figlet:5.0 golden rams
- Caveat with
ENTRYPOINT
:/bin/bash
does not work as expected.- Need to override with
--entrypoint
flag.
- Need to override with
5. Infrastructure as Code: Storage
Overview
- Docker images are immutable (read-only)
- Docker containers are mutable, but are available only for the running duration
of the containers.
- Once containers are shutdown, to retain modifications, they need to be made immutable.
- How do we reconcile new data into Docker images/containers?
- Copy data into images during the building process
- Mount storage directory from host machines into containers at run time
COPY
COPY src dst
- Copy contents into the image prior to the installation steps.
- Need to be done prior to
RUN
statements.
Hands on: Importing and building external code using COPY
- Inside the
myimage
directory - Create the following file called
hello.c
:
- Create the following Dockerfile called
Dockerfile.hello
:
- You can build an image with a specific Dockerfile
Hands on: Mounting external storage to running containers
- Create a directory called
src
insidemyimage
. - Copy
hello.c
into this directory.
- Create the following Dockerfile called
Dockerfile.gcc
:
docker build -t linhbngo/gcc:1.0 -f Dockerfile.gcc .
docker run -it -v ./src:/ext_src linhbngo/gcc:1.0
- From the screenshot below, notice that after exiting out of the container, the newly
created binary file
hello
still persists.
6. Infrastucture as Code: Networking
Networking for container
- How can services provided by a container become available to the world?
- Piggyback on the host machine's network.
- Docker has supporting drivers for containers:
bridge
: The default network driver.host
: Remove network isolation between the container and the Docker host.none
: Completely isolate a container from the host and other containers.overlay
: Overlay networks connect multiple Docker daemons together.ipvlan
: IPvlan networks provide full control over both IPv4 and IPv6 addressing.macvlan
: Assign a MAC address to a container.
- Why do we have to map ports?
- By default, containers are not exposed to the world.
- Internally to host, containers have their own private addresses
- Services have to be exposed port by port.
- These have to be mapped to avoid conflicts.
Hands on: A simple web server
- Run the following commands inside the Docker terminal
-P
: make this service reachable from other computers (--publish-all
)-d
: run in background- Where is the port?
- Run the following command
- The outconme of the above command is
{"80/tcp":{}}
. - In other words, the container exposes its own internal port,
80
. In turn, this port is mapped to the external port55000
, as shown in the screenshot. - You can view the nginx webserver by visiting
127.0.0.1:55000
Hands on: manual allocation of port numbers
- In the previous example, the port randomly assigned by the host machine was
55000
. - This could be specified via the
-p
flag as follows:
- Convention:
port-on-host:port-on-container
- Check out the web servers at all of these ports