Docker & Docker-Compose

Many of our projects use the Docker containerization technology (plus the docker-compose container creation manager) to simplify establishing a development environment. By using containers to spin up a dev environment, software team members can have a uniform development experience on any Linux distro, thus avoiding the “it works on my machine” conundrum.

Make sure you understand Linux before attempting to understand Docker, as our Docker images are all based on Linux.

The full docs for Docker and Docker-Compose are here:

Docker Docker-Compose

This doc only provides a high level overview of what you need to maintain the stack; it does not give in depth info about Docker. We recommend you consult the docs and Google for more info.

Common Errors

You may run into permission issues running docker/docker-compose commands. You can add yourself to the docker group (usermod -a -G docker <user>) or use sudo.

Docker and Dockerfiles

In order to understand docker you need to understand a few terms/concepts:

  • Images are templates that docker containers are created from.
  • Containers are the actual container environment the program runs in. Think of it as a chroot on steroids.
  • Dockerfiles are files that describe how to create an image.

At the core of Docker is the Dockerfile, which describes how to create the image. Here’s a sample Dockerfile from IMS:

FROM python:3.9.0-alpine3.12
COPY . /classitron
WORKDIR /classitron

# Install dependencies
RUN apk add --no-cache --virtual .build-deps \
                gcc \
                libc-dev \
                libffi-dev \
                linux-headers \
                jpeg-dev \
                mariadb-dev \
                python3-dev \
                postgresql-dev \
                git \
        ;
RUN pip3 install -r requirements.txt

# That's literally it lmao
# Setup command
WORKDIR /classitron/classitron
CMD python3 manage.py runserver

Let’s break this down:

FROM - this is the base image your image is derived from. In this case, we’re pulling the public python image from Docker Hub. The variant (3.9.0-alpine3.12) is specified after the colon. This whole thing (image name + variant) is called the image tag.

COPY - here, we’re copying the current directory (which is the classitron project itself) on the host system to the /classitron path. This is so we can run the project inside the container.

WORKDIR - this sets the current directory, equivalent to cd. Note that since environment changes don’t persist over RUN commands you can’t just RUN cd <dir>, as it will just be reset.

RUN - this runs a command as you normally would from the shell.

Notice that instead of a common package manager like apt, Docker images usually use the Alpine Linux distro and the apk package manager.

Creation

To create a Dockerfile for a project, put a file named Dockerfile (case sensitive) in the project root. You can either build the image manually (see docs) or have docker-compose do it for you (read below).

Maintenance Notes

The IMS Dockerfile above specifies a specific Python version and Alpine version to derive from. While this allows additional stability, in that the environment will not suddenly change on you, it also means you will need to update the image every few months; find the right image tag (in the case of IMS, python:-alpine) and change the Dockerfile to use that.

You can also base your Dockerfile off the :latest tag. However, this means that if the maintainer of the image updates :latest, your environment may change/break.

Other than that you should be fine. See the handbook and official docs for more details.

Docker-Compose

Docker Compose is a tool to automate creating and managing an environment with multiple containers. This allows us to e.g. run a separate mysql container which the classitron container can conect to. It also massively simplifies creating and running containers by putting it all in a readable YAML format.

With your earlier basic knowledge of Docker, you should more or less be able to understand what most of this docker-compose.yml does:

version: "3"
services:
  classitron:
    image: "registry.gitlab.com/amadoruavs/vision/classitron:latest"
    build: ./
    stdin_open: true
    tty: true
    network_mode: host
    environment:
      - INTEROP_URL="http://localhost"
      - INTEROP_PORT="8000"
      - INTEROP_USER="testuser"
      - INTEROP_PASS="testpass"
      - DB_HOST="mysql"
      - DB_NAME="classitron"
      - DB_USER="classitron"
      - DB_PASS="suasdev2020" # Development purposes
  mysql:
    image: "mysql:latest"
    stdin_open: true
    tty: true
    environment:
      - MYSQL_USER="classitron" # Same as above
      - MYSQL_PASSWORD="suasdev2020" # Development purposes
      - MYSQL_ROOT_PASSWORD="suasdev2020" # Development purposes

Again, we can break this down:

version: "3" specifies the docker-compose YML format version. This should always be 3 unless you have a specific need to use an older version.

services specifies the containers used in the docker-compose application. In this case, we list 2 containers used below - classitron and mysql.

classitron: and mysql: are the container names. These can be basically arbitrary, but must follow regular network hostname rules. In each container, you can reference the other using the container name as the hostname (e.g. classitron connects on mysql://mysql:3306).

image is the image used. Notice that one pulls from Docker Hub (simple

: format or /: format), while the other pulls from our own team's private Gitlab image registry (registry.gitlab.com/amadoruavs/:). `build` is optional and describes the project to build for the container. If the image does not already exist or a user triggers a build with `docker-compose build`, docker-compose will build the Dockerfile in the specified directory for you. `stdin_open` and `tty` are used to hold the container's standard input open. If you need to answer a prompt from the container (unlikely, but possible), you can attach to this stdin with `docker attach __1`. As always, see docs for more details. `network_mode: host` means the container uses the host's network (so all ports are open). By default, containers are isolated from host network; you can manually forward ports or forward the whole thing. `environment` specifies various environment variables to set when running the container (see linux env vars). ### Writing Your Own The format above contains most of what you need to know. Honorable mention: `cmd: ""` will specify what command to run in the container on startup, overriding the `CMD` in the Dockerfile. To create a docker-compose environment for your project, add a `docker-compose.yml` to your project root. To run it, run `docker-compose up`. You may need sudo. You can also run a different docker-compose file by using `docker-compose -f up`. ## Gotchas Docker-compose sometimes tries to preserve cached volumes across rebuilds. Use `docker-compose rm -v` to remove cached volumes. ### Maintenance Notes Really, not much. Just keep in mind where the configs are and know how to change them when needed. You'll probably only ever need to change the environment variables in the yaml. Also keep in mind how to add and remove containers to the docker-compose file, interface between containers using hostnames, and connect to containers with `docker attach`. Those are the basics of using Docker. Contact the writer of this <vwangsf@gmail.com> if you have questions.