Docker
Docker Compose
Healthcheck
Environment Variables
DevOps

Docker compose healthcheck use environment variables

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Yes, you can use environment variables in a Docker Compose healthcheck, but there are two different expansion stages and that is where most confusion comes from. Compose can interpolate variables when it reads the YAML file, and the container shell can expand variables later when the healthcheck command actually runs. To make healthchecks reliable, you need to decide which layer should resolve each variable and write the command accordingly.

Compose Interpolation Versus Container Expansion

Compose supports variable interpolation in YAML values using syntax like ${PORT}. That happens before the container starts.

Example:

yaml
1services:
2  web:
3    image: nginx:alpine
4    healthcheck:
5      test: ["CMD-SHELL", "wget -qO- http://localhost:${APP_PORT}/health || exit 1"]

If APP_PORT=8080 is present in your shell or .env file, Compose resolves the command text before sending the configuration to Docker.

This is useful when the value belongs to deployment configuration rather than runtime state inside the container.

Using Container Environment Variables

Sometimes the value you want comes from the container environment itself, not from Compose-time interpolation. In that case, escape the dollar sign so Compose leaves it alone and the shell inside the container expands it later.

yaml
1services:
2  db:
3    image: postgres:16
4    environment:
5      POSTGRES_USER: appuser
6      POSTGRES_DB: appdb
7    healthcheck:
8      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
9      interval: 10s
10      timeout: 5s
11      retries: 5

The double dollar sign $$ is the critical detail. Compose turns $$ into a literal $, so the container shell eventually sees ${POSTGRES_USER} and expands it at runtime.

Why CMD-SHELL Matters

If you want shell variable expansion, you need a shell form healthcheck. In Compose, that means either:

  • a string form healthcheck
  • '["CMD-SHELL", "..."]'

For example:

yaml
healthcheck:
  test: "curl -f http://localhost:$${PORT}/health || exit 1"

or:

yaml
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:$${PORT}/health || exit 1"]

If you use plain CMD form without a shell, normal shell expansion does not happen.

A Full Practical Example

Here is a service that reads its internal port from the container environment and uses it in the healthcheck:

yaml
1services:
2  api:
3    image: my-api:latest
4    environment:
5      APP_PORT: "9000"
6    healthcheck:
7      test: ["CMD-SHELL", "curl -fsS http://localhost:$${APP_PORT}/ready || exit 1"]
8      interval: 15s
9      timeout: 3s
10      retries: 4
11      start_period: 20s

That will work as long as curl exists in the container image. If the image is minimal, you may need wget, nc, or a custom probe command instead.

Debugging What Compose Sees

If variable behavior looks wrong, inspect the resolved configuration:

bash
docker compose config

This command shows what Compose interpolated before container startup. It is the fastest way to determine whether:

  • Compose already replaced the variable
  • a variable became an empty string
  • your escaped dollar signs survived as intended

If the generated config shows the wrong command, the issue is in YAML interpolation. If the config looks correct but the probe still fails, the issue is probably inside the container runtime environment.

Choosing the Right Source of Truth

Use Compose interpolation when the healthcheck should depend on deployment configuration known outside the container, such as a port number chosen in .env. Use $$ plus CMD-SHELL when the command should read variables from the container’s own environment.

Keeping those two cases separate prevents the most common configuration bugs.

Image Constraints Matter

A healthcheck command runs inside the container. That means the probe tool must exist in the image. Minimal images often do not include curl.

For example, this can fail even if the variable expansion is correct:

yaml
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:$${PORT}/health || exit 1"]

If curl is missing, the container becomes unhealthy for the wrong reason. Always verify the toolchain available inside the image.

Common Pitfalls

The biggest mistake is forgetting that Compose and the container shell expand variables at different times. Another common problem is using ${VAR} when you really meant $${VAR}, which causes Compose to consume the variable too early. Developers also often use CMD form and expect shell expansion that never happens. Finally, many healthchecks fail because the image does not actually contain curl, wget, or the database client used in the probe.

Summary

  • Compose can interpolate variables in healthcheck definitions before containers start.
  • Use $${VAR} when the variable should be expanded by the shell inside the container.
  • Use CMD-SHELL or string form if shell expansion is required.
  • Validate the resolved YAML with docker compose config.
  • Make sure the probe command exists inside the container image.

Course illustration
Course illustration

All Rights Reserved.