[Docker] Dockerfile

Dockerfile

The Dockerfile contains special instructions, which tell the Docker Engine about the steps required to build an image. To invoke a build using Docker, you issue the Docker build command.

A typical Dockerfile looks like this:

1
2
3
4
5
6
FROM ubuntu:latest
LABEL author="sathyabhat"
LABEL description="An example Dockerfile"
RUN apt-get install python
COPY hello-world.py
CMD python hello-world.py

Build Context

A build context is a file or set of files available at a specific path or URL. To understand this better, we might have some supporting files that we need during a Docker image build—for instance, an application specific config file that was been generated earlier and needs to be part of the container.

The build context can be local or remote—we can even set the build context to the URL of a Git repository, which can come in handy if the source files are not located on the same host as the Docker daemon or if we’d like to test out feature branches.

  • Build docker via a specific branchdocker build https://github.com/sathyabhat/sample-repo.git#mybranch
  • Build docker via a pull requestdocker build https://github.com/sathyabhat/sample-repo.git#pull/1337/head

The build command sets the context to the path or URL provided, uploading the files to the Docker daemon and allowing it to build the image.

Dockerignore

The ignore list is provided by a file known as .dockerignore and when the Docker CLI finds this file, it modifies the context to exclude the files/patterns provided in the file. Anything starting with a hash (#) is considered a comment and ignored.

Example .dockerignore file

1
2
3
*/temp*
.DS_Store
.git

Build with docker build

  • In a folder, create dockerfile.
  • Run docker build . Will build according to the instructions in the dockerfile.
  • Run docker image docker run <image-id>
  • Tag an docker image docker tag <image_id> <tag_name>
    • Run with tag docker build -t <tag_name> .
    • Search for image by tag docker images <image-tag>

Dockerfile Instructions

FROM

The FROM instruction tells the Docker Engine which base image to use for subsequent instructions. Every valid Dockerfile must start with a FROM instruction.

  • FROM <image> [AS <name>]
  • FROM <image>[:<tag>] [AS <name>]

WORKDIR

WORKDIR instruction sets the current working directory for RUN, CMD, ENTRYPOINT, COPY, and ADD instructions.

  • WORKDIR /path/to/directory

WORKDIR can be set multiple times in a Dockerfile and, if a relative directory succeeds a previous WORKDIR instruction, it will be relative to the previously set working directory.

1
2
3
4
5
FROM ubuntu:latest
WORKDIR /usr
WORKDIR src
WORKDIR app
CMD pwd

ADD and COPY

ADD and COPY allow you to transfer files from the host to the container’s filesystem. Copy supports basic copying of files to the container, while ADD has support for features like tarball auto extraction and remote URL support.

  • ADD <source> <destination>
  • COPY <source> <destination>

i.e. Moving the requirements.txt file from the current working directory to the /usr/share/app directory.

  • ADD requirements.txt /usr/share/app
  • COPY requirements.txt /usr/share/app

RUN

The RUN instruction will execute any commands in a new layer on top of the current image and create a new layer that is available for the next steps in the Dockerfile.

1
2
3
RUN <command> (known as the shell form)
RUN ["executable", "parameter 1", " parameter 2"] (known as the
exec form)

Docker recommends chaining multiple RUN commands into a single command.

1
2
3
4
RUN apt-get update && apt-get install -y \
foo \
bar \
baz

This reduces the number of layers and makes for a leaner Docker image.

CMD and ENTRYPOINT

1
2
3
4
5
CMD ["executable","param1","param2"] (exec form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
ENTRYPOINT ["executable", "param1", "param2"] (exec form)
ENTRYPOINT command param1 param2 (shell form)

Difference between CMD and RUN: CMD will only execute after a container starts running.

CMD is usually the last instruction in the dockerfile.

i.e.

1
2
3
4
5
FROM ubuntu:latest
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
CMD curl

In this Docker image, we select Ubuntu as the base image, install curl on it, and choose curl as the CMD instruction. This means that when the container is created and run, it will run curl without any parameters.

docker run sathyabhat:curl curl -s wttr.in

We can set ENTRYPOINT to the executable while the parameter can be passed from the command line and will be overridden.

1
2
3
4
5
FROM ubuntu:latest
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl", "-s"]

docker run sathyabhat:curl wttr.in

ENV

The ENV instruction sets the environment variables to the image. The ENV instruction has two forms:

1
2
ENV <key> <value> # the entire string after the <key> will be considered the value, including whitespace characters.
ENV <key>=<value> ... # multiple variables can be set at one time, with the equals character assigning value to the key.

i.e.

1
2
3
FROM ubuntu:latest
ENV LOGS_DIR="/var/log"
ENV APPS_DIR /apps/

The environment variables defined for a container can be changed when running a container by the -e flag.

docker run -it -e LOGS_DIR="/logs" sathyabhat:env-example

VOLUME

The VOLUME instruction tells Docker to create a directory on the host and mount it to a path specified in the instruction.

VOLUME /var/logs/nginx

EXPOSE

The EXPOSE instruction tells Docker that the container listens for the specified network ports at runtime.

i.e. Expose port 80:
EXPOSE 80
EXPOSE 53/tcp
EXPOSE 53/udp

An EXPOSE instruction doesn’t publish the port. For the port to be published to the host, you need to use the -p flag when you do a docker run to publish and map the ports.

docker run -d -p 8080:80 sathyabhat:web The -d flag makes the nginx container run in the background; the -p flag does the port mapping.

LABEL

The LABEL instruction adds metadata to an image as a key/value pair. LABEL <key>=<value> <key>=<value> <key>=<value> ....

For the Dockerfile, we should minimize the number of layers. As of Docker 1.10 and above, only RUN, COPY, and ADD instructions create layers.

A Project Dockerfile Example

What we need:

  • A Docker image based on Python3
  • The project dependencies listed in requirements.txt
  • An environment variable named NBT_ACCESS_TOKEN

Dockerfile should do:

  1. Start with a proper base image
  2. Make a list of files required for the application
  3. Make a list of environment variables required for the application
  4. Copy the application files to the image using a COPY instruction
  5. Specify the environment variable with the ENV instruction
1
2
3
4
5
6
FROM python:3-alpine
COPY * /apps/subredditfetcher/
WORKDIR /apps/subredditfetcher/
RUN ["pip", "install", "-r", "requirements.txt"]
ENV NBT_ACCESS_TOKEN="<token>"
CMD ["python", "newsbot.py"]