Skip to content
Monitor with code open in an editor, in a dark room with a warm light in the background

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 HEALTHCHECK instruction 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=1 environment variable when running docker build.
  • Also add the line # syntax=docker/dockerfile:1 at 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.

Back to the blog

Other articles

See all articles

Tell us about your case

In a free 20-minute call we look at where you are and where to start. No commitment.

Book a 20-minute call
WhatsApp