1. Overview

docker-compose is a popular tool for defining and running multi-container applications.

In this tutorial, we’ll focus on how to use docker-compose to check whether a container is running.

2. Setup

In this section, we’ll build a sample project that uses a docker-compose.yml file for containerization.

First, let’s use the exa command to look at the project’s directory structure in a tree format:

$ exa --tree .
.
├── backend
│  ├── app1
│  │  ├── app1.sh
│  │  └── Dockerfile
│  └── app2
│     ├── app2.sh
│     └── Dockerfile
└── docker-compose.yml

Next, let’s write two scripts, namely, app1.sh and app2.sh, representing two different demo applications:

$ tail -n +1  backend/app1/app1.sh backend/app2/app2.sh
==> backend/app1/app1.sh <==
#!/bin/bash
while :
do
    echo "app1 is running ..."
    sleep 60
done

==> backend/app2/app2.sh <==
#!/bin/bash
while :
do
    echo "app2 is running ..."
    sleep 60
done

Now, we’ll use Ubuntu’s base image for writing the Dockerfile for these two applications:

$ tail -n +1  backend/app{1,2}/Dockerfile
==> backend/app1/Dockerfile <==
FROM ubuntu
COPY --chmod=700 app1.sh app1.sh
ENTRYPOINT /app1.sh

==> backend/app2/Dockerfile <==
FROM ubuntu
COPY --chmod=700 app2.sh app2.sh
ENTRYPOINT /app2.sh

Moving on, let’s see the docker-compose.yml to orchestrate the containerization of these two services:

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

services:
  app1:
    build: backend/app1
    container_name: demo_app1_container
  app2:
    build: backend/app2
    container_name: demo_app2_container

Finally, let’s use the docker-compose build command to build the services:

$ docker-compose build --no-cache

Let’s note that we’ll reuse this demo project to simulate different scenarios in this tutorial.

3. Using docker-compose ps

Let’s start by spawning up the services using the docker-compose up command:

$ docker-compose up -d
[+] Running 3/3
 ⠿ Network lnx-1370_default       Created                                                                                                                                                              0.0s
 ⠿ Container demo_app2_container  Started                                                                                                                                                              0.4s
 ⠿ Container demo_app1_container  Started                                                                                                                                                              0.4s

On execution, the containers are running in detached mode. So, we don’t have any visibility into the current state of the services.

Next, let’s see how we can use the docker-compose ps command to list all the containers:

$ docker-compose ps --all
NAME                  COMMAND                 SERVICE             STATUS              PORTS
demo_app1_container   "/bin/sh -c /app1.sh"   app1                running
demo_app2_container   "/bin/sh -c /app2.sh"   app2                running

We must note that the output contains the STATUS column denoting the current status of the container.

Finally, let’s also see how we can use the –status filter to show the running containers for the app1 service:

$ docker-compose ps --status=running app1
NAME                  COMMAND                 SERVICE             STATUS              PORTS
demo_app1_container   "/bin/sh -c /app1.sh"   app1                running

Great! With this, we can selectively see the running containers for a given service.

4. Using docker-compose exec

In this section, we’ll see how to use the docker-compose exec command to check whether a service has a running container.

First, let’s simulate a scenario where one of the services is down by manually stopping the app2 service:

$ docker-compose stop app2
[+] Running 1/1
 ⠿ Container demo_app2_container  Stopped                                                                                                                                                             10.2s

Next, let’s use the docker-compose exec command to execute a trivial echo command inside the container for service app1:

$ docker-compose exec app1 echo "app1 is up"
app1 is up

We can see that the service app1 is still running, so the echo command was completed successfully.

Finally, let’s also try to execute the echo command inside the container for service app2:

$ docker-compose exec app2 echo "app2 is up"
service "app2" is not running container #1
$ echo $?
1

As expected, the echo command couldn’t execute because there were no running containers for service app2. Additionally, the exit status of the docker-compose exec command is non-zero.

5. Using healthcheck in docker-compose.yml

Using the docker-compose ps and docker-compose exec commands, we can get to know the current status of the container. However, it doesn’t guarantee that the target application running inside the container is in a healthy state. In this section, we’ll learn to solve this well-known problem by adding a healthcheck for the target application within the docker-compose.yml file.

First, let’s determine a valid healthcheck for our demo application by checking if an active process is associated with the app1.sh script:

$ ps -elf | grep 'app1.sh' | grep -v grep || exit 1

It’s important to exclude the process running grep itself from the output using the grep -v command in the pipeline. Further, we’re exiting with a non-zero status if we fail to find any active process for app1.sh.

Now, let’s define this healthcheck for the app1 service in the docker-compose.yml file:

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

services:
  app1:
    build: backend/app1
    container_name: demo_app1_container
    healthcheck:
      test: ["CMD-SHELL", "ps -elf | grep 'app1.sh' | grep -v grep || exit 1"]
  app2:
    build: backend/app2
    container_name: demo_app2_container

We must note that the CMD-SHELL will execute the healthcheck command using the /bin/sh shell.

Next, let’s shutdown the services and bring them up again using the docker-compose down and docker-compose up commands, respectively:

$ docker-compose down
[+] Running 3/2
 ⠿ Container demo_app1_container  Removed   ...   10.2s
 ⠿ Container demo_app2_container  Removed   ...   10.2s
 ⠿ Network lnx-1370_default       Removed   ...    0.0s
$ docker-compose up -d
[+] Running 3/3
 ⠿ Network lnx-1370_default       Created   ...    0.0s
 ⠿ Container demo_app1_container  Started   ...    0.4s
 ⠿ Container demo_app2_container  Started   ...    0.3s

Finally, let’s use the docker-compose ps commands to check the status of the services:

$ docker-compose ps
NAME                  COMMAND                 SERVICE             STATUS              PORTS
demo_app1_container   "/bin/sh -c /app1.sh"   app1                running (healthy)
demo_app2_container   "/bin/sh -c /app2.sh"   app2                running

Although the STATUS column for service app1 also shows the health status. However, this output format can change depending on the docker-compose version. So, let’s learn a machine-friendly approach to get the health status in the next section.

6. Using docker inspect

When we know the container ID of the target service, we can use the docker inspect command to get the status of the container’s current state.

First, let’s see how we can use the -q option with the docker-compose ps command to retrieve the container ID for the app1 and app2 services:

$ docker-compose ps -q app1
04fa0e3dffd6e7119c8be6194a59bd5fef2b9598d096331357a4c51dfd010831
$ docker-compose ps -q app2
f88fdf249b31483bd8cd5353a6d11d2ff3f5a0f0c2756cc805e4c67246ee91b5

Next, let’s use the docker inspect command to get the status of the app1 service:

$ docker inspect --format "{{.State.Status}}" 04fa0e3dffd6e7119c8be6194a59bd5fef2b9598d096331357a4c51dfd010831
running
$ docker inspect --format "{{.State.Health.Status}}" 04fa0e3dffd6e7119c8be6194a59bd5fef2b9598d096331357a4c51dfd010831
healthy

Great! This approach successfully retrieves the container’s current state and health status for app1.

Finally, let’s see if we can use the same approach to get the status of the app2 service:

$ docker inspect --format "{{.State.Status}}" f88fdf249b31483bd8cd5353a6d11d2ff3f5a0f0c2756cc805e4c67246ee91b5
running
$ docker inspect --format "{{.State.Health.Status}}" f88fdf249b31483bd8cd5353a6d11d2ff3f5a0f0c2756cc805e4c67246ee91b5
Template parsing error: template: :1:8: executing "" at <.State.Health.Status>: map has no entry for key "Health"
$ echo $?
1

Unfortunately, the attempt to get the health status for the app2 service fails with a non-zero exit status because we haven’t defined its healthcheck.

7. Conclusion

In this tutorial, we learned to use the docker-compose command to check whether a container is running. Additionally, we learned how to make such checks more effective by adding a healthcheck for the services.