How to Use the depends_on Setting in Docker Compose

Posted on  Dec 14, 2023  in  Docker  by  Amo Chen  ‐ 6 min read

When setting up a development environment using Docker Compose, you might encounter service dependency issues. Take a simple example: the database container usually needs to start up first before the API server container can follow. This is because the API server might need to establish a connection pool with the database before it can offer API services; otherwise, errors could occur and cause the container to fail to start.

In this situation, the API server container relies on the database container to function properly, which is what we refer to as service dependency. Without understanding service dependencies, you could frequently experience inconsistent results with docker-compose, sometimes successful and sometimes not, as services might or might not start up in time or be ready.

To address this, Docker Compose provides a solution with the depends_on setting.

This article will guide you on how to use the depends_on setting.

Environment for this Article

Project Structure Example in this Article

Here’s the project structure for the example used in this article. The Dockerfile is used to build a container for running app.py, and compose.yaml is the Docker Compose file to set up the development environment:

.
├── Dockerfile
├── app.py
└── compose.yaml

We’re using Python Flask to set up a simple web server as a simulation. Don’t worry if you’re not familiar with Python; it won’t affect the tutorial. Below is the content of app.py, which connects to Redis to increment the hits key value each time you GET / on app.py.

app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hit count: {}\n'.format(count)

Below is the content of the Dockerfile.

Dockerfile

FROM python:3.8-alpine
WORKDIR /app
ENV FLASK_APP=app.py
RUN apk add --no-cache gcc musl-dev linux-headers
RUN pip install flask redis
EXPOSE 5000
COPY . .
CMD ["flask", "run", "--host", "0.0.0.0"]

Below is the content of compose.yaml, showing the development environment that needs to run Redis and our compiled web server.

compose.yaml

services:
  web:
    build: .
    ports:
      - "8000:5000"
  redis:
    image: "redis:alpine"

Explanation of depends_on

As mentioned earlier, depends_on is a setting to solve service dependencies.

Short Syntax

In the example from this article, the web container depends on the redis container, which can be represented with the arrow relationship web → redis. Therefore, you need to add the depends_on setting to the web container to clearly specify that it needs to wait for the redis container to start.

Thus, you can modify the compose.yaml like this:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      - redis
  redis:
    image: "redis:alpine"

If there are multiple dependencies, you can refer to the following example:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      - container_1
      - container_2
  container_1:
    image: "redis:alpine"
  container_2:
    image: "redis:alpine"

The previous depends_on setting just lists the names of dependent containers, which is called short syntax 1. It only indicates the order of startup, so the redis container will start before the web container.

Although the above setting starts the redis container before the web container, there may still be issues under some circumstances:

  1. The redis container takes longer to start running than expected.
  2. The redis container fails to function properly after starting.

These situations will cause the web container to fail to start because the redis container isn’t ready to provide services when the web container attempts to connect.

This can be made clearer with a timeline:

timeline.png

To address time gaps or improper container operations, you can switch to long syntax to specify more explicitly when a dependent container should be ready before starting another.

Long Syntax

depends_on in long syntax allows you to explicitly specify conditions that a dependent container must meet.

Condition

In this example, we need the redis container to be fully operational before starting the web container. We can use the condition setting to specify that we should wait until the redis is in a service_healthy state before starting web. Thus, you can further modify compose.yaml like this:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      redis:
        condition: service_healthy
  redis:
    image: "redis:alpine"

Three values can be specified for the condition setting:

  • service_started: similar to short syntax; it passes the condition simply if the dependent container has started.
  • service_completed_successfully: waits for the dependent container to finish execution successfully (exit code must be 0). This is generally less common.
  • service_healthy: waits for the dependent container to be in a healthy state. This requires containers with a specified HEALTHCHECK either during the build phase or explicitly in the docker-compose file; otherwise, errors like dependency failed to start might occur.

Since the official Docker image of Redis doesn’t include a HEALTHCHECK, you’ll need to add a healthcheck in compose.yaml:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      redis:
        condition: service_healthy
  redis:
    image: "redis:alpine"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      timeout: 10s
      retries: 3
      start_period: 10s

Here’s an explanation of the healthcheck block:

  1. test: ["CMD", "redis-cli", "ping"]: the command to check the health status. CMD indicates executing a command. Redis’s redis-cli ping is handy to verify if the service is operational.
  2. start_period: 10s: wait 10 seconds after the container starts before running the check command.
  3. timeout: 10s: if the check exceeds 10 seconds, it’s considered unhealthy, assuming the service should respond within 10 seconds.
  4. retries: 3: retries up to 3 times if unhealthy. A single successful response ends the checks, marking it healthy; if all 3 retries fail, the container is considered unhealthy.

You can test the above Docker Compose file with the command below, and you’ll notice that the web container waits about 10 seconds after the redis container starts:

$ docker compose up --build

For more information on HEALTHCHECK, see the documentation.

Required

Besides condition, there is also required, which you can use to specify if a container is optional with required: false.

Initially, the redis container needs to be healthy to start the web container:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      redis:
        condition: service_healthy
  redis:
    image: "redis:alpine"

But if the redis container lacks a healthcheck, you may encounter a dependency failed to start error, ultimately preventing the web container from starting.

If the state of redis doesn’t matter for the web container, use required: false. This allows the web container to start even if redis doesn’t start correctly:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      redis:
        condition: service_healthy
        required: false
  redis:
    image: "redis:alpine"

You can execute the above docker-compose file with:

$ docker compose up --build
Restart

Along with condition and required, there’s also restart, which requires a condition.

The restart setting ensures that if a dependent container restarts or is updated, the containers depending on it also restart.

For example:

services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      redis:
        condition: service_started
        restart: true
  redis:
    image: "redis:alpine"

If redis is set with restart: true, when you restart it with:

$ docker compose restart redis

You’ll notice the web container restarts as well:

 Restarting 2/2
 ✔ Container myrepo-redis-1  Started
 ✔ Container myrepo-web-1    Started

Conclusion

The depends_on setting is crucial in a docker-compose file as modern development environments often require interactions among multiple containers, such as typical Web API servers and databases or cache servers. These dependencies might lead docker-compose not to behave as expected since some containers need others to function properly.

Learning to use depends_on helps resolve dependency issues and results in a more robust docker-compose file, creating a more stable development environment.

That’s all!

Enjoy!

References