Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

When running a service such as Redis inside a Docker container, we may assume that once the container is up, the service inside it is healthy, which isn’t always the case. The container may be running, but the Redis server inside could be broken. For instance, it may be unresponsive to commands or unable to write to the filesystem.

Caching, queuing, and session storage are a few tasks that Redis normally takes care of. Therefore, Redis needs to be healthy and responsive, particularly for production environments.

In this tutorial, we’ll explore how to perform a health check for Redis with the help of built-in tools available inside the official image, in this case, redis:6-alpine. To that end, we’ll use a small project, discuss common pitfalls, and how to troubleshoot them.

2. Example Subset

To begin with, we initialize a project to run Redis using Docker Compose.

First, let’s create the project directory and navigate into it:

$ mkdir redis-healthcheck-demo && cd redis-healthcheck-demo

Next, we create the docker-compose.yml file and add the content:

$ cat docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:6-alpine
    container_name: redis_server
    ports:
      - "6379:6379"

After that, let’s start the container:

$ docker-compose up -d
Creating network "redis-healthcheck-demo_default" with the default driver
Pulling redis (redis:6-alpine)...
...
Creating redis_server ... done

Next, we modify docker-compose.yml to include a health check.

3. Introduce a Basic Internal Health Check

So, let’s add a simple health check that utilizes redis-cli, a built-in command present in the Redis image:

$ cat docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

With this modification, the configuration file now instructs Docker to run redis-cli ping every 10 seconds. If, for 3 consecutive attempts, Docker fails to receive PONG within 5 seconds, it marks the container as unhealthy.

4. Verifying the Health Status

To verify the health status, let’s begin by starting the container:

$ docker-compose up -d
Creating network "redis-healthcheck-demo_default" with the default driver
Pulling redis (redis:6-alpine)...
...
Creating redis_server ... done

Now, we check the health of the container:

$ docker inspect --format='{{json .State.Health}}' $(docker-compose ps -q redis) | jq
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2025-04-30T18:36:58.834324106Z",
      "End": "2025-04-30T18:36:58.913174172Z",
      "ExitCode": 0,
      "Output": "PONG\n"
    },
    ...
    {
      "Start": "2025-04-30T18:37:39.154847298Z",
      "End": "2025-04-30T18:37:39.208100459Z",
      "ExitCode": 0,
      "Output": "PONG\n"
    }
  ]
}

The output above shows the container is healthy. So, the command docker-compose ps -q redis fetches the container ID for the Redis service in the docker-compose file. Meanwhile, docker inspect –format='{{json .State.Health}}’ <container-id> inspects the health status of the container:

  • –format='{{json .State.Health}}’ instructs Docker to only return the health section of the container’s state in JSON format
  • .State.Health represents the part of the Docker container’s metadata that holds health check information

Finally, | jq makes the output easier to read and navigate.

5. Using a Custom Redis Health Check Script

In the root directory, let’s first create the healthcheck.sh file and make it executable:

$ touch healthcheck.sh && chmod +x healthcheck.sh

Thereafter, we can open and populate healthcheck.sh:

$ cat healthcheck.sh
#!/bin/sh

# Attempt to ping Redis
response=$(redis-cli ping)

# Check the response
if [ "$response" != "PONG" ]; then
  echo "Health check failed: $response"
  exit 1
else
  echo "Redis responded: $response"
  exit 0
fi

In this script, we see several checks and responses:

  • response=$(redis-cli ping) executes the command redis-cli ping and stores its output in the variable response
  • if [ $response != “PONG”]; then checks if the response is not exactly PONG, indicating that Redis is down, unreachable, or returns an error message, with the condition becoming true
  • echo “Health check failed: $response” prints a message to indicate the health check failed and shows the actual response
  • exit 1 exits the script with a non-zero status (1) to signal failure in shell scripts, marking the container as unhealthy in a container environment
  • else runs when the response is PONG
  • echo “Redis responded: $response” prints a message confirming that Redis is healthy
  • exit 0 exits the script with status 0, signaling success, indicating Redis is healthy

Next, let’s update the existing docker-compose.yml file:

$ cat docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    volumes:
      - ./healthcheck.sh:/usr/local/bin/healthcheck.sh
    healthcheck:
      test: ["CMD-SHELL", "/usr/local/bin/healthcheck.sh"]
      interval: 10s
      timeout: 5s
      retries: 3

With this modification, we mount the script /usr/local/bin/healthcheck.sh inside the container and configure the health check to use it.

Further, let’s restart the Redis container:

$ docker-compose down && docker-compose up -d --build

Finally, we verify the health check works:

$ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS                    PORTS                    NAMES
6ae36dd7b2a7   redis:6-alpine   "docker-entrypoint.s…"   14 seconds ago   Up 12 seconds (healthy)   0.0.0.0:6379->6379/tcp   redis-healthcheck-demo_redis_1

In this output, we see that the status of the container is indicated as healthy. For detailed health logs, we can use a more complex command:

$ docker inspect --format='{{json .State.Health}}' $(docker-compose ps -q redis) | jq
{
  "Status": "healthy",
  "FailingStreak": 0,
  "Log": [
    {
      "Start": "2025-04-30T22:33:15.395253942Z",
      "End": "2025-04-30T22:33:15.450729833Z",
      "ExitCode": 0,
      "Output": "Redis responded: PONG\n"
    }
  ]
}

Now, let’s simulate a Redis failure.

6. Troubleshooting the Common ContainerConfig Error

After adding the health check and starting the container, it’s not uncommon to encounter an error:

ERROR: for redis_server  'ContainerConfig'

...
KeyError: 'ContainerConfig'

The error usually happens because of a corrupt and incomplete Redis image cached locally.

To resolve this, we first need to stop the container:

$ docker-compose down

After this, let’s remove the broken image:

$ docker rmi redis:6-alpine

Finally, we can rebuild the image and re-run the container:

$ docker-compose up -d --build

To resolve this issue, Docker pulls a clean image.

7. Simulating Redis Failure

To ensure the health check works correctly, let’s simulate a Redis failure without stopping the container. For example, directly executing pkill redis-server terminates PID 1, stopping the container entirely.

Instead, let’s override the Redis entry point with a custom script to run Redis as a child process.

To that end, let’s create the script redis-entrypoint.sh:

$ cat redis-entrypoint.sh
#!/bin/sh
redis-server &
echo $! > /tmp/redis.pid
tail -f /dev/null

Next, we make the script executable:

$ chmod +x redis-entrypoint.sh

Once the script is executable, let’s update the docker-compose.yml file to utilize the script:

services:
  redis:
    image: redis:6-alpine
    ports:
      - "6379:6379"
    volumes:
      - ./healthcheck.sh:/usr/local/bin/healthcheck.sh
      - ./redis-entrypoint.sh:/usr/local/bin/redis-entrypoint.sh
    entrypoint: ["/bin/sh", "/usr/local/bin/redis-entrypoint.sh"]
    healthcheck:
      test: ["CMD-SHELL", "/usr/local/bin/healthcheck.sh"]
      interval: 10s
      timeout: 5s
      retries: 3

Thereafter, we rebuild and restart:

$ docker-compose down && docker-compose up -d --build

Now, let’s kill Redis to simulate failure:

$ docker exec -it redis-healthcheck-demo_redis_1 sh -c 'kill $(cat /tmp/redis.pid)'

Finally, we wait for a short while, and then check the status of the container:

$ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS                      PORTS                    NAMES
e48b39deac17   redis:6-alpine   "/bin/sh /usr/local/…"   32 seconds ago   Up 30 seconds (unhealthy)   0.0.0.0:6379->6379/tcp   redis-healthcheck-demo_redis_1

From the output, we see an unhealthy status, confirming that the health check works as expected.

8. Conclusion

In this article, we walked through setting up Redis in Docker, implementing a health check, troubleshooting Docker image issues, and simulating Redis failure to validate the health check setup.

In summary, even though a container is running, Redis may fail silently inside the container. For this reason, we need to configure a proper health check using built-in tools like redis-cli or custom scripts to add a powerful safeguard against such silent failures.

Now, we should be able to check the health of a Redis server more easily in a Docker image.