The Problem: Services Starting Before Their Dependencies

You deploy a Docker Compose stack with a web app and a database. The app container starts immediately, tries to connect to the database, fails because the database isn’t ready yet, and crashes. Docker restarts it. It crashes again. This loop continues until—maybe—the database finishes initializing and the connection finally works.

This isn’t just annoying. It causes:

  • Crash loops that fill your logs with connection errors
  • Unpredictable startup times—sometimes everything works, sometimes it doesn’t
  • Failed health checks that trigger false alerts in your monitoring
  • Difficult debugging when the root cause is hidden in race conditions

The solution is to make containers wait for their dependencies to be ready before starting. That’s exactly what wait-for-it does.

What Is wait-for-it?

wait-for-it is a tiny pure-bash script that waits for a TCP host and port to become available before proceeding. It has no dependencies—you just drop it into your container and use it in your command.

Think of it as a gatekeeper. Your container starts, but instead of immediately launching your application, it first runs wait-for-it to verify the database (or API, or cache, or any TCP service) is accepting connections. Only then does your app start.

This approach is especially useful in Docker Compose environments where depends_on only guarantees container start order—not service readiness.

Prerequisites

To follow this guide, you’ll need:

  • A server running Docker and Docker Compose
  • Basic familiarity with Docker Compose YAML syntax
  • A service you want to wait for (database, API, cache, etc.)

Hosting recommendation: For self-hosted Docker stacks, we recommend a Cloud VPS with at least 2 vCPU and 4GB RAM. Canadian Web Hosting offers Canadian data centres in Vancouver and Toronto with full root access—perfect for Docker workloads.

How to Add wait-for-it to Your Docker Compose Stack

Step 1: Download the Script

The script is a single file available on GitHub. Download it directly:

curl -o wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
chmod +x wait-for-it.sh

For Alpine-based images, you may need to install bash first:

# In your Dockerfile
RUN apk add --no-cache bash
COPY wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh

Step 2: Update Your Docker Compose File

Here’s a typical scenario: a web application that needs PostgreSQL to be ready before starting.

Before (problematic):

services:
  app:
    image: myapp:latest
    depends_on:
      - db
    command: python app.py
    # App starts immediately after db container starts
    # But PostgreSQL isn't ready to accept connections yet

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secretpassword

After (with wait-for-it):

services:
  app:
    image: myapp:latest
    depends_on:
      - db
    volumes:
      - ./wait-for-it.sh:/wait-for-it.sh:ro
    command: >
      bash -c "/wait-for-it.sh db:5432 --timeout=30 -- python app.py"

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secretpassword

Now the app container will wait up to 30 seconds for PostgreSQL on port 5432 to accept connections before running python app.py.

Step 3: Understand the Command Syntax

The basic syntax is:

wait-for-it.sh host:port [options] -- command
Option Description Example
-h HOST or --host=HOST Host or IP to test -h db
-p PORT or --port=PORT TCP port to test -p 5432
host:port Combined format (shorthand) db:5432
-t TIMEOUT Timeout in seconds (default: 15) -t 60
-s or --strict Only run command if test succeeds --strict
-q or --quiet Suppress status messages --quiet
-- COMMAND Command to run after wait -- python app.py

Step 4: Test Your Configuration

Start your stack and observe the logs:

docker compose up -d
docker compose logs -f app

You should see output like:

wait-for-it.sh: waiting 30 seconds for db:5432
wait-for-it.sh: db:5432 is available after 3 seconds
# Your app starts here...

If the dependency isn’t available within the timeout, you’ll see:

wait-for-it.sh: timeout occurred after waiting 30 seconds for db:5432

Waiting for Multiple Dependencies

What if your app needs both a database and a Redis cache? Chain the waits:

services:
  app:
    image: myapp:latest
    depends_on:
      - db
      - redis
    volumes:
      - ./wait-for-it.sh:/wait-for-it.sh:ro
    command: >
      bash -c "/wait-for-it.sh db:5432 -t 30 &&
               /wait-for-it.sh redis:6379 -t 30 &&
               python app.py"

  db:
    image: postgres:16

  redis:
    image: redis:7

The && ensures that if the first wait fails, subsequent commands won’t run.

For a real-world example of a multi-container stack with dependencies, see our guide on setting up Graylog for production — it uses Docker Compose with Elasticsearch, MongoDB, and the Graylog server.

Common Use Cases

Service Default Port wait-for-it Command
PostgreSQL 5432 wait-for-it.sh db:5432
MySQL / MariaDB 3306 wait-for-it.sh mysql:3306
Redis 6379 wait-for-it.sh redis:6379
Elasticsearch 9200 wait-for-it.sh elasticsearch:9200
RabbitMQ 5672 wait-for-it.sh rabbitmq:5672
Memcached 11211 wait-for-it.sh memcached:11211
Any HTTP API Varies wait-for-it.sh api:8080

When to Use wait-for-it vs Alternatives

wait-for-it is simple and effective, but it’s not the only option. Here’s when to choose each approach:

Approach Best For Trade-offs
wait-for-it Simple TCP port checks; quick implementation Only tests TCP connectivity, not application readiness
dockerize More complex wait conditions; template generation Additional dependency; more features to learn
depends_on with healthcheck Native Docker solution; no extra scripts Requires modifying dependency images to add healthcheck
Application-level retry Production-grade reliability Requires code changes; most robust long-term

Our recommendation: Start with wait-for-it for quick fixes. For production systems, consider adding healthchecks to your Docker Compose file or implementing retry logic in your application code.

Production Considerations

Timeout Values

Set realistic timeouts based on your slowest dependency:

  • Redis: 5-10 seconds is usually sufficient
  • PostgreSQL: 15-30 seconds for initialization
  • Elasticsearch: 30-60 seconds on first run with data

Setting -t 0 disables the timeout entirely—use with caution, as your container could wait forever.

Strict Mode

Use --strict to prevent your application from starting if the dependency isn’t available:

command: >
  bash -c "/wait-for-it.sh db:5432 -t 30 --strict -- python app.py"

In strict mode, if the wait times out, the script exits with an error and your application won’t start. This prevents partially-functional containers from running.

Logging

In production, you might want quieter output. Use --quiet to suppress the wait messages:

command: >
  bash -c "/wait-for-it.sh db:5432 -t 30 --quiet -- python app.py"

Troubleshooting

“command not found” Error

Symptom: /bin/bash: /wait-for-it.sh: No such file or directory

Cause: The script isn’t in the container or isn’t executable.

Fix: Either mount it via volumes (as shown above) or COPY it in your Dockerfile:

COPY wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh

“bash: not found” Error on Alpine

Symptom: /bin/sh: bash: not found

Cause: Alpine Linux uses ash by default, but wait-for-it requires bash.

Fix: Install bash in your Dockerfile:

RUN apk add --no-cache bash

Wait Times Out But Service Is Running

Symptom: Timeout occurs even though the database container is running.

Cause: Network connectivity issue or wrong host/port.

Fix: Verify from inside the container:

docker compose exec app sh -c "nc -zv db 5432"

If this fails, check your Docker network configuration and ensure the service name matches the hostname you’re using.

Container Still Crashes After Wait

Symptom: wait-for-it succeeds, but the app still fails to connect.

Cause: The port is open but the application isn’t fully initialized.

Fix: This is a limitation of TCP-only checks. PostgreSQL accepting connections doesn’t mean the database is created and ready. Consider:

  • Increasing the timeout to give more initialization time
  • Adding a small sleep after the wait
  • Implementing application-level retry logic

For more complex crash scenarios, see our guide on why Docker containers keep restarting.

Related Approaches to Explore

If wait-for-it doesn’t meet your needs, consider these alternatives:

  • dockerize — Similar concept but with additional features like template generation and parallel waits
  • tini — An init system for containers that handles zombie processes and signal forwarding (complementary to wait-for-it)
  • Docker Compose healthchecks — Native Docker approach using depends_on: condition: service_healthy

Summary

Service startup order issues are a common source of container crashes and unpredictable behaviour. wait-for-it provides a simple, dependency-free solution: wait for TCP connectivity before starting your application.

Key takeaways:

  • Use depends_on for container start order, wait-for-it for service readiness
  • Set appropriate timeouts based on your slowest dependency
  • Use --strict mode to prevent partial failures
  • For production, consider healthchecks or application-level retry logic

For self-hosted Docker workloads, Canadian Web Hosting’s Cloud VPS provides Canadian data centres, full root access, and 24/7 support.