All notes
Dockerfil

Template


FROM alpine
MAINTAINER silencej [email protected]
RUN apk update
RUN apk add bash bash-doc bash-completion openssh openssh-doc

# Make man working:
RUN apk add man man-pages mdocml-apropos
RUN makewhatis /usr/share/man
RUN export PAGER=less

EXPOSE 8000
ENTRYPOINT bash

Best practices

From Docker

docs.docker.com: dockerfilebest practices.

Build cache

For the ADD and COPY, the contents of the files in the image are examined and a checksum is calculated for each file. The last-modified and last-accessed times of the file(s) are not considered. If there is a difference, cache is invalidated.

In other cases, just the command string itself will be used to find a match. If the command string is different, cache is invalidated.

Once the cache is invalidated, all subsequent Dockerfile commands will generate new images and the cache will not be used.

apt-get update and install

Avoid "RUN apt-get upgrade or dist-upgrade". Because many of the “essential” packages from the parent images won’t upgrade inside an unprivileged container.

Always combine "RUN apt-get update" with "apt-get install -y" in the same RUN statement. Using apt-get update alone causes caching issues and subsequent apt-get install instructions fail.

Specify each package line by line and in alphabetic order to avoid duplication.


RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

Copy files individually

Copying requirements.txt first results in fewer cache invalidations for the RUN step:


COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

Build stops when RUN fails



# This build step succeeds and produces a new image so long as the wc -l command succeeds, even if the wget command fails.
RUN wget -O - https://some.site | wc -l > /number

# Ensure to fail due to an error at any stage in the pipe:
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

# Not all shells support the -o pipefail option.
# Use exec form instead:
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

Use gosu instead of sudo

Avoid installing or using "sudo" since it has unpredictable TTY and signal-forwarding behavior that can cause more problems than it solves. If you absolutely need functionality similar to sudo (e.g., initializing the daemon as root but running it as non-root), you may be able to use "gosu".

To reduce layers and complexity, avoid switching USER back and forth frequently.

Base image, labels, and others

Base image: "Debian" is recommended (about 150 mb).

Add labels image to help organize images by project, record licensing information, to aid in automation, etc.

Prefer curl/wget over add


# Bad.
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
And instead, do something like:

# Better.
RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

By silencej/wcf

Construct the best dockerfile is not an easy job. I recommend to use:

  1. Write a shell script for all the setting. Later on you will convert this script into Dockerfile.
  2. Construct base image (with the "Dockerfile.base" as config file). Copy all the necessary files, such as Gemfile, Gemfile.lock, requirements.txt etc. And ofcourse the script file in the previous step.
  3. Run the base image in a container, try and modify the script file.
  4. Docker cp out the successful script file. Convert it into Dockerfile.

By others

crosbymichael.

Commands

COPY, ADD

SO: difference between copy and add.

ADD allows "src" to be an URL. If the "src" parameter of ADD is an archive in a recognised compression format, it will be unpacked.

COPY is recommended over ADD (where possible): "For other items (files, directories) that do not require ADD’s tar auto-extraction capability, you should always use COPY."

# COPY is used in the same way as below.

ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/         # adds "test" to /absoluteDir/

# You need to escape those paths following the Golang rules to prevent special chars from being treated as a matching pattern:
ADD arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

Copy directory from host to container

ADD go /usr/local/
will copy the contents of your local go directory in the /usr/local/ directory of your docker image.

To copy the go directory itself in /usr/local/ use:
  ADD go /usr/local/go
or
  COPY go /usr/local/go

CMD

The main purpose of a CMD is to provide defaults for an executing container.

The CMD instruction has three forms:


CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)

# The CMD instruction should be used to run the software contained by your image.
CMD ["apache2","-DFOREGROUND"]
# Or be given an interactive shell, such as bash, python and perl.
CMD ["perl", "-de0"]
CMD ["python"]
CMD ["php", "-a"]

If you use the shell form of the CMD, then the "command" will execute in "/bin/sh -c".

ENTRYPOINT

It allows that image to be run as though it was a command line tool.



ENTRYPOINT ["s3cmd"]
CMD ["--help"]

# Now the image can be run to show the command’s help:
# docker run s3cmd
# Or use the right parameters to execute another command:
# docker run s3cmd ls s3://mybucket

Example: postgres


COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

"docker-entrypoint.sh":


#!/bin/bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "[email protected]"
fi

exec "[email protected]" # "Exec" so that the final running application becomes the container’s PID 1, which allows the application to receive any Unix signals sent to the container.

# It can simply start Postgres:
docker run postgres

# It can be used to run Postgres and pass parameters to the server:
docker run postgres postgres --help

# It could also be used to start a totally different tool, such as Bash:
docker run --rm -it postgres bash

ENV

Use ENV instructions in a Dockerfile to define variable values. These values persist in the built image. To set a value for a single command, use


RUN key=value command

# Ensure that CMD ["nginx"] just works.
ENV PATH /usr/local/nginx/bin:$PATH

ONBUILD

An ONBUILD command executes after the current Dockerfile build completes. ONBUILD executes in any child image derived FROM the current image. Think of the ONBUILD command as an instruction the parent Dockerfile gives to the child Dockerfile.

Images built from ONBUILD should get a separate tag, for example: ruby:1.9-onbuild or ruby:2.0-onbuild.

VOLUME

The VOLUME instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.

Best practice: The VOLUME instruction should be used to expose any database storage area, configuration storage, or files/folders created by your docker container. You are strongly encouraged to use VOLUME for any mutable and/or user-serviceable parts of your image.

This Dockerfile results in an image that causes docker run, to create a new mount point at /myvol and copy the greeting file into the newly created volume.


FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

UFS

dockone.io.

在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。

为了能够保存(持久化)数据以及共享容器间的数据,Docker提出了Volume的概念。简单来说,Volume就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。

我们可以通过两种方式来初始化Volume,这两种方式有些细小而又重要的差别。我们可以在运行时使用-v来声明Volume.


docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
# [email protected]:/# ls /data

docker inspect -f {{.Volumes}} container-test
# 你会看到类似的输出:
# map[/data:/var/lib/docker/vfs/dir/cde167197ccc3e138a14f1a4f...b32cec92e79059437a9] 
# 这说明Docker把在host上的/var/lib/docker下的某个目录挂载到了容器内的/data目录下。

# 只有-v参数能够做到而Dockerfile是做不到的事情就是在容器上挂载指定的主机目录。例如:
docker run -v /home/adrian/data:/data debian ls /data
# 该命令将挂载主机的/home/adrian/data目录到容器内的/data目录上。

USER

If a service can run without privileges, use USER to change to a non-root user.


RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres.

FAQ

Cannot add relative path to Dockerfile

github.com.

The build actually happens in /tmp/docker-12345, so a relative path like ../relative-add/some-file is relative to /tmp/docker-12345. It would thus search for /tmp/relative-add/some-file, which is also shown in the error message. It is not allowed to include files from outside the build directory, so this results in the "Forbidden path" message.

Workaround with docker-compose

docker-compose.yml:

web:
  build: ./web/

webstatic:
  build: ./webstatic/
  volumes_from:
    - web

Then in the webstatic code I can access the code from the web container. No need for ADD ../web/ .. in webstatic.