Skip to content

Docker introduction

Published:

A quick and painless introduction to Docker and the features it provides and why you should probably use it, with some examples. In other words, why you should use it.

Why do we need virtualization?

Applications in general need to interact with the operating system, the hardware, and other applications.

If even an element of the system changes, the application may not work as expected.

Virtual machines

Virtual Machines (VMs) are a great way to improve reproducibility.

They use a very complex software, the hypervisor, to emulate the hardware and the operating system. They are (mostly) completely independent from the host system they are running on.

Containers

Containers are a lightweight alternative to VMs.

They use some features of the Linux kernel, namely namespaces and cgroups, to completely isolate a process from the rest of the system. They can’t emulate different kernel or hardware.

Docker

Docker is the most popular containerization software.
Some alternatives are Podman or LXC.

It also provides a registry where you can find pre-built images.

Installation

Since Docker is specifically built with the Linux kernel in mind, it is not natively supported on Windows and macOS. It needs to spin up a virtual machine to run Linux.

Docker Desktop handles everything for you.

Images

An image is a read-only template with instructions for creating a Docker container.

They are made up of layers that are stacked on top of each other, like git commits.

Only the top layer is writable.

Dockerfile

Dockerfiles are the instructions to build an image.

FROM python:3.9.7-slim-buster # Base image

WORKDIR /app # Working directory (creates it if it does not exist)

COPY requirements.txt requirements.txt # Copy files from host to container

RUN pip install -r requirements.txt # Run a command in the container

COPY . . # Copy files from host to container

ENTRYPOINT ["python", "main.py"] # Run a command as soon as the container starts

Build an image

# Build an image from the Dockerfile in the current directory
# docker build -t <image-name>:<tag> <context>
# -t: tag the image with a name
# .: use the current directory as context
docker build -t my-image:latest .

Run a container

# Run a container from an image
# docker run <image-name>:<tag>
# -d: run the container in detached mode
# -p: publish a container's port(s) to the host
# --name: name the container
# --rm: remove the container when it exits
docker run -d -p 5000:5000 --name my-container --rm my-image:latest

Storage

Storage inside a container is ephemeral and is destroyed when the container is removed.

Volumes and bind mounts are used to persist data.

Transferring files into containers

# COPY <src> <dest>
# ADD has the same syntax, src can be a URL or a tar file
COPY requirements.txt requirements.txt
COPY . .
# docker run -v <host-path>:<container-path>
docker run -v $(pwd):/app

Ports

Ports are used to expose a container’s network socket to the host.

This must be done explicitly when running the container with the -p flag.

Port visualization

Loading diagram...
# docker run -p <host-port>:<container-port> <image-name>
docker run -p 8000:80 my-web
docker run -p 3306:3306 my-database

Docker compose

Docker compose is a tool for defining and running multi-container Docker applications.

It uses a declarative YAML file to define and configure all the different services that make up the application. It can also specify volumes and networks to be created automatically as needed.

Even when dealing with a single container, having a docker-compose.yml file allows to easily visualize and change its configuration without having to use all the command line frags.

docker-compose.yml

version: "3.9" # Version of the docker-compose file

services: # List of services
  db:
    image: mysql # Pull the image from Docker Hub
    volumes:
      - db-data:/var/lib/mysql # Create a volume to persist data
    environment: # Set environment variables
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: example
      MYSQL_USER: example
      MYSQL_PASSWORD: example
  web: # Name of the service
    build: . # Build the image from the Dockerfile in the current directory
    ports: # List of ports to forward
      - "5000:5000"
    volumes: # List of volumes to mount
      - .:/app
    depends_on: # List of services to start before this one
      - db

volumes: # List of volumes
  db-data: # Name of the volume

Examples

Three examples of how to use Docker in your workflow.

Distributing a Haskell script

You have a Haskell script that you want to distribute to other people.
They may not have the whole Haskell toolchain installed or they may be unfamiliar with it.
Docker provides easy portability.

# https://hub.docker.com/_/haskell
FROM haskell:slim       # Official base image
COPY main.hs .          # Copy the script into the container
RUN ghc -o main main.hs # Compile the script
ENTRYPOINT ["./main"]   # Run the script as soon as the container starts
docker build -t my-haskell-script .   # Build the image
docker run -it --rm my-haskell-script # Create and run the container

Running Qiskit in a Jupyter notebook

You want to run a Jupyter Notebook with Qiskit on a machine that does not have nor want Python and the right packages installed. Docker provides immediate access to the right environment without polluting the host system.

# https://quay.io/repository/jupyter/datascience-notebook
FROM quay.io/jupyter/datascience-notebook           # Official base image
USER root                                           # Switch to root user
COPY requirements.txt .                             # Copy the requirements file
RUN pip install --no-cache-dir -r requirements.txt  # Install the requirements
docker build -t jupyter-qiskit-example .  # Build the image
docker run -it --rm \                     # Create and run the container.
  -p 8888:8888 \                          # Forward port 8888 to the host
  -v $(pwd)/notebooks:/home/jovyan/work \ # Bind mount the notebooks directory
  -e NB_UID=$(id -u) -e NB_GID=$(id -g) -e GRANT_SUDO=yes \ # Env vars
  jupyter-qiskit-example

Web architecture with php and mysql

You want to run a php application with a mysql database.

Loading diagram...
docker compose up -d # Run the application in detached mode
docker compose down  # Stop the application
docker volume prune  # Remove all unused volumes

Commands

A collection of the most common commands you may need

Images commands

# Build an image
docker build -t <image-name>:<tag> <context>
# List all images
docker images
# Remove an image
docker rmi <image-name>:<tag>
# Remove all images
docker rmi $(docker images -q)
# Remove all dangling images
docker image prune

Container creation commands

# Create and run a container from an image
# docker [OPTIONS] run <image-name>:<tag>

# (some) OPTIONS:
# -it: run the container in interactive mode with terminal attached
# -d: run the container in detached mode
# -p: publish a container's port(s) to the host
# --name: name the container
# --rm: remove the container when it exits
# -v <host-path>:<container-path>: mount a host directory to a container directory
# -v <volume-name>:<container-path>: mount a volume to a container directory
# --env-file <file>: load environment variables from a file
# -e <key>=<value>: set an environment variable
# --entrypoint <command>: run a command as soon as the container starts
docker run -d -p 5000:5000 -v my-volume:/app --name my-container my-image:latest
docker run -it -v ./app:/app my-image:latest
docker run -e MYSQL_PASSWORD=pass --rm my-image:latest
docker run -it --entrypoint "/bin/bash" my-image:latest

Container management commands

# List all running containers
docker ps
# List all containers
docker ps -a
# Stop a container
docker stop <container-name>
# Stop all running containers
docker stop $(docker ps -q)
# Remove a container
docker rm <container-name>

Container interaction commands

# Run a command in a running container
docker exec <container-name> <command>
# Attach a terminal to a running container
docker exec -it <container-name> /bin/bash
# Show logs of a container
docker logs <container-name>
# Show logs of a container in real time
docker logs -f <container-name>

Volume commands

# Create a  named volume
docker volume create <volume-name>
# List all volumes
docker volume ls
# Remove a volume
docker volume rm <volume-name>
# Remove all unused volumes
docker volume prune

Docker compose commands

# Run the application
docker compose up -d
# Force rebuild of images
docker compose up --build
# Run a single container
docker compose up <service-name>
# Stop the application
docker compose down
# List all running containers
docker compose ps

References

Images