cswork / libraryOpen practice workspace

Articles

Docker Compose Interview Questions: Readiness, Service DNS, and Configuration Precedence

Prepare for Docker Compose interviews with a two-service failure fixture, health checks, service DNS, env precedence, and diagnostic commands.

“It works on my machine after I restart it” is not a readiness strategy. In a Docker Compose interview, a small API-and-database setup can reveal three distinct failure classes: the database container started but is not ready, the API points to the wrong hostname, or a value from a shell, .env, Compose file, or override is not the value the engineer thinks it is. Treating all three as “Docker networking” leads to random retries and brittle fixes. This guide uses one original incident exercise to separate startup, service DNS, and configuration resolution.

Evidence boundary: the behavior described here is grounded in current Docker Compose documentation. The app, incident, commands-to-run sequence, and evaluation rubric are editorial examples. Verify your Compose CLI release and project files before assuming that an example maps exactly to your environment.

The interview incident: an API cannot connect to Postgres

Assume a db service runs Postgres and an api service connects on startup. The API logs “connection refused” on its first boot. After a manual restart, it works. Another teammate reports “name not found,” while a third sees the app connect to a developer laptop database. These symptoms should not be merged into one diagnosis. Start by asking for the effective Compose configuration and the exact endpoint the API tried, then sort the evidence into three planes.

PlaneTypical symptomFirst questionUseful evidence
ReadinessConnection refused, then succeeds after restartWas Postgres accepting connections, not merely started?Health status, app logs, dependency condition
Service DNS and portCannot resolve host or connects to selfIs the API using db:5432 inside the network?Network membership, service name, container environment
ConfigurationEndpoint differs across machinesWhich source supplied the effective value?docker compose config, shell and env-file inputs

The distinction is causal. A hostname error is not cured by making Postgres healthy. A healthy database will not help if the API's DATABASE_URL points to localhost. A correctly resolved db hostname can still refuse connections while Postgres initializes. An interviewer may deliberately give you all three clues to see whether you isolate them rather than applying one memorized command.

Startup order is not application readiness

Docker's Compose startup-order documentation distinguishes a dependency that is started from one that is healthy. A basic depends_on relationship controls startup ordering, but it does not by itself wait until Postgres is ready to handle application queries. A healthcheck plus the service_healthy condition can delay starting the dependent service until that healthcheck passes. There is also service_completed_successfully for a one-shot job, such as a migration that should finish before the API begins. Name the required state rather than saying “Compose waits for the database.”

A minimal teaching configuration is:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
  api:
    build: ./api
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD:?set DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy

This is an illustrative local setup, not a recommendation to commit a real password. The required-value expression makes missing input fail during Compose interpolation. In production, use the platform's appropriate secret mechanism and avoid exposing credentials through careless logs or shared config output. The healthcheck only proves what pg_isready checks: a readiness signal from Postgres under those arguments. It does not prove migrations are complete, an expected table exists, or the API's credentials can perform every operation. If migrations are a dependency, model and test that condition explicitly.

Even with startup ordering, an operationally resilient API should usually tolerate later database interruptions. A health condition helps at initial orchestration; it is not a substitute for bounded connection retries, useful errors, graceful shutdown, and monitoring. In an interview, distinguish a local Compose fix from the application's ongoing failure handling. Do not add an infinite sleep 30 and call it reliable readiness: different machines and data volumes make the necessary time variable.

Service DNS: inside the network, db is a hostname

On the default Compose network, services can reach each other using the service name. In the example, the API should address db on Postgres's container port 5432. The localhost seen by the API process names the API container itself, not the developer's host and not the db container. A host-published port, if one is configured, is for a connection from the host side; it is not normally the port the API uses to call its sibling service.

This is why DATABASE_URL=postgres://...@localhost:5432/app can fail in the API while psql on the laptop succeeds. The two commands run in different network namespaces. State where each client runs, then identify the hostname and port from that vantage point. If the services use separate explicitly declared networks, confirm that they actually share one. If a service name is overridden or the app expects an alias, inspect the resolved network configuration rather than assuming the default.

An interviewer may ask whether a published mapping such as 5433:5432 changes the API URL. Usually it does not: 5433 is the host-facing published port, while db:5432 remains the service-to-service endpoint on the Compose network. This one sentence catches a common error in otherwise strong answers. The exception is a deliberately different network topology; explain that topology rather than repeating the rule without context.

Configuration precedence has two different questions

“Which environment variable wins?” is ambiguous. First ask whether Compose is interpolating the YAML model or whether it is setting the environment inside a container. Docker documents interpolation sources for ${VAR} values and separately documents container environment precedence across CLI -e, environment, env_file, and image ENV inputs. A shell variable can affect interpolation without being automatically injected into the container unless the Compose file passes it through. An env_file can supply container variables without being the same as Compose's project .env interpolation file.

Consider this deliberately confusing case. A developer's shell has DB_HOST=localhost. A project .env says DB_HOST=db. The Compose file says DATABASE_HOST: ${DB_HOST:-db}. If shell input wins interpolation, the resolved service model gives the API DATABASE_HOST=localhost despite the default and the .env line. The database may be healthy and DNS may work perfectly; the app simply received the wrong endpoint. Do not memorize this example as a universal claim about every launch context. Reproduce the actual invocation, including --env-file, working directory, and any -f override files, then inspect the result.

Use docker compose config to render the effective configuration and see substituted values. That command can expose secrets, so do not paste its full output into a public ticket. For a specific environment variable, inspect only what is needed in a controlled setting. Then compare the rendered value with what the running container sees; changing a file does not necessarily alter an already-running process. If a teammate uses a different shell or an additional Compose override, their effective model may differ even though the base YAML in Git is identical.

Multiple Compose files add a further layer: merge rules can override or combine fields. For an interview answer, it is enough to say “I will inspect the exact -f file order and the rendered model.” Do not guess that the last visible line in a repository is the effective value. If a value is unexpectedly empty, a required-variable expression can turn a silent misconfiguration into a clear startup error. If the app logs the final connection host, redact credentials and make the hostname visible; otherwise diagnosis is needlessly slow.

A five-minute investigation sequence

First, reproduce the command the team actually used and capture the API's error class: name resolution, connection refusal, authentication failure, or SQL/schema failure. Those are different stages. Second, render the Compose model and inspect api's DATABASE_URL or host variable and network membership. Third, inspect db health and logs. Fourth, from the API container's vantage point, test name resolution and a connection to db:5432 if the image has suitable diagnostic tools. Fifth, after fixing the cause, start from a clean state and assert the app becomes ready without a manual restart. Do not delete persistent volumes just to make a demo pass; that can destroy the very database state the incident concerns.

EvidenceLikely hypothesisNext discriminating check
db resolves, port refuses brieflyReadiness gapHealthcheck, app retry logs, initialization timeline
db does not resolveNetwork/name mismatchShared Compose network and actual service name
App tries localhostInterpolation or runtime configurationRendered model and running container environment
TCP connects, login failsCredentials or role mismatchSanitized auth configuration and database logs
Login works, query failsMigration or schema issueMigration completion and expected table/version

The last two rows matter because a generic “database unavailable” message can hide later stages. A good engineer narrows the failing layer before changing code. When asked to fix it, choose the smallest intervention aligned to evidence: a healthcheck dependency for startup timing, a service name for container-to-container traffic, or a corrected variable source for configuration. If more than one is wrong, fix and verify each independently.

What makes a strong interview answer?

Lead with the distinction between started, healthy, and application-ready. Draw the two network locations: host and Compose network. Then separate YAML interpolation from the environment available to the running process. Include a command that shows the resolved model and a test that fails if the API needs a manual restart. This is more persuasive than listing Docker terms without connecting them to the symptom.

If the interviewer changes the prompt to “works locally, fails in CI,” ask for CI's shell variables, .env availability, file order, and whether the healthcheck command exists inside the image. A healthcheck that calls a missing executable may keep a service unhealthy forever. If they ask about scaling the API, explain that service discovery and startup checks are still relevant but one-shot initialization and migrations should be coordinated carefully; multiplying containers is not a cure for shared-schema readiness. If they ask whether depends_on guarantees zero runtime outages, answer no and describe ongoing retry or circuit-breaking behavior.

Sources and Further Reading