Forget wait-for-it, use docker-compose healthcheck and depends_on instead

I use docker-compose a lot, especially for end-to-end (e2e) testing my Node apps. E2E tests tend to involve your app as well as a lot of dependencies, such as a database and perhaps a Redis instance or two.

A challenge that everyone tends to face when setting up these kind of e2e tests is how to run everything in a specific order. You want your Postgres DB and Redis cache to fully bootstrap first, then your app, and finally your test runner.

To do this, I used wait-for-it scripts heavily:

# docker-compose.e2e.yml

version: '3.8'
services:
  app:
		# ...
    entrypoint: /bin/sh
    command: -c "./wait-for-it.sh postgres:5432 && ./wait-for-it.sh redis:6379 -- node dist/main"
# ...

But, I can finally throw away all my wait-for-it scripts because I’ve found the best way to orchestrate my docker-compose dependencies: using healthcheck and depends_on.

This isn’t particularly new. healthcheck has been part of the version 2.1 Compose file format. It’s just that I haven’t witnessed it’s power alongside depends_on until now.

When I used depends_on without health checks in the past, I didn’t get much out of it, because although a dependency has started, it isn’t necessarily ready, and we don’t want our app to start without it being fully ready.

But when you pair it with healthcheck, you can truly have your app wait for your dependency to fully complete its bootstrap.

Here’s what a typical healthcheck for a Postgres DB looks like, where we use the pg_isready utility to check:

postgres:
  image: postgres:16
  container_name: postgres_e2e
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -d <dbname> -U <username>"]
    interval: 3s
    timeout: 10s

And for Redis, we use redis-cli to ping:

redis_cache:
  image: redis:7
  container_name: redis_cache_e2e
  healthcheck:
    test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
    interval: 3s

And finally, you can add the dependencies in your app’s depends_on along with condition:

app:
  # ...
  command: -c "node dist/main"
  depends_on:
    postgres:
      condition: service_healthy
    redis_cache:
      condition: service_healthy

Bonus: Localstack health checks

Setting up healthchecks for Localstack is a bit trickier, but you can use awslocal in the container to wait for a specific service to start, for example:

healthcheck:
  test:
    - CMD
    - bash
    - -c
    - awslocal dynamodb list-tables
      && awslocal es list-domain-names
      && awslocal s3 ls
      && awslocal sqs list-queues
  interval: 5s
  timeout: 10s
  start_period: 10s

Source: https://github.com/localstack/localstack/issues/1095#issuecomment-505368702