Docker Compose — Wait Strategies

1 minute read

Beyond Docker health checks, you may need application-level wait strategies for dependencies that take time to initialize.

Strategy Comparison

Strategy Level Complexity Reliability
depends_on Container Low Low
condition: service_healthy Docker Medium High
wait-for-it / dockerize Script Medium High
Application retry logic App High Highest

wait-for-it Script

A popular shell script that waits for a TCP port:

services:
  api:
    image: myapi:latest
    command: >
      sh -c "
        /wait-for-it.sh db:5432 --timeout=60 --strict &&
        node server.js
      "
    depends_on:
      - db

dockerize

Similar approach with the dockerize utility:

services:
  api:
    image: myapi:latest
    command: >
      dockerize
        -wait tcp://db:5432
        -wait tcp://cache:6379
        -timeout 60s
        node server.js

Application-Level Retry

The most robust approach — build retry logic into your application:

Node.js with retry:

async function connectWithRetry(maxRetries = 10) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await db.connect();
      console.log('Database connected!');
      return;
    } catch (err) {
      console.log(`Retry ${i + 1}/${maxRetries}...`);
      await new Promise(r => setTimeout(r, 2000));
    }
  }
  throw new Error('Failed to connect after retries');
}

Python with tenacity:

from tenacity import retry, wait_exponential, stop_after_attempt

@retry(wait=wait_exponential(min=1, max=30), stop=stop_after_attempt(10))
def connect_db():
    return psycopg2.connect("host=db dbname=mydb")

Combine multiple strategies:

services:
  api:
    image: myapi:latest
    depends_on:
      db:
        condition: service_healthy    # Docker-level wait
    # Plus: application has retry logic built in

This gives you defense in depth — Docker handles the initial wait, and your app handles edge cases.

Init Containers Pattern

Use a separate service for one-time setup:

services:
  db-init:
    image: myapp:latest
    command: npm run db:migrate
    depends_on:
      db:
        condition: service_healthy

  api:
    image: myapp:latest
    depends_on:
      db-init:
        condition: service_completed_successfully

Summary

Strategy Best For
condition: service_healthy Most cases
wait-for-it Legacy images without healthcheck
App retry logic Production robustness
Init containers One-time setup tasks

Quick Reference

Item Detail
service_healthy Best Docker-level strategy
wait-for-it TCP port wait script
dockerize Multi-dependency TCP wait
App retry Most robust, defense in depth
Init container service_completed_successfully