Steps to check when using docker
- Keep image small
- Keep build fast
- Make build reproducible
- Improve security
- Improve usability
- Setup linting
- Deploy right
- Provide documentation
- See checklists for specific tools
Image size matters
But if you are using multi-stage builds, you should do steps below only in final stage. You can still reduce size of images of build stages, but it's not so important.
- Use alpine (except for python)
- Use multi-stage builds
- Use whitelist dockerignore
- Alternatively, use this dockerignore
- Reduce number of image layers
- Combine repeated commands (
RUNwith\ &&,ENVwith\, etc.)- Note: sometimes this is in conflict with build time
- Combine repeated commands (
Building image faster as well as running application in container faster
Build performance is really important, cause it provide better development experience and allow to speed up CI cycle.
- Place instructions that are less likely to change (and easier to cache) first
- Install dependencies first, then copy application sources (see documentation)
- Prefer exec form for
CMD,ENTRYPOINTandRUNinstructions - Place
ENVinstructions as late as possible
Making build process reproducible and error prune
- Make container stateless and universal
- Use
VOLUMEinstruction to store state in volumes - Settings should be provided via environment variables when container is running
- Use
- Add healthchecks
- Ensure healthcheck exit code is either 0 (healthy) or 1 (unhealthy)
- Use autoheal
- Pin virtually all versions
- Explicitly specify base image version
- Use lock files
- See checklists for specific tools
- Use shell with additional flags to improve robustness
- See The Eeuo pipefail option and best practice to write a shell script
- Use
set -Eeuo pipefailin all bash scripts (e.g. inENTRYPOINT) - For bash:
SHELL ["/bin/bash", "-Eeuo", "pipefail", "-c"] - For ash (alpine):
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
- Use CI tool to build and publish image
- Do not use
latesttag, always explicitly tag images (see 1 and 2)
Security matters, and there are no excuses
Docker containers are quite secure itself, but there are vulnerabilities in docker daemon itself, that allow to escape attacks.
- Use trusted base images
- Build using unprivileged user (check out podman)
- Run final process from unprivileged user (Dockerfile)
- Consider using
--read-onlyflag - Make secrets unavailable to unprivileged users on host system
Making image easier to use
- Expose used ports
- Prefer common, traditional ports
- Use port 8080 for http (see List of TCP and UDP port numbers)
- Add a development image
- Mount source directory to development container as running
- Enable debug mode, autoreload, increase log verbosity
Check your
Dockerfilefor errors automatically
- Use hadolint
- Run linting in CI pipeline
- Lint your
entrypoint,healthcheckand other scripts
Then it comes to deploy on the production
- Have single image for QA/Staging/Production
- Build image once and publish it to the registry
- Consider using a private registry
- Consider using watchtower to automate deploy
Make users (including future yourself) suffer less using your image
- Have a nice
README.mdfile, like official images have - Update it automatically with dockerhub-description
- Add badges
- CI pipeline status
- Image size
- Layers count
- DockerHub stars
- DockerHub pulls
- DockerHub latest version
- Find more at shields.io and badgen.net.
- Define opencontainers annotations as labels
Check if you are using
docker runcorrectly
- Use
--rm,-it
- Specify name:
--name=app - Make sure container will restart on failure:
--restart=<on-failure|always|unless-stopped> - Prevent resource depletion
- Limit memory usage:
--memory=1G(upper bound) and--memory-reservation=100M(lower bound) - Limit CPU usage:
--cpus=0.5/--cpus=16 - Set CPU usage priority:
--cpu-shares=512(see documentation) - Set I/O priority:
--blkio-weight=100(see documentation) - Configure log rotation globally or per-container
- Make sure you have good PID 1 (or use
--init) to prevent zombie processes
- Limit memory usage:
docker-compose.ymlis the way to specify multiple services
- Specify compose version
- Service names as what they are, not what they use (e.g. database instead of postgres, api instead of fastapi)
- Configure services
- Pin image versions
- Configure log rotation for background services
- Limit resource usage if necessary via
mem_limit- Split critical services from utility and limit cpu and IO usage
with
blkio_config.weightandcpu_shares
- Split critical services from utility and limit cpu and IO usage
with
- Specify listen address for
ports - Specify
restart - Specify service dependencies (
depends_on) - Use
read_onlyif possible - Use yaml anchors to extract common parts
- Split special services
- Specify one-shot tasks in separate profile
- Specify debug tasks in separate profile
- Specify development compose
- Lint docker-compose files with
yamllint
Alpine package manager
- Use
--no-cachekey
Ubuntu / Debian package manager
- Update before installing:
apt-get update - Use
--no-install-recommendskey - Use
--yeskey - Remove redundant state information:
rm -rf /var/lib/apt/lists/* - Pin versions (
apt-get install <package>=<version>)- Use
apt-cache madison <package>to get available versions
- Use
Arch Linux package manager
WARNING: Arch Linux is not recommended as a base image
- Update system:
pacman -Syu - Use
--noconfirmoption - Cleanup package cache after installation:
rm -rf /var/cache/pacman/pkg/*
- Use faulthandler:
PYTHONFAULTHANDLER=yes - Disable output buffering:
PYTHONUNBUFFERED=yes - Disable bytecode writing:
PYTHONDONTWRITEBYTECODE=yes(if process is running not too often)
Python package manager
- Pin versions of all packages in
requirements.txt(usepip freeze) - Do not check for pip version on start:
PIP_DISABLE_PIP_VERSION_CHECK=yes - Disable cache:
PIP_NO_CACHE_DIR=yes - Use
PIP_DEFAULT_TIMEOUT=120to preventConnectTimeoutError
Python package manager
- Pin version:
POETRY_VERSION=1.16.0 - Disable interactivity:
POETRY_NO_INTERACTION=true - Install in recommended and secure way with checksum check:
curl -sSL https://install.python-poetry.org -o install-poetry.pyecho "$POETRY_HASHSUM install-poetry.py" | sha256sum --checkpython3 install-poetry.py
- Store virtualenvs in project's root:
POETRY_VIRTUALENVS_IN_PROJECT=true- Copy
.venvdir from build stage to final - Remove
*.pycfiles from.venv:RUN find /app/.venv -name '*.pyc' -delete(this reduces image size by ~10%) - Remove
pip,setuptoolsandwheelfrom.venv - Remove
*.pyc,ensurepip,lib2to3anddistutilsfrom final image - Do not copy to main image (or remove from builder image) poetry files:
pyproject.tomlandpoetry.lock
- Copy
- Use
--no-devkey - Use
--no-rootkey
- Use cargo-chef for
cargo build
- Increase
shm_size