depends_on and Service Dependencies: Teaching Docker Who Goes First

depends_on and Service Dependencies: Teaching Docker Who Goes First

A complete, beginner-to-intermediate guide to how Docker understands the relationships between your application's services, what depends_on actually guarantees, and the surprisingly common ways this gets misunderstood.

Picture a stage play with several actors. One actor can’t deliver their line until another has finished setting a prop on the table, and a third can’t enter until the lights have been cued. A director doesn’t leave this to chance — there’s a script, and a clear understanding of who needs to be ready before whom. Docker’s multi-service applications face a strikingly similar challenge: a web server that talks to a database needs that database to exist before it tries to connect. The depends_on setting is Docker’s way of writing that script down. This guide walks through exactly what it does, what it genuinely guarantees, and where it stops — because that boundary trips up far more developers than you’d expect.

1What a Service Dependency Actually Means

Before the specific setting, let’s understand the relationship it’s describing.

When an application is made up of multiple services — as covered in detail elsewhere — those services are rarely fully independent of one another. A web service typically needs a database service to exist and be reachable before it can properly function. This kind of relationship, where one service genuinely relies on another being available, is called a service dependency. Declaring it explicitly, rather than leaving it to chance, is what depends_on is for.

Simple Analogy

Think of assembling flat-pack furniture from an instruction booklet. Step four might genuinely require step two to already be finished — you can’t attach a shelf to a frame that hasn’t been built yet. depends_on is Docker’s version of that instruction booklet, making the required order explicit rather than leaving someone to guess it and risk building things in the wrong sequence.

Without declaring dependencies, Docker has no inherent understanding of which services rely on which others — from its point of view, every service defined together is just as independent as any other, free to start in whatever order happens to be convenient, which is frequently not the order your application actually needs.

Dependency

A Genuine Reliance

One service needing another to exist, and often be properly ready, before it can function correctly.

depends_on

The Declaration

The setting used to explicitly tell Docker about a dependency relationship between two defined services.

Startup Order

What’s Being Controlled

The sequence in which services are told to start, influenced directly by declared dependencies.

Readiness

A Related but Separate Idea

Whether a service is actually capable of handling requests, which — as this guide explains at length — isn’t automatically the same thing as having merely started.

2What Happens Without Declared Dependencies

Understanding the problem this setting solves makes its purpose much clearer.

Without any dependency information, Docker generally starts every defined service at roughly the same time, in no particular guaranteed order relative to one another. For services with no real relationship, this is completely fine. But for a web service that immediately tries to connect to its database the moment it starts, this can easily mean the web service attempts that connection before the database has even begun starting up, resulting in a failed connection and, depending on how the web service handles that failure, potentially a crash right at startup.

sequenceDiagram
    participant Web as Web Service
    participant DB as Database Service

    Note over Web,DB: Both told to start at roughly the same time
    Web->>DB: Attempts connection immediately
    Note over DB: Still starting up, not ready yet
    DB-->>Web: Connection fails
        
FIG 1 — Without a declared dependency, a web service may attempt to connect before its database has even begun starting up.

This kind of failure is often intermittent and confusing, since the exact timing can vary from one startup to the next — sometimes the database happens to be ready in time purely by chance, and sometimes it isn’t, making the problem seem to appear and disappear randomly rather than consistently.

3What depends_on Actually Controls

A precise, careful description of exactly what this setting does.

Declaring that one service depends on another tells Docker to control the order in which their containers are created and started, ensuring the depended-upon service is instructed to start first. It also affects the reverse direction when stopping the application: services are generally stopped in the opposite order, so a service isn’t abruptly cut off from something it still depends on while it’s still in the process of shutting down.

ActionWhat depends_on Controls
Starting the applicationDepended-upon services are started before the services that rely on them
Stopping the applicationServices are generally stopped in roughly the reverse order

This is a genuinely useful guarantee about ordering. But it’s important to be precise about exactly what “started first” means here, because it’s narrower than many beginners initially assume — a distinction significant enough that the entire next chapter is dedicated to it.

This is the single most important, and most commonly misunderstood, aspect of dependency declarations.

By its most basic behavior, depends_on only guarantees that a depended-upon service’s container has been told to start before the dependent service’s container is started. It does not, on its own, guarantee that the depended-upon service has actually finished its own internal startup process and is genuinely ready to accept connections or requests. A database container can be “started,” in the sense that its process has begun running, while it’s still in the middle of its own internal setup — initializing its storage, preparing to accept connections — and not yet truly ready to be used.

!
Common Trap

This is one of the single most common points of confusion for developers new to declaring service dependencies. Declaring a dependency reduces the chance of a startup-order problem significantly, but it does not eliminate it entirely on its own, because “started” and “genuinely ready” are two different things, and the basic form of depends_on only guarantees the former.

flowchart TD
    A["Database container told to start"] --> B["Process begins running"]
    B --> C["Database performs internal setup"]
    C --> D["Database genuinely ready to accept connections"]
    E["Basic depends_on guarantees B happens before web service starts"] -.-> B
    style D fill:#fef2f2,stroke:#dc2626
        
FIG 2 — The basic form of depends_on only guarantees the process has begun running, not that the full internal setup has finished.

Fortunately, this gap has a solid solution, which is precisely the subject of the next chapter.

5Closing the Gap: Condition-Based Dependencies

A more precise way of declaring a dependency, going beyond simply “started.”

To address the gap described in Chapter 4, dependency declarations can be made more specific by attaching a condition describing exactly what “ready enough” means for that particular dependency, rather than settling for the basic, looser guarantee of merely having started.

Service Started

The Basic Condition

The depended-upon service’s container has been told to start — the default, looser guarantee discussed in Chapter 4.

Service Healthy

A Stronger Condition

The depended-upon service must be reporting a healthy status from its own defined health check before the dependent service is started.

Service Completed Successfully

For One-Off Tasks

The depended-upon service, typically a short-lived setup task, must have already finished running and exited successfully.

The “service healthy” condition, in particular, directly closes the gap described in Chapter 4 — but it depends entirely on that depended-upon service having a properly defined health check in the first place, since without one, there’s simply no meaningful signal available to wait for.

6Why Health Checks Are the Real Foundation Here

A meaningful dependency on readiness is only as good as the health check backing it up.

A health check, as covered elsewhere in the context of container states, is a small, repeated test that determines whether a running service is genuinely working correctly, not merely alive. Without one defined for a service, there’s no reliable, structured signal available describing when that service transitions from “just started” to “genuinely ready,” which is exactly why the “service healthy” condition described in Chapter 5 has a real prerequisite behind it.

i
Good to Know

Defining a proper health check for a service isn’t just useful for monitoring on its own — it becomes a genuine, load-bearing part of the application’s startup reliability once other services depend on that particular service being reported as healthy before they start.

This connection between health checks and dependency conditions is a good example of how different pieces of a well-designed application definition reinforce one another, rather than existing as separate, unrelated features.

7Dependency Graphs: More Than Just Two Services

Real applications often involve chains and branches of dependencies, not just a single pair.

Larger applications frequently involve several services with dependencies on one another, forming what’s effectively a small map, or graph, of relationships — a background worker might depend on both a database and a message queue, while a web service depends on that same database and perhaps a caching layer too. Docker examines every declared dependency across the whole application and works out a startup order that respects all of them simultaneously.

flowchart TD
    DB["Database"] --> Web["Web Service"]
    DB --> Worker["Background Worker"]
    Queue["Message Queue"] --> Worker
    Cache["Cache"] --> Web
        
FIG 3 — A dependency graph with several services; Docker determines a startup order respecting every declared relationship at once.

In a graph like this, the database and the message queue, having no dependencies of their own, would be started first, followed by the web service and the background worker once their own respective dependencies are satisfied, with the cache also starting early since nothing depends on it existing beforehand.

8Circular Dependencies: An Unsolvable Puzzle

One particular shape of dependency graph simply cannot be resolved.

A circular dependency occurs when a chain of dependencies loops back on itself — service A depends on service B, which in turn depends on service A. This creates a genuine logical impossibility: there’s no valid order in which both requirements can be satisfied simultaneously, since each one needs the other to go first.

flowchart LR
    A["Service A"] -->|"depends on"| B["Service B"]
    B -->|"depends on"| A
    style A fill:#fef2f2,stroke:#dc2626
    style B fill:#fef2f2,stroke:#dc2626
        
FIG 4 — A circular dependency has no valid resolution, since each service requires the other to start first.
!
Serious Design Issue

A circular dependency almost always signals a genuine design problem worth addressing directly, rather than something to work around. Two services that truly, mutually require each other to be fully ready before either can start suggests they may need to be restructured, or that the dependency should be expressed differently — for instance, having each retry its connection independently rather than blocking on a strict startup order at all.

9depends_on vs. Application-Level Retry Logic

Two complementary approaches to handling the same underlying uncertainty, each with a different role.

Even with a carefully declared, condition-based dependency, real-world timing can still occasionally be tighter than expected — a health check might report healthy just moments before a service actually becomes fully stable, or a brief network hiccup might cause an otherwise well-timed connection attempt to fail anyway. Many resilient applications handle this final layer of uncertainty with their own internal retry logic, attempting a failed connection again after a short pause rather than giving up or crashing immediately.

What depends_on Provides

  • Sensible, predictable startup ordering across the whole application
  • A strong, condition-based readiness signal when paired with health checks
  • Clear, declared documentation of real relationships between services

What It Doesn’t Fully Replace

  • Resilience to a dependency briefly becoming unavailable later, after startup
  • Protection against extremely tight timing edge cases at the very moment of startup
  • Handling for dependencies outside the application’s own defined services entirely

The most resilient applications treat these two approaches as complementary rather than as alternatives — depends_on handles the predictable, common-case ordering problem cleanly, while application-level retry logic provides a genuine safety net for the rarer, tighter edge cases that ordering alone can’t fully guarantee against.

10Dependencies and Scaling

How declared dependencies interact with a service running as several containers at once.

As explored in the context of defining services, a single service definition can be scaled to run as multiple identical containers. When a scaled service depends on another, the dependency relationship still applies consistently — the depended-upon service is still started (and, with the right condition, confirmed ready) before any of the dependent service’s multiple containers are started, regardless of how many of them there are.

Scaling a Web Service That Depends on a Database

A web service scaled to run five containers at once still waits on the same declared database dependency as it would with just one container — the database only needs to become ready a single time, and every one of the five web containers benefits from that same guarantee before starting.

11Design Patterns for Structuring Dependencies

A few recurring, sensible ways real applications organize their declared dependencies.

Foundational Services First

Databases, message queues, and other foundational infrastructure services are defined with no dependencies of their own, sitting naturally at the base of the dependency graph, as discussed in Chapter 7.

Setup Tasks as Completed Dependencies

A one-off task — such as preparing initial data — is defined as its own short-lived service, with dependent services using the “service completed successfully” condition from Chapter 5 to wait for that setup to genuinely finish before starting.

Health-Checked Critical Dependencies

Any dependency where “merely started” genuinely isn’t good enough — most commonly databases — is paired with a proper health check and the “service healthy” condition, closing the gap described in Chapters 4 through 6.

12Monitoring and Diagnosing Dependency-Related Startup Issues

What to look at when an application isn’t starting up the way you expect.

When a multi-service application fails to start correctly, checking the current state of each service — whether it’s running, exited, or stuck restarting, as discussed elsewhere regarding container states — combined with its exit code and logs is usually the fastest path to understanding what actually went wrong. Specifically for dependency-related problems, it’s worth checking whether a depended-upon service ever actually reached a healthy status at all, or whether it was still merely “started” when a dependent service gave up trying to connect.

Service State
Is the dependency actually running or exited?
Health Status
Did it ever report genuinely healthy?
Startup Logs
What did the dependent service report when it tried to connect?

A dependent service that keeps crash-looping shortly after startup, as discussed elsewhere, is a common symptom worth checking against exactly this kind of dependency timing problem, especially if the crashes cluster tightly around the very beginning of the application’s startup sequence.

13Best Practices and a Common Anti-Pattern

A short checklist for declaring dependencies that genuinely hold up in practice.

1

Declare every genuine dependency explicitly

Don’t rely on services happening to start in a convenient order by coincidence, as explained in Chapter 2.

2

Use the “service healthy” condition for anything that genuinely needs to be ready

Don’t settle for the basic “started” guarantee, as discussed in Chapter 4, when readiness truly matters.

3

Pair critical dependencies with a real health check

As covered in Chapter 6, the condition is only as reliable as the health check backing it.

4

Add application-level retry logic as a genuine safety net

As explained in Chapter 9, this handles the rare edge cases ordering alone can’t fully guarantee against.

ANTI-PATTERN-01 Avoid
Problem

A team declares that a web service depends on its database using only the basic, default condition, and assumes this fully guarantees the database will be ready to accept connections by the time the web service starts.

Why It’s Harmful

As explained in Chapter 4, this only guarantees the database’s container was told to start first — not that its own internal setup has actually finished. Under normal, everyday circumstances this might work fine most of the time, but intermittent, hard-to-reproduce connection failures at startup are a common and confusing result of relying on this weaker guarantee for a dependency that genuinely needs to be ready, not just started.

Correct Approach

Use the “service healthy” condition described in Chapter 5, paired with a properly defined health check as discussed in Chapter 6, for any dependency where the dependent service genuinely can’t function correctly until the other is truly ready.

14Real-World and Industry Examples

Declared service dependencies show up as a foundational, everyday practice across nearly every multi-service application.

Web Applications

Web Services Waiting on Databases

The most common real-world use, ensuring a web service never attempts to serve requests before its database is genuinely ready to accept connections.

Data Pipelines

Sequential Processing Stages

Each stage of a data pipeline declares a dependency on the previous stage having successfully completed, using the “service completed successfully” condition from Chapter 5.

Local Development Environments

Reliable One-Command Startup

Teams rely on well-declared dependencies so that starting an entire local development stack with a single action reliably produces a fully working environment, rather than requiring manual, ordered startup steps.

Message-Driven Systems

Workers Waiting on Queues

Background worker services declare dependencies on message queue services being genuinely healthy before attempting to begin consuming messages from them.

15Frequently Asked Questions

Q1Does depends_on guarantee my database is fully ready before my web service starts?

Not by default — as explained in Chapter 4, the basic condition only guarantees the database’s container was told to start first. Use the “service healthy” condition from Chapter 5 for a genuine readiness guarantee.

Q2What’s needed for the “service healthy” condition to actually work?

As covered in Chapter 6, the depended-upon service needs a properly defined health check — without one, there’s no meaningful signal to wait for.

Q3Can two services depend on each other?

No — as explained in Chapter 8, this creates a circular dependency with no valid resolution, and usually signals a design issue worth addressing directly.

Q4Does depends_on also control the order services are stopped in?

Yes — as noted in Chapter 3, services are generally stopped in roughly the reverse of their declared startup order.

Q5If I scale a service to multiple containers, does each one wait on the dependency separately?

The dependency only needs to be satisfied once — as explained in Chapter 10, all of the scaled service’s containers benefit from the same underlying guarantee before starting.

Q6Should I still add retry logic in my application even if I’ve declared dependencies properly?

Yes — as discussed in Chapter 9, retry logic provides a genuine safety net against tight timing edge cases that ordering alone can’t fully guarantee against.

16Summary and Key Takeaways

depends_on gives Docker explicit knowledge of the real relationships between your application’s services, turning what would otherwise be an unpredictable, potentially race-prone startup order into something deliberate and reliable. Its basic form guarantees only that a depended-upon service has been told to start first, a distinction genuinely worth internalizing, since true readiness requires pairing the dependency with a condition based on a proper health check. Understood correctly, alongside its interaction with dependency graphs, scaling, and application-level retry logic as a complementary safety net, depends_on becomes one of the most practical tools for making a multi-service application start up the same reliable way, every single time.

Key Takeaways

  • Service dependencies describe genuine reliance — one service needing another to exist and often be ready before it.
  • Without declaring them, startup order is essentially unpredictable — leading to intermittent, confusing failures.
  • “Started” is not the same as “ready” — the single most important distinction in this entire topic.
  • Condition-based dependencies close that gap — particularly the “service healthy” condition, paired with a real health check.
  • Circular dependencies have no valid resolution — and almost always point to a real design issue.
  • Retry logic and depends_on are complementary — not substitutes for one another.
  • Dependencies scale naturally with a service — a scaled service’s dependency only needs to be satisfied once.