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