Activity 1: Building and running Linux containers
- What you’ll need
- Building a simple container
- Running a simple container
- Packaging a Python webserver application
- Setting entrypoints and exposing ports
What you’ll need
To build and run Linux containers, you’ll need one of the following system configurations:
A 64-bit version of one of Docker’s supported Linux distributions (CentOS 7+, Debian 7.7+, Fedora 26+, Ubuntu 14.04+) with Docker Community Edition (CE) installed and non-root access enabled so commands don’t need to be prefixed with sudo
A 64-bit version of Windows 10 Home (Version 2004 or newer) with the Windows Subsystem for Linux (WSL2) and Docker Desktop for Windows installed, and Docker Desktop configured to use the WSL2 backend for Linux containers
macOS 10.10.3 Yosemite or newer with Docker Desktop for Mac installed
Building a simple container
Create a file called
Dockerfile with the following contents:
# Use the filesystem layers and default configuration from the Ubuntu 18.04 base image FROM ubuntu:18.04 # Create a new filesystem layer with an empty directory RUN mkdir /mydir
Run the docker build command to build a container image from the Dockerfile:
# Build an image using the Dockerfile in the current directory and tag it as "test:latest" docker build -t "test" .
ubuntu:18.04 base image is not already present on your system, you will see Docker downloading the filesystem layers for that image, followed by the execution of the
Once the image has been built, you can inspect its filesystem layers using the docker history command:
docker history "test"
The layers will be listed in reverse-chronological order, with your directory creation layer at the top of the list and the filesystem layers from the
ubuntu:18.04 base image below it. Each layer lists its size, creation time, and the details of the directive that created it. This varies based on the type of directive:
RUNdirectives, the command that was executed is displayed.
For other directives that do not have a command associated with them (such as
CMD), the text
#(nop)is listed, followed by the raw directive itself.
Running a simple container
Now that you have a container image built, you can start a container using the docker run command:
# Start a container with an interactive bash shell docker run -ti "test" bash
This will start a container using the container image built in the previous section and run the
bash shell inside it. The
-ti flag instructs Docker to run the container in foreground mode with interactive input, which allows you to run commands in the bash shell just as you would if it were running outside a container. (The other supported mode is detached mode, specified by the
-d flag, whereby the container runs in the background with no user input.)
Once the container starts you will see that you are running
bash as the root user, with the current working directory set to the root of the container’s filesystem.
If you run the
ps command to list running processes, you will see only the processes running inside the container:
PID TTY TIME CMD 1 pts/0 00:00:00 bash 10 pts/0 00:00:00 ps
If you run the
ls command then you will see the list of directories under the root of the container’s filesystem, including the
mydir directory that was created in the Dockerfile’s
exit command to terminate the interactive shell and stop the container. By default, Docker will not delete stopped containers, instead simply leaving them inactive so that they can be started again if desired. You can see the list of all containers (including the stopped container) using the docker ps command:
# List all containers, including stopped containers docker ps --all
You should see the stopped container listed with its exit code, exit time, and the command that was originally used to start the container.
If you want Docker to automatically delete containers when they stop then you can use the
--rm flag when invoking the
docker run command:
# Automatically delete the container when it exits docker run --rm -ti "test" bash
If you run
docker ps in a separate command prompt or terminal window you will see the container listed with a state of “Up N seconds” (where N is the time since the container started), which indicates that it is currently running. Once you have stopped the container, you will see that it has been removed and is no longer listed in the output of
Packaging a Python webserver application
Create a file called
app.py in the same directory as the Dockerfile from the previous sections with the following contents:
#!/usr/bin/env python3 from flask import Flask # Create our Flask application app = Flask(__name__) # Wire up our endpoint @app.route('/') def endpoint(): return 'Hello World!\n' # Start our Flask webserver if __name__ == '__main__': app.run(host='0.0.0.0', port=80, debug=True)
Update the Dockerfile so that it contains the following directives:
# Use the filesystem layers and default configuration from the Ubuntu 18.04 base image FROM ubuntu:18.04 # Create a new filesystem layer with an empty directory RUN mkdir /mydir # Install Python and the Flask package (plus curl for testing purposes) RUN apt-get update && \ apt-get install -y --no-install-recommends curl python3 python3-pip && \ pip3 install flask # Copy our Python webserver app into the image COPY app.py /app/app.py
Once the files have been modified, rebuild the container image to use them:
docker build -t "test" .
There are a couple of things to note during the build process:
Docker’s build cache avoids the need to re-run directives that have not changed since the last time they were built. The
RUNdirective that creates our empty directory will use the cache instead of being run again:
Step 2/4 : RUN mkdir /mydir ---> Using cache
RUNdirectives are executed non-interactively, which means they cannot rely on user input to function. The
apt-get installcommand only works correctly because the
-yflag is passed to suppress interactive prompts. If you remove the
-yflag then the command will attempt to prompt the user for confirmation and fail because no user input is available.
Once the updated container image is built, start a new container with the Python application as the entrypoint process:
# Start a container with our Python webserver as the entrypoint # (Note that we explicitly set a name here so the instructions below can reference it) docker run --name="server" --rm -ti "test" python3 /app/app.py
This starts a container and immediately launches the Python webserver. To test that the server is working we can attach an additional shell to the container using the docker exec command and perform a test HTTP request with
# Attach an interactive bash shell to the running container # (Note that we are using the container name we set in the `docker run` command) docker exec -ti "server" bash # This should print "Hello World!" curl "http://127.0.0.1/"
Shift focus to the command prompt or terminal window running the Python webserver and terminate it by pressing Ctrl-C. You will note that this not only stops the webserver process, but also the additional
bash process that was attached using the
docker exec command. This is because Docker containers always stop when their entrypoint process terminates, even if there are additional processes running inside the container at the time. Any additional processes will be forcibly terminated when the container stops.
Setting entrypoints and exposing ports
In the previous section the Python webserver application was explicitly specified as the container entrypoint at the time when the container was started, but this information can be included directly in the metadata of the container image itself. Update the Dockerfile from the previous section so that it contains the following directives:
# Use the filesystem layers and default configuration from the Ubuntu 18.04 base image FROM ubuntu:18.04 # Create a new filesystem layer with an empty directory RUN mkdir /mydir # Install Python and the Flask package (plus curl for testing purposes) RUN apt-get update && \ apt-get install -y --no-install-recommends curl python3 python3-pip && \ pip3 install flask # Copy our Python webserver app into the image COPY app.py /app/app.py # Set our entrypoint command to the Python webserver app ENTRYPOINT ["python3", "/app/app.py"]
Rebuild the container image and then start the container again, this time omitting the entrypoint information from the
docker run command:
# Start a container using the entrypoint specified in the image metadata docker run --rm -ti "test"
You will see that the Python webserver application starts automatically, just as it did in the previous section when the entrypoint was specified manually. Press Ctrl-C to stop the container.
In order to access the server from outside of the container it is necessary to expose TCP port 80 (used for HTTP traffic) to the host system. There are two steps required for this to work:
Mark the port as exposed in the Dockerfile using the
Map the exposed port to a host port when starting the container (unless you want Docker to choose a random port for you automatically, but that’s typically inconvenient because you will then need to query Docker to determine which port number it selected)
First, update the Dockerfile so that it contains the following directives:
# Use the filesystem layers and default configuration from the Ubuntu 18.04 base image FROM ubuntu:18.04 # Create a new filesystem layer with an empty directory RUN mkdir /mydir # Install Python and the Flask package (plus curl for testing purposes) RUN apt-get update && \ apt-get install -y --no-install-recommends curl python3 python3-pip && \ pip3 install flask # Copy our Python webserver app into the image COPY app.py /app/app.py # Set our entrypoint command to the Python webserver app ENTRYPOINT ["python3", "/app/app.py"] # Export TCP port 80 EXPOSE 80
Now rebuild the container image and specify a port mapping when starting the container:
# Start a container with port 80 mapped to port 8080 on the host system docker run --rm -ti -p 8080:80 "test"
You should now be able to view the server output by opening a web browser on the host system and navigating to http://127.0.0.1:8080/. If everything is working correctly then you should see the text “Hello World!” displayed. When you are done, focus on the command prompt or terminal window running the container and press Ctrl-C to stop it.
To remove the container image and stopped containers from the previous sections, run the following commands:
# Untag the "test:latest" image docker rmi "test" # Clean up our stopped containers and orphaned filesystem layers docker system prune --force