Kubeflow for Poets · Part 4 of 8

Docker

27 March 2019Updated 14 October 20267 min read

This part explains containers and Dockerfiles. It builds, runs and publishes Docker images, serves a web page with nginx and runs a Jupyter TensorFlow image.

Docker is a platform for building and running applications in isolated environments known as containers. A container packages an application with the software tools and dependencies needed to develop and deploy it.

Traditionally, an application is designed and hosted on a single server. This setup causes several problems, including the familiar "it works on my machine but not on yours". Applications in this setup are also hard to scale and to migrate. The result is high cost and slow deployment.

Stack diagram of a single server. An application sits on its libraries and dependencies, which sit on an operating system running on a physical machine.
An application hosted directly on a single physical server.

Virtual Machines and Containers

A virtual machine (VM) emulates a physical machine. You can install and run an operating system on it. A piece of software called a hypervisor makes this possible. The hypervisor runs on the physical machine (the host) and creates and manages multiple guest machines.

Stack diagram of two virtual machines. Each holds an application, its libraries and dependencies, and a guest operating system. Both run on a hypervisor, a host operating system and a physical machine.
Each virtual machine runs a full guest operating system on a hypervisor.

A container also isolates an application with its own libraries and dependencies. Containers on the same machine share the kernel of the host operating system. They do not need a guest operating system. Docker is a tool for building and running containers.

Stack diagram of two containers. Each holds an application and its libraries and dependencies. Both run directly on one host operating system and a physical machine.
Containers share the kernel of the host operating system.

Working with Docker

Install Docker on your local machine to run containers. Docker Desktop runs on macOS, Windows and Linux. On a Linux server you can install Docker Engine instead. The Get Docker page has the download links.

Key concepts to note are:

  • Dockerfile: A text file with the instructions for building an image.
  • Image: A read-only template built from a Dockerfile. It holds the application and its dependencies.
  • Container: A running instance of an image.

The diagram below shows the process of building an image and running a container.

Diagram in three steps. A Dockerfile is built into a Docker image, and the image is run as a Docker container.
The steps that turn a Dockerfile into a running container.

Introducing Docker Hub

Docker Hub is a registry for storing and sharing container images. It hosts official images such as Alpine Linux and nginx. We use both of these images below.

Screenshot of the Docker Hub image catalog in 2019. Filters run down the left side and a list of images such as Oracle Database, Oracle Java 8 SE and Couchbase fills the right side.
Docker Hub in 2019. The current site has a different design.

Key Instructions in a Dockerfile

The following instructions are the ones you use most when writing a Dockerfile.

Instruction Description
FROM Sets the base image for the build.
LABEL Adds metadata to the image as key-value pairs.
RUN Runs a command during the build and saves the result as a new image layer.
COPY Copies files from the build context on the local machine into the image filesystem.
EXPOSE Documents the port that the application in the container listens on. Publish the port with -p when you run the container.
CMD Sets the default command that runs when the container starts. A command given to docker run replaces it.
ENTRYPOINT Sets the executable that runs when the container starts. With the exec form, arguments given to docker run are passed to it. The --entrypoint flag of docker run replaces it.
WORKDIR Sets the working directory for the instructions that follow and for the running container.
VOLUME Declares a path in the container as a mount point for data stored outside the container filesystem. You choose the host directory or volume when you run the container.
ARG Defines a build-time variable that you can set with docker build --build-arg. The variable is unavailable in the running container.
ENV Sets an environment variable that is available during the build and in the running container.

Build and Run a Simple Docker Container

In this example, a shell script named date-script.sh assigns the current date to a variable and prints it to the console. The Dockerfile copies the script from the local machine into the image and runs the script when the container starts. The files are in the docker-intro/hello-world folder of the source repository.

bash
# navigate to the folder with the files cd docker-intro/hello-world

Let's view the shell script.

bash
cat date-script.sh
bash
#!/bin/sh DATE="$(date)" echo "Today's date is $DATE"

Let's view the Dockerfile.

bash
cat Dockerfile
dockerfile
# base image for building the container FROM alpine # add maintainer label LABEL maintainer="dvdbisong@gmail.com" # copy script from local machine to container filesystem COPY date-script.sh /date-script.sh # execute script CMD ["sh", "/date-script.sh"]
  • The image builds on the official Alpine Linux image. Alpine is a small Linux distribution.
  • The CMD instruction runs the script when the container starts. It uses the JSON array syntax known as the exec form. Docker recommends the exec form for CMD and ENTRYPOINT.

Build the Image

bash
# build the image docker build -t ekababisong.org/first_image .

Docker prints its progress through each step of the Dockerfile. The image is tagged ekababisong.org/first_image when the build finishes.

Run the Container

bash
# list the images on the machine docker images

The list includes ekababisong.org/first_image.

bash
# run the docker container from the image docker run ekababisong.org/first_image

The container prints the date and time when it ran, for example:

text
Today's date is Sun Feb 24 04:45:08 UTC 2019

Important Docker Commands

Commands for Managing Images

Command Description
docker images List all images on the machine.
docker rmi [IMAGE_NAME] Remove the image with name [IMAGE_NAME] from the machine.
docker rmi $(docker images -q) Remove all images from the machine.

Commands for Managing Containers

Command Description
docker ps List running containers. Add -a to also list stopped containers.
docker stop [CONTAINER_ID] Gracefully stop the container with [CONTAINER_ID].
docker kill [CONTAINER_ID] Forcefully stop the container with [CONTAINER_ID].
docker rm [CONTAINER_ID] Remove the container with [CONTAINER_ID] from the machine.
docker rm $(docker ps -a -q) Remove all containers from the machine.

You can use a container name in place of [CONTAINER_ID] in these commands.

Running a Docker Container

Let's break down the following command for running a Docker container.

bash
docker run -d -it --rm --name [CONTAINER_NAME] -p 8081:80 [IMAGE_NAME]

The flags do the following:

  • -d: Run the container in detached mode. This mode runs the container in the background.
  • -it: Keep standard input open (-i) and attach a terminal session (-t).
  • --rm: Remove the container when it exits.
  • --name: Give the container a name.
  • -p: Publish a container port on the host in the form host:container.

Serve a Web Page on an nginx Web Server with Docker

The files for this example are in the docker-intro/nginx-server folder of the source repository.

The Dockerfile

dockerfile
# base image for building the container FROM nginx # add maintainer label LABEL maintainer="dvdbisong@gmail.com" # copy html file from local machine to container filesystem COPY html/index.html /usr/share/nginx/html # port that the web server listens on EXPOSE 80

The official nginx image serves static files from /usr/share/nginx/html.

Build the Image

bash
# navigate to the folder from the root of the repository cd docker-intro/nginx-server # build the image docker build -t ekababisong.org/nginx_server . # list images on the machine docker images

The list now includes ekababisong.org/nginx_server.

Run the Container

bash
# run the container docker run -d -it --name ebisong-nginx -p 8081:80 ekababisong.org/nginx_server # list containers docker ps

The docker ps output lists the ebisong-nginx container. Its ports column shows port 8081 on the host mapped to port 80 in the container.

View the Web Page on the Running Server

Open a web browser and go to http://localhost:8081.

Browser window at port 8081 showing a page titled My Webpage Running on a Docker Container followed by a paragraph of placeholder text.
The web page served by nginx from the container.

Clean Up

bash
# stop the container docker stop ebisong-nginx # remove the container docker rm ebisong-nginx

Push an Image to Docker Hub

Create a Docker Hub account and log in from the terminal.

bash
# log in to docker hub docker login

For Docker Hub, docker login uses a device code flow by default. You confirm a one-time code in your web browser to sign in. To sign in with a username and a personal access token instead, run docker login --username [USERNAME].

Tag the image with your Docker Hub username and push it. Replace ekababisong with your own username.

bash
# tag the image docker tag ekababisong.org/nginx_server ekababisong/ebisong-nginx-server:latest # push to docker hub docker push ekababisong/ebisong-nginx-server:latest

Docker uploads the image layers to the ekababisong/ebisong-nginx-server repository on Docker Hub.

Bind Mounts and Volumes

Data written inside a container is lost when you remove the container. To keep data or share files with a container, you mount storage from outside it. Two common options are bind mounts and volumes. A bind mount maps a file or directory on the host into the container. A volume is storage that Docker creates and manages. You attach both with the -v or --mount flag of docker run.

Mount a Local Folder into the Containerized nginx Web Server

Run this command from the docker-intro/nginx-server folder. It bind-mounts the local html folder over the folder that nginx serves.

bash
docker run -d -it --name ebisong-nginx -p 8081:80 -v "$(pwd)/html:/usr/share/nginx/html" ekababisong.org/nginx_server

Any change you make to index.html on your machine now shows in the browser when you reload the page.

Run a Jupyter TensorFlow Image

The Jupyter Docker Stacks project publishes ready-to-run Jupyter images. Since October 2023, the project pushes new images only to the Quay.io registry. The older copies on Docker Hub no longer receive updates.

Pull the Image

Note: This image is large and takes a while to download.

bash
# pull the image from quay.io docker pull quay.io/jupyter/tensorflow-notebook

Run the Container

This command starts an ephemeral container that runs a Jupyter Server with the JupyterLab interface on host port 8888. The container is ephemeral because the --rm flag tells Docker to delete it when the server exits. The server logs appear in the terminal. Open the link of the form http://127.0.0.1:8888/lab?token=<token> from the logs in a browser to load JupyterLab.

bash
# run the image docker run -it --rm -p 8888:8888 quay.io/jupyter/tensorflow-notebook

The image below shows a notebook running from an earlier version of this container. The current image opens in JupyterLab. Its interface looks different from the classic notebook in the screenshot.

Classic Jupyter Notebook at 127.0.0.1:8888 with cells that import TensorFlow, NumPy, pandas and Matplotlib.
A notebook served from the Jupyter TensorFlow container.

Press Ctrl+C twice in the terminal to stop the server and reclaim the terminal.

Ekaba Bisong is a Ph.D. researcher at the University of Victoria and the founder of SiliconBlast. More about me