Don’t Let Your Containers Race: How to Make Services Wait for MySQL in Docker Compose

Don’t Let Your Containers Race: How to Make Services Wait for MySQL in Docker Compose
Photo by paolo candelo / Unsplash

In the world of Docker Compose, defining service dependencies is easy — just list them under depends_on. But if you’ve ever run a PHP app, Directus, or Ghost CMS that talks to MySQL, you might have hit this problem:

“My app container started fine... but immediately crashed with a MySQL connection error.”

Why? Because depends_on only waits for the container to start, not to be ready.

Let’s break this down — and solve it properly.


🧠 Why depends_on Isn’t Enough

Here’s what a lot of people do:

services:
  app:
    build: .
    depends_on:
      - mysql

Looks fine, right? But Docker Compose interprets this as:

“Start mysql before starting app.”

That’s all. It doesn’t care whether MySQL is done initializing, finished applying migrations, or even able to accept connections.

If your app hits the database too soon, it’s game over.


✅ Solution: Combine depends_on With healthcheck

The real solution is a two-part approach:

1. Add a healthcheck to your MySQL container:

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

This tells Docker, “Ping MySQL until it responds properly.”

2. Tell your app container to wait for a healthy MySQL:

  app:
    build: .
    depends_on:
      mysql:
        condition: service_healthy

Now, Docker Compose will wait until MySQL passes the healthcheck before launching the app container.


🛠 Bonus: Add wait-for-it.sh or dockerize for Extra Safety

Sometimes, even a healthcheck isn't enough. Your app might still try to connect before the DB is truly ready — especially in complex setups.

You can add a wrapper script like wait-for-it.sh or dockerize to your app container.

Example using wait-for-it.sh in your Dockerfile:

COPY wait-for-it.sh /usr/local/bin/
ENTRYPOINT ["wait-for-it.sh", "mysql:3306", "--", "npm", "start"]

This will pause your app startup until MySQL’s port is available.


⚠️ Things to Watch Out For

  • No built-in retries: Most apps will not retry a failed DB connection unless explicitly coded to do so.
  • Healthchecks are crucial for reliability, especially in CI or production setups.
  • Beware of silent failures. Even if the containers are marked as “up”, they might still be broken internally.

🧩 Consider Using Init Containers (in Kubernetes)

If you’re planning to scale into Kubernetes, similar logic applies — but you’ll use init containers or startupProbe instead of Compose’s depends_on.


✨ Finally

Don’t let your containers race each other to start. Just because Docker says something is “up” doesn’t mean it’s actually ready. That’s a critical distinction when working with databases like MySQL or Postgres.

By combining:

  • depends_on with condition: service_healthy
  • A proper healthcheck
  • And optionally, a startup wait script

…you can avoid those pesky “can’t connect to DB” errors and build a more reliable stack.

Support Us