Home How to Wait for Dependencies in Docker Compose
Post
Cancel

How to Wait for Dependencies in Docker Compose

Every once in a while you may find yourself having to deal with services that have some hard dependencies on each other. Service B (frontend) needs to have service A (API) running before it starts. In most cases the best way to deal with this sort of thing is to just restart service B after a couple of probes for responsiveness. The service’s exit code can also be used for this purpose. Say, these solutions don’t work all that well for you and you really need to have your services wait for each other, here is something you can do.

Let’s work with a theoretical Django application. You need to run migrations run when the database service is ready. First let’s come up with a Dockerfile for our application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM python:3.10

RUN apt-get update -qq
# wget is required for probing HTTP and netcat for raw TCP
RUN apt-get install -y wget netcat

RUN curl -L -o /usr/local/bin/wait-for https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for
RUN chmod +x /usr/local/bin/wait-for

WORKDIR /opt/django_app/

COPY . /opt/django_app/

RUN pip install poetry
RUN poetry config virtualenvs.create false
RUN poetry install $(test "$ENV" == production && echo "--no-dev") --no-interaction --no-ansi

CMD ["python", "manage"]

In the Dockerfile above, we are installing a tool, wait-for, that polls an HTTP or TCP service and runs a command when the service is available (TCP) or responding with a 200 - OK (for HTTP). The tool depends on wget and netcat. If you are only interested in an HTTP service then you only need to install wget, else you need netcat. Next thing, let’s define our docker-compose.yml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
version: "3.9"

x-pg-env: &pg-env
  POSTGRES_USER: ${POSTGRES_USER:-iamgroot}
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-iamgroot}
  POSTGRES_DB: ${POSTGRES_DB:-groot}

services:
  app:
    build: .
    command: python manage.py runserver
    ports:
      - 8000:8000
    environment:
      <<: *pg-env
    depends_on:
      - database
    links:
      - database
    restart: always
  db-migrations:
    build: .
    command: sh -c 'wait-for database:5432 --timeout 10 -- python manage.py migrate'
    environment:
      <<: *pg-env
    depends_on:
      - database
    links:
      - database
    restart: on-failure
  database:
    image: postgres
    environment:
      <<: *pg-env
    ports:
      - 5432:5432
    restart: always

From the compose file above, zone in on the db-migrations service. We have defined a command that first of all tries to wait for postgres to start. It waits for 10 seconds and fails if not successful within that time. Otherwise, if a connection is made within that 10 seconds, the command python manage.py migrate is executed. The above is not a very good use case for this, because as you can see, if db-migrations fails then it will get executed again. I have however run into situations were I had no option but to do something like this (e.g. start a frontend service only when the backend service is running - frontend built with an implicit assumption that the backend will never be unavailable. It happens more often than you would think. When is the last time your frontend handled a network error not just a status code?).

Happy holidays!!!

This post is licensed under CC BY 4.0 by the author.