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_onfor container start order,wait-for-itfor service readiness - Set appropriate timeouts based on your slowest dependency
- Use
--strictmode 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.
Be First to Comment