How To Successfully Implement A Healthcheck In Docker Compose

1. Introduction

A health check is exactly what they sound like - a way of checking the health of a resource. In the case of Docker, a health check is used to determine the health of a running container.

When a health check command is created, it defines how a container can be tested to see if it is working correctly. With no health check defined, Docker cannot know whether or not the services running within your container are actually started or not.

While working with Docker, two ways of defining a health check exist:

  • Dockerfile
  • Docker-Compose file.

I will cover only Docker-Compose Healthchecks within this article.

2. An example using Nginx

Let’s see how health checks work by using a simple Nginx web service. To create a very simple website we need three files:

A docker-compose.very-simple-web.yml, a Dockerfile, and an index.html:

version: '3.4'
services:
  web:
    image: very-simple-web
    build:
      context: ./
      dockerfile: Dockerfile
    ports:
      - "80:80"
    restart: unless-stopped
FROM nginx
COPY html /usr/share/nginx/html
<html>
<head>
    <title>Hello World FTP</title>
</head>
<body>
  Hello World
</body>
</html>

The three files must be saved in this structure

Sample directory structure of website to build with docker-compose

Now let’s create the service:

docker-compose up -d --build

If you open a web browser to localhost you should see “Hello World”

3. Why do we need a health check?

Well, in this special case normally we don't need any health check because we are just serving a simple website with Nginx. But in a production environment, it is possible that we run multiple processes that could crash.

In this case, we could use our website, but the Docker service will still be running even though we cannot access the website.

If this service is running within a Docker Swarm, the Swarm will still think everything is working correctly because the container is in a running state. This leads to a problem that the Swarm thinks everything is fine and will not restart the container so that it will be working again.

4. Configure a health check within the compose file

As an example, the docker-compose.very-simple-web.yml will be extended by a simple curl based health check (will be explained later):

version: '3.4'
services:
  web:
    image: very-simple-web
    build:
      context: ./
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "80:80"
    healthcheck:
      test: curl --fail http://localhost || exit 1
      interval: 60s
      retries: 5
      start_period: 20s
      timeout: 10s

The Docker Compose Healtcheck contains five properties:

  • test: This property specifies the command that will be executed and is the health check of the container. This command HAS TO be available and working if everything is set up correctly.
  • interval: This property specifies the number of seconds to initially wait before executing the health check and then the frequency at which subsequent health checks will be performed.
  • timeout: This property specifies the number of seconds Docker awaits for your health check command to return an exit code before declaring it as failed.
  • retries: This property specifies the number of consecutive health check failures required to declare the container as unhealthy.
  • start_period: This property specifies the number of seconds your container needs to bootstrap. During this period, health checks with an exit code greater than zero won’t mark the container as unhealthy; however, a status code of 0 will mark the container as healthy.

Note that this health check is based on curl . To use this health check you have to guarantee that curl is installed within the image used for running the service.

If curl is not available another often used health check is based on wget and can look like this:

wget --no-verbose --tries=1 --spider http://localhost || exit 1

Just replace the curl command in the prior defined compose file.

5. Often used Docker health checks

5.1 WGET

healthcheck:
  test: wget --no-verbose --tries=1 --spider http://localhost || exit 1
  interval: 60s
  retries: 5
  start_period: 20s
  timeout: 10s

5.2 CURL

healthcheck:
  test: curl --fail http://localhost || exit 1
  interval: 60s
  retries: 5
  start_period: 20s
  timeout: 10s

5.3 What to do if CURL/WGET is not available?

If you use an image that doesn’t have curl or wget installed you need to install it. This can be achieved by adding an install command to your Dockerfile.

curl

RUN apk --update --no-cache add curl

wget

RUN apt-get update && apt-get install -y wget

But keep in mind that if you addcurl or wget you could also add all the attack surfaces of those two tools.

A good workaround will be creating your own program which will then be included in the docker health check test command.

6. A custom health check

In comparison to curl or wget the custom health check gets over all the issues of using an external tool:

  1. You’re using the same runtime as your actual app, so there are no additional prerequisites for your health check that has to be installed
  2. You can decide which logic you want in your health check and it can stay private, so only the Docker platform can execute that code.

The downside is that you have to create and maintain a separate piece of code. But as this will be written in the same language as your app it should be much easier to achieve than developing a complex curl or wget command (If it is complex).

7. Closing Notes

I hope you learned that having your Docker container started does not necessarily mean that your application is running and works how it was designed. Docker health checks can be implemented quickly to identify possible bugs in your software before they become a real problem.

Next time you create a Docker-Compose file, consider adding a health check.

Feel free to connect with me on Medium, LinkedIn, and Twitter.