Terminus by Warp
Understand healthcheck in Docker Compose

Understand healthcheck in Docker Compose

 Gabriel Manricks
Gabriel Manricks
Chief Architect at ClearX

In Docker Compose, a health check is a configuration option that allows you to define a command to periodically check the health of a container. This feature helps ensure that the container is not only running but also in a "healthy" state, meaning it's ready to handle requests or perform its intended functions.

The short answer

To define a health check for a specific service, you can specify the [.inline-code] healthcheck[.inline-code]  property within the service definition in the [.inline-code] compose.yaml[.inline-code]  file as follows:

 services:
  <service_name>:
    image: <image_name>
    healthcheck:
      test: <command>

Where [.inline-code] command[.inline-code]  is the command that will be periodically executed by Compose to check whether the service is healthy or unhealthy. The container is considered healthy if the exit status of this command is [.inline-code] 0[.inline-code] , and unhealthy otherwise.

For example, the following configuration checks whether the [.inline-code] webserver[.inline-code]  service is healthy by sending an HTTP request to the [.inline-code] nginx[.inline-code]  server located at the [.inline-code] localhost[.inline-code] address:

 services:
  webserver:
    image: nginx
    healthcheck:
      test: "curl -f http://localhost"

Note that the [.inline-code] -f[.inline-code]  flag will cause the command to fail by returning a non-zero exit code if the HTTP request' status code represents an error (e.g. [.inline-code] 500[.inline-code] ).

[#easily-retrieve-syntax-using-ai] Easily retrieve this syntax using the Warp AI feature [#easily-retrieve-syntax-using-ai]

If you’re using Warp as your terminal, you can easily retrieve this syntax using the Warp AI feature:

Entering [.inline-code] docker compose health checks[.inline-code]  in the AI question input will prompt a human-readable step-by-step guide, including code snippets.

[#customize-health-check-settings] Customizing the health check settings [#customize-health-check-settings]

By default, Docker will run the test command every 30 seconds, and it will give the command 30 seconds to complete before considering the health check failed. If the health check command fails 3 times in a row, the container is then considered "unhealthy".

To change these settings, you can use the following properties:

  • [.inline-code] interval[.inline-code] : the period between health checks (default [.inline-code] 30s[.inline-code] ).
  • [.inline-code] timeout[.inline-code] : the amount of time given to the command to complete (default [.inline-code] 30s[.inline-code] ).
  • [.inline-code] retries[.inline-code] : the number of consecutive fails before considered unhealthy (default [.inline-code] 3[.inline-code] ).

For example:

 healthcheck: 
  test: "curl -f http://localhost"
  interval: 15s
  timeout: 1m
  retries: 2

In some cases, you may have a container that takes a long time to initially start, which would cause the service to be considered unhealthy using the regular interval settings.

To change these settings, you can use the following properties:

  • [.inline-code] start_period[.inline-code] : the grace period during which Compose will not consider the health check failures (default [.inline-code] 0s[.inline-code] ). However, if the health check succeeds, then the container is marked healthy, and the grace period ends early.
  • [.inline-code] start_interval[.inline-code] : the period between health checks during the grace period (default [.inline-code] 5s[.inline-code] ).

For example:

 healthcheck: 
    test: "curl -f http://localhost"
    start_period: 1m
    start_interval: 10s

[#customize-test-commands-format] Customizing the test command format [#customize-test-commands-format]

In Docker Compose, health check test commands can be executed using two different keywords: [.inline-code] CMD[.inline-code]  and [.inline-code] CMD-SHELL[.inline-code] .

Executing test commands using [.inline-code] CMD[.inline-code] 

When using the [.inline-code] CMD[.inline-code]  form, the test command is split into multiple elements, where the first element is the command itself, and the subsequent elements are its options and arguments. In this form, the test command is executed directly without involving a shell.

For example:

 healthcheck: 
  test: ["CMD", "curl", "-f", "http://localhost"]

Executing test commands using [.inline-code] CMD-SHELL[.inline-code] 

When using the [.inline-code] CMD-SHELL[.inline-code]  form, the test command is specified as a single string. In this form, the test command is executed in a subshell, which allows you to leverage the shell's environment and features like variable substitution, pipes, or redirects.

For example:

 healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost"]

Note that, in this form, the above command is equivalent to the following one:

healthcheck:
  test: "curl -f http://localhost"

[#manage-dependencies-with-health-checks] Managing dependencies with health checks [#manage-dependencies-with-health-checks]

One of the main uses of health checks is to manage the dependencies between containers. For example, if you have a web server that depends on a MySQL server, you might need to make sure that the MySQL server is up and running before starting the web server.

This can be done using the [.inline-code] depends_on[.inline-code]  property, waiting for the condition [.inline-code] service_healthy[.inline-code]  as follows:

 services:
  mysql:
    image: mysql:8.2
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: test
      MYSQL_PASSWORD: test
    healthcheck:
      test: "mysql --user=$$MYSQL_USER --password=$$MYSQL_PASSWORD --execute \"SHOW DATABASES;\" || exit 1"
      interval: 10s
      timeout: 10s
      retries: 5
      start_period: 20s
 test_site:
    image: nginx
    depends_on:
      mysql:
        condition: service_healthy

In this example, there is a health check set on the MySQL service, which simply tries to execute a simple query to see if the DB is responding.

Under the [.inline-code] test_site[.inline-code]  service, there is the [.inline-code] depends_on[.inline-code]  field with a single dependency on the service named [.inline-code] mysql[.inline-code]  and a condition of [.inline-code] service_healthy[.inline-code] , which means when the service is marked healthy. Running this, you will see that at first, only the MySQL service will be started, and the test_site container will wait until MySQL is marked healthy to begin running.

To learn more about the dependencies between services in Docker Compose, you can read our in-depth article on how to use the depends_on field in Compose.

[#view-health-check-status-and-logs] Viewing the status and logs of a health check [#view-health-check-status-and-logs]

To view the status of the health checks along with the logs from each test, you can use the following [.inline-code] docker inspect[.inline-code]  command:

$ docker inspect --format='{{json .State.Health}}' <container_name_or_id>

Where:

  • The [.inline-code] docker inspect[.inline-code]  command returns a JSON output with all the information about a specific container.
  • The [.inline-code] --format[.inline-code]  option extracts just the subfield in the JSON object with the health check info, which is located under the [.inline-code] Health[.inline-code]  key that is inside an object called [.inline-code] State[.inline-code] .

This output can be piped to a JSON parsing CLI like [.inline-code] jq[.inline-code]  or [.inline-code] fx[.inline-code]  to pretty print the JSON like in the following example:

 $ docker inspect --format "{{json .State.Health }}" test-docker-mysql-1 | jq

Checking for a specific HTTP status code

To create a health check that checks for a specific HTTP status code, you can combine the [.inline-code] curl[.inline-code]  command to make a request and the [.inline-code] grep[.inline-code]  command to filter the result as follows:

 services:
  <service_name>:
    image: <image_name>
    healthcheck:
      test: "curl -s -I http://localhost | head -n 1 | grep 200"

Where:

  • The [.inline-code] curl -s -l[.inline-code]  command is used to only print the response’s headers while avoiding printing the request’s progress or errors.
  • The [.inline-code] head -n 1[.inline-code]  command is used to only print the first line of the output generated by the [.inline-code] curl[.inline-code]  command.
  • The [.inline-code] grep[.inline-code]  command is used to verify that the filtered output includes the status code 200. If the line doesn’t include the specified code, it will return a non-zero exit code, triggering a failed health check.

[#check-open-tcp-ports]Checking for an open TCP port [#check-open-tcp-ports]

A basic health check that can be done on a variety of containers is to check if a TCP port is open and accepting connections.

[#check-open-tcp-port-with-netcat] Attempting a TCP connection with Netcat [#check-open-tcp-port-with-netcat]

If the container you want to perform a health check on has access to the [.inline-code] nc[.inline-code]  utility (short for Netcat), you can use the [.inline-code] -z[.inline-code]  flag to attempt a connection on a specified port without actually sending any data to it:

 healthcheck:
  test: ["CMD", "nc", "-z", "localhost", "5672"]

[#check-open-tcp-port-with-bash] Attempting a TCP connection with Bash [#check-open-tcp-port-with-bash]

If the container you want to perform a health check on has access to the [.inline-code] bash[.inline-code]  utility, you can use the following command to write an empty string to a pseudo file in the [.inline-code] /dev/tcp[.inline-code]  directory, which will attempt a connection on the TCP socket identified by the specified [.inline-code] ip_address[.inline-code]  and [.inline-code] port[.inline-code] :

 healthcheck:
  test: ["CMD", "bash", "-c", "echo -n '' > /dev/tcp/<ip_address>/<port>"]

Where:

  • The [.inline-code] -c[.inline-code]  option is used to tell [.inline-code] bash[.inline-code]  to execute a command.
  • The [.inline-code] -n[.inline-code]  option is used to tell [.inline-code] echo[.inline-code]  to write without appending a newline character.

For example, this command will attempt a TCP connection to the localhost identified by the IP address [.inline-code] 127.0.0.1[.inline-code]  and the port [.inline-code] 5672[.inline-code] :

healthcheck:
  test: ["CMD", "bash", "-c", "echo -n '' > /dev/tcp/127.0.0.1/5672"]

It is worth noting that even though this command uses a shell to run a command, you can’t use [.inline-code] CMD-SHELL[.inline-code]  as this feature doesn’t work in all shells (e.g. doesn’t work with sh) so you need to manually specify the use of [.inline-code] bash[.inline-code] .

Experience the power of Warp

  • Write with an IDE-style editor
  • Easily navigate through output
  • Save commands to reuse later
  • Ask Warp AI to explain or debug
  • Customize keybindings and launch configs
  • Pick from preloaded themes or design your own
brew install --cask warp
Copied!
Join the Windows waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.
Join the Linux waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.
Join the Linux waitlist or join the Windows waitlist
Join the Windows waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.