TensorWorks Training Logo

CN001 - Containers and container orchestration

Activity 1: Building and running Linux containers

Contents

What you’ll need

To build and run Linux containers, you’ll need one of the following system configurations:

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" .

If the 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 RUN directive.

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:

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 RUN directive.

Use the 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 docker ps.

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:

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 curl:

# 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:

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.

Cleanup

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