What is the alternative to condition form of depends_on in docker-compose Version 3?

Solution 1:

There's been a move away from specifying container dependencies in compose. They're only valid at startup time and don't work when dependent containers are restarted at run time. Instead, each container should include mechanism to retry to reconnect to dependent services when the connection is dropped. Many libraries to connect to databases or REST API services have configurable built-in retries. I'd look into that. It is needed for production code anyway.

Solution 2:

From 1.27.0, 2.x and 3.x are merged with COMPOSE_SPEC schema.

version is now optional. So, you can just remove it and specify a condition as before:

services:
  web:
    build: .
    depends_on:
      redis:
        condition: service_healthy
  redis:
    image: redis
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 1s
      timeout: 3s
      retries: 30

Solution 3:

There are some external tools that let you mimic this behaviour. For example, with the dockerize tool you can wrap your CMD or ENTRYPOINT with dockerize -wait and that will prevent running your application until specified services are ready.

If your docker-compose file used to look like this:

version: '2.1'
services:
  kafka:
    image: spotify/kafka
    healthcheck:
      test: nc -z localhost 9092
  webapp:
     image: foo/bar # your image
     healthcheck:
       test: curl -f http://localhost:8080
  tests:
     image: bar/foo # your image
     command: YOUR_TEST_COMMAND
     depends_on:
       kafka:
         condition: service_healthy
       webapp:
         condition: service_healthy

then you can use dockerize in your v3 compose file like this:

version: '3.0'
services:
  kafka:
    image: spotify/kafka
  webapp:
     image: foo/bar # your image
  tests:
     image: bar/foo # your image
     command: dockerize -wait tcp://kafka:9092 -wait web://webapp:8080 YOUR_TEST_COMMAND

Solution 4:

Just thought I'd add my solution for when running postgres and an application via docker-compose where I need the application to wait for the init sql script to complete before starting.

dockerize seems to wait for the db port to be available (port 5432) which is the equivilant of depends_on which can be used in docker 3:

version: '3'

services:
  app:
    container_name: back-end
    depends_on:
      - postgres
  postgres:
    image: postgres:10-alpine
    container_name: postgres
    ports:
      - "5432:5432"
    volumes:
      - ./docker-init:/docker-entrypoint-initdb.d/

The Problem:

If you have a large init script the app will start before that completes as the depends_on only waits for the db port.

Although I do agree that the solution should be implemented in the application logic, the problem we have is only for when we want to run tests and prepopulate the database with test data so it made more sense to implement a solution outside the code as I tend not like introducing code "to make tests work"

The Solution:

Implement a healthcheck on the postgres container. For me that meant checking the command of pid 1 is postgres as it will be running a different command on pid 1 while the init db scripts are running

Write a script on the application side which will wait for postgres to become healthy. The script looks like this:

#!/bin/bash
function check {
  STATUS=\`curl -s --unix-socket /var/run/docker.sock http:/v1.24/containers/postgres/json | python -c 'import sys, json; print json.load('sys.stdin')["State"]["Health"]["Status"]'\`

  if [ "$STATUS" = "healthy" ]; then
    return 0
  fi
  return 1
}

until check; do
  echo "Waiting for postgres to be ready"
  sleep 5
done

echo "Postgres ready"

Then the docker-compose should mount the directories of the scripts so that we don't edit the Dockerfile for the application and if we're using a custom postgres image, this way we can continue to use the docker files for your published images.

We're also overriding the entry point defined in the docker file of the app so that we can run the wait script before the app starts

version: '3'

services:
  app:
    container_name: back-end
    entrypoint: ["/bin/sh","-c","/opt/app/wait/wait-for-postgres.sh && <YOUR_APP_START_SCRIPT>"]
    depends_on:
      - postgres
    volumes:
      - //var/run/docker.sock:/var/run/docker.sock
      - ./docker-scripts/wait-for-postgres:/opt/app/wait
  postgres:
    image: postgres:10-alpine
    container_name: postgres
    ports:
      - "5432:5432"
    volumes:
      - ./docker-init:/docker-entrypoint-initdb.d/
      - ./docker-scripts/postgres-healthcheck:/var/lib
    healthcheck:
      test: /var/lib/healthcheck.sh
      interval: 5s
      timeout: 5s
      retries: 10