- Use an appropriate / minimal base image that's actively maintained and responsive to security updates. The current recommendation is
minidebas it's minimal and well-maintained from a security standpoint. Bitnami offers minideb based images for many common use cases such asbitnami/javafor OpenJDK andbitnami/postgresql. - Minimize number of instructions being used as each instruction like
ENV,ARGgenerates a layer which can be separately cached for subsequent rebuilds- Collapse instructions where allowed (i.e.
ENVstatements can be combined,ARGcannot). Typically multipleRUNinstructions are unnecessary and should be combined. - The one exception is when a specific
ENVvalue depends on anotherENVvalue. In such instances, anotherENVblock must be introduced to consume the previously declared value.
- Collapse instructions where allowed (i.e.
- Prefer ordering instructions to prioritize cacheable instructions earlier
ENV,VOLUME,ARG,EXPOSE,ENTRYPOINT,CMD,HEALTHCHECK,ADD(i.e. from urls) andCOPY(in some cases) are examples. TheRUNinstruction always prevents caching from the point it is introduced forward.
- Only use
ENVfor values intended to be set by container consumers - don't use them as general purpose variables, as the presence of anENVvar typically signals a configurable setting to consumers. - Use
ARGfor variables that can be supplied at build-time and as variables used during the build that shouldn't be surfaced to end users- Use
ARG lowerfor arguments intended to be supplied by--build-args - Use
ARG UPPERfor arguments intended to be as environment variables exposed only during build-time
- Use
- Always add standard static Puppet metadata values early to the
Dockerfile:
LABEL org.label-schema.maintainer="Puppet Release Team <release@puppet.com>" \
org.label-schema.vendor="Puppet" \
org.label-schema.url="https://github.com/puppetlabs/pe-puppetdb" \
org.label-schema.name="PE Puppet Server" \
org.label-schema.license="Apache-2.0" \
org.label-schema.vcs-url="https://github.com/puppetlabs/pe-puppetdb" \
org.label-schema.schema-version="1.0" \
org.label-schema.dockerfile="/Dockerfile"
Add metadata labels determined dynamically near the end of the Dockerfile:
LABEL org.label-schema.version="$version" \
org.label-schema.vcs-ref="$vcs_ref" \
org.label-schema.build-date="$build_date"
-
EXPOSEis useful as documentation, but doesn't actually result in open ports at runtime -
Use an init process for multi-process containers to properly:
- exit when child processes terminate
- fulfill PID 1 responsibilities (zombie reaping and signal forwarding)
A popular solution across containers is to to run custom entrypoint scripts out of the
docker-entrypoint.ddirectory with tini like:
ENTRYPOINT ["/tini", "-g", "--", "/docker-entrypoint.sh"]
CMD ["foreground"]
This also allows for running multiple processes, which tini and dumb-init don't typically support - but because of the use of Bash, also loses signal handling capabilities.
CLI run and exit style containers are typically invoked directly like
ENTRYPOINT ["/opt/puppetlabs/bin/puppet"]
- Define a
HEALTHCHECKto inform Docker when a container service is ready. Docker / compose are still useful as testing tools and this check can be leveraged there. This check typically should not consider downstream service dependencies in other containers.
NOTE: this check is not used by Kubernetes as it has separate probes for startup, liveness and readiness which have different semantics.
- Define a
VOLUMEfor each area of the file system that should persist user data that survives upgrades. If a user doesn't map theVOLUMEto a local path via bind mount or to a named volume, Docker creates an anonymous volume.
NOTE: VOLUME directories should be empty at startup, because Kubernetes will shadow the contents with an empty VOLUME, unlike Docker which will copy files from the shadowed image layers to the new VOLUME.
- Use the
USERinstruction to run as a high-numbered non-root user created in the container during the mainRUNinstruction. This better supports security guidelines for Kubernetes and is typically necessary to support OpenShift.- When using
COPYit is often useful to supply--chown=user:groupto match theUSER.
- When using
- When installing packages during a
RUNinstruction, be sure to cleanup any package manager files from disk to reduce shipping container size. - Always add the current
Dockerfilein a step at the end withCOPY Dockerfile /as a reference
- Optimize around use of buildkit / buildx, as it provides:
- Improved caching for repeat builds / shorter build times
- Ability to execute instructions in parallel where possible
- Ability to intelligently skip unnecessary intermediate
Dockerfileinstructions
- Use hadolint to scan the Dockerfile / embedded shell scripts (hadolint includes shellcheck)
- Use an image scanner