All notes


FROM alpine
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


Best practices

From Docker 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 - | wc -l > /number

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

# Not all shells support the -o pipefail option.
# Use exec form instead:
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - | 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 /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 \
    | 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




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
  COPY go /usr/local/go

Add zipped resources use multi-stage build.

FROM alpine:3.7 AS build-stage
ADD /root
RUN unzip /root/

FROM alpine:3.7
RUN apk update && apk add ca-certificates emacs
COPY --from=build-stage /root/wcfShells-master /root/master
COPY init.el /root/.emacs.d/
CMD [ "emacs" ]


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".


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

ENTRYPOINT ["s3cmd"]
CMD ["--help"] # Here CMD provides a default parameter to the ENTRYPOINT "s3cmd".

# 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 ./ /


set -e

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

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

    exec gosu postgres "$@"

exec "$@" # "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


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


multi-stage build multistage-build.

Old way

It was actually very common to have one Dockerfile to use for development (which contained everything needed to build your application), and a slimmed-down one to use for production, which only contained your application and exactly what was needed to run it. This has been referred to as the “builder pattern”. Maintaining two Dockerfiles is not ideal.

For example, a and Dockerfile which adhere to the builder pattern:

// This is used to build the go app.

FROM golang:1.7.3
WORKDIR /go/src/
COPY app.go .
RUN go get -d -v \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

//---------- Dockerfile:

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app . // Copy the go app to production image.
CMD ["./app"]


echo Building alexellis2/href-counter:build

docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \  
    -t alexellis2/href-counter:build . -f

docker container create --name extract alexellis2/href-counter:build  
docker container cp extract:/go/src/ ./app  
docker container rm -f extract

echo Building alexellis2/href-counter:latest

docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app

The new way

FROM golang:1.7.3 as builder
WORKDIR /go/src/
RUN go get -d -v
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Here it only copies the built app:
COPY --from=builder /go/src/ .
CMD ["./app"]

# The "COPY --from" line copies just the built artifact from the previous stage into this new stage. The Go SDK and any intermediate artifacts are left behind, and not saved in the final image.


HEALTHCHECK [OPTIONS] CMD command (check container health by running a command inside the container)
HEALTHCHECK NONE (disable any healthcheck inherited from the base image)

Status could be: starting, healthy, unhealthy.

The options that can appear before CMD are:
--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--start-period=DURATION (default: 0s)
--retries=N (default: 3)


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.


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


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



docker run -it --name container-test -h CONTAINER -v /data debian /bin/bash
# root@CONTAINER:/# 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目录上。


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.


Cannot add relative path to Dockerfile

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


  build: ./web/

  build: ./webstatic/
    - web

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