
Advanced Dockerfiles: HEALTHCHECK, heredoc and BuildKit mounts
Part two of Dockerfile best practices: health checks, heredoc syntax and RUN --mount for secrets, caches and SSH keys.
By Víctor Piles TemporalSRE and systems engineerPublished on 4 min read
If you’ve landed here without reading part one, I’d recommend reading it first and coming back. In this second instalment I carry on with tips and best practices for writing Dockerfiles, at a more advanced level.
In short: add a HEALTHCHECK so Docker knows whether the container is really working, and check it with docker inspect; use BuildKit’s heredoc syntax to write several commands or create files without copying them; and use RUN --mount so the build can access secrets, caches or SSH keys without leaving them in the image.
Add health checks
- The
HEALTHCHECKinstruction tells Docker how to test a container to verify that it’s working properly. For example, a server can get stuck in an infinite process loop and stop accepting new connections even though the process is still running. - It saves time when detecting errors: you can inspect your containers.
- The system catches errors that a person would struggle to spot.
- The check runs at intervals of seconds from the moment the container starts. If a check takes longer than it should for any reason, it’s considered failed.
FROM nginx
WORKDIR /usr/share/nginx/html
COPY . .
HEALTHCHECK --interval=10m --timeout=10s \
CMD curl --fail http://localhost/ || exit 1
Here the check runs every 10 minutes to make sure the web server can serve the page within 10 seconds.
Query the health checks
To see the results of the checks, inspect the running containers:
docker inspect --format='{{json .State.Health}}' $IDContenedor
{"Status":"healthy","FailingStreak":0,"Log":[{"Start":"2022-10-19T18:00:00.809087707Z","End":"2022-10-19T18:00:00.909087707Z","ExitCode":0,"Output":"Web test"}]}
Here the container is inspected in JSON format. You can see that the status is healthy and that it shows the output of our website.
Create files with heredoc syntax
- BuildKit brings performance and security improvements, among others. One of them is support for heredoc syntax, which makes complex things simple: for example, creating files without copying them into the container.
- BuildKit has been the default build engine since Docker Engine 23.0; on earlier versions you need to enable it with the
DOCKER_BUILDKIT=1environment variable when runningdocker build. - Also add the line
# syntax=docker/dockerfile:1at the top of the Dockerfile.
# syntax=docker/dockerfile:1
FROM alpine
RUN <<EOF
apk update
apk upgrade
EOF
Here the system is updated with the RUN instruction.
# syntax=docker/dockerfile:1
FROM httpd
COPY <<EOF /usr/local/apache2/htdocs/index.html
<h1>Hello World!</h1>
<p>Test web</p>
EOF
Here an accessible website is generated by writing the code directly into the image.
Create mounts
RUN --mount lets you create mounts that the build process can access to carry out actions. For example, the build can read secrets, such as AWS credentials, and upload content to S3 without needing environment variables in plain sight.
There are different types of mount:
- Bind: the default mount; it binds directories with read-only permissions.
- Cache: mounts temporary directories for compiler and package manager caches.
- Secret: a mount for accessing secrets.
- SSH: lets the container access SSH keys through the agent.
Examples:
# syntax=docker/dockerfile:1
FROM python:3
RUN pip install awscli
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
aws s3 cp test.txt s3://bucket/test.txt --expires 2022-10-31T20:30:00Z
Here an S3 bucket is accessed using secrets and files are copied to it. The secret is passed at build time, for example with docker build --secret id=aws,src=$HOME/.aws/credentials .
# syntax=docker/dockerfile:1
FROM alpine
RUN apk add openssh-client
RUN mkdir -pm 700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh \
ssh git@github.com 2>&1 | tee /dirname
Here, using the SSH agent, the build connects to the destination and writes the output to a file with tee. The agent is passed at build time with docker build --ssh default .
Using secrets and mounts instead of copying credentials into the image is one of the things I check in vulnerability management, and image builds are part of the pipelines I set up in infrastructure as code, GitOps and CI/CD. If you need anything, get in touch through the contact page.
Originally published on Medium.


