Docker Best Practices
→ 日本語版を読むA summary of Docker best practices I researched.
General Guidelines
- Pin the version of the base image. Using digest references is even better.
- Make use of build cache
- Use backslashes in
RUNto split arguments across multiple lines - Isolate applications. Do not put multiple applications in a single container.
- Do not install unnecessary packages. For example, a DB image does not need a text editor.
- Use multi-stage builds to reduce the final image size.
- Use a .dockerignore file to exclude files unrelated to the build.
Image
- Use official images as base images (do not use your own images as base images).
- Alpine is recommended due to its small size.
LABEL
-
Adding labels allows you to search for images and containers.
-
Dockerfile
dockerfile LABEL python_version="3.8.12" -
Searching
> docker images --filter "label=python_version=3.8.12"
REPOSITORY TAG IMAGE ID CREATED SIZE
mmtest latest b2cf0b43f1dc About a minute ago 2.39GB
> docker container ls --filter "label=python_version=3.8.12"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b15779dec25a mmtest "/bin/bash" 13 seconds ago Up 12 seconds blissful_chebyshev
RUN
- When statements in
RUNbecome long, split them across multiple lines using backslashes for readability. RUN apt-gethas some counterintuitive behaviors:- Always include
apt-get installin the sameRUNstatement asapt-get update(a technique called "cache busting"). Runningapt-get updatealone causes cache problems and can cause issues with subsequentapt-get install.- OK
- Always include
RUN apt-get update && RUN apt-get install -y curl
* You can also bust the cache with version pinning:
RUN apt-get update && RUN apt-get install -y curl=7.74.0
* NG
* When building, Docker stores all layers in the Docker cache. If you build multiple times, Docker considers the first and subsequent instructions identical and reuses the cache from the previous step. As a result, `apt-get update` is not executed, and there is a possibility of obtaining outdated versions of curl and nginx packages.
RUN apt-get update
RUN apt-get install -y curl
* Cleaning the apt cache
* Delete files under /var/lib/apt/lists to reduce image size.
RUN apt-get update && RUN apt-get install -y curl
&& rm -rf /var/lib/apt/lists/*
* Error handling in pipes
* `RUN` executes with `/bin/sh -c`. Since the exit code is only evaluated from the last operation in the pipe, errors in the middle of the pipe may not be detected.
* Use `set -o pipefail &&` to fail on errors.
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
ENV
- Setting version numbers with
ENVmakes maintenance easier.
ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres && …
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH
ENVcreates a layer just likeRUN. If youunsetan environment variable in a layer afterENV, it remains in the layer and can be dumped. To prevent this and properlyunsetthe environment variable, you need tosetandunsetit within a singleRUNcommand.
# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh
- Alternatively, put all commands into a shell script and execute it with a
RUNcommand.
ADD or COPY
COPY copies files from the build context or a stage in a multi-stage build into the container.
ADD, on the other hand, fetches and adds files from a remote HTTPS or Git repository.
You can also use bind mounts instead of COPY. Bind mounts are more efficient than COPY because they can include files from the build context.
Files mounted via bind mount are temporarily mounted for a single RUN execution and are not persisted in the final image. Use COPY if you want to persist files in the final image.
RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
pip install --requirement /tmp/requirements.txt
For more reliable build caching, ADD is preferable to manually adding remote files with wget or tar.
ENTRYPOINT
The best practice is to specify the command with ENTRYPOINT and set command parameters in CMD. You can pass parameters via docker run to override the CMD arguments.
FROM buildpack-deps:bullseye
ENTRYPOINT ["/bin/bash", "-c"]
CMD ["ls"]
USER
- When running a service as an unprivileged user, use
USERto switch to a non-root user. When switching to a non-root user withUSER, create the group and user in advance.
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
- However, avoid frequent use of
USERto reduce complexity. - Avoid installing and using
sudo, as it can cause unexpected TTY and signal forwarding behavior issues. If you absolutely need to usesudo, for example when initializing a daemon as root and running it as non-root, consider using gosu.
WORKDIR
- For clarity and readability, always use absolute paths for
WORKDIR. - Avoid writing
RUN cd … && do-somethingfrom a readability and maintainability perspective. UseWORKDIRinstead.