Docker Compose
Docker Compose defines and orchestrates multi-container applications, including their services, networks, volumes, configuration, and dependencies. For a Java backend engineer, it is commonly used to run an application together with infrastructure such as PostgreSQL, Redis, or Kafka in a reproducible local environment.
A Compose application is described in a YAML file, commonly named `compose.yaml` or `docker-compose.yml`. Each service represents a containerized component and can specify an image or build context, environment variables, ports, volumes, networks, and other runtime settings.
Compose creates and manages the resources needed for the declared services. Containers on a shared Compose network can normally reach one another by service name, so an application can connect to PostgreSQL using a hostname such as `db` rather than `localhost`.
Dependencies can be declared with `depends_on`, which controls service startup ordering and, depending on the configured conditions, can coordinate startup with container health. It does not by itself guarantee that an application is ready to accept requests, so health checks may be needed.
Volumes provide persistent storage independently of a container's writable layer. This is especially useful for databases: recreating a PostgreSQL container does not necessarily discard its data when a named volume is retained.
For development, Compose is useful because a multi-service environment can be started from one configuration. A Java application can therefore use the same declared PostgreSQL, Redis, or messaging dependencies across developer machines without requiring each dependency to be installed directly on the host.
Key points
- A Compose file declares multiple services and their runtime configuration.
- Services on the same Compose network can communicate using service names as DNS names.
- `depends_on` expresses dependency relationships but is not a general readiness mechanism.
- Named volumes can preserve state outside a container's writable filesystem.
- Compose is particularly useful for reproducible local multi-container development environments.
Interview questions
What problem does Docker Compose solve?
Docker Compose provides a declarative way to define and run an application composed of multiple containers. Instead of manually starting PostgreSQL, Redis, and an application container with separate commands, their configuration can be stored together. Compose also manages shared networks, volumes, environment configuration, and service relationships. This makes local environments easier to reproduce.
How can one Compose service connect to another?
Services on the same Compose network can normally address each other by service name. For example, if PostgreSQL is declared as `db`, a Java application can use `db` as the database hostname. `localhost` would instead refer to the application container itself. Host port publishing is generally unnecessary for container-to-container communication on the same network.
What does `depends_on` guarantee?
`depends_on` expresses that one service depends on another and can control startup ordering. A basic dependency does not necessarily mean the dependency is ready to serve requests. A health check combined with an appropriate dependency condition can provide stronger startup coordination. Applications should still handle connection failures and retries because readiness can change after startup.
What is the difference between a bind mount and a named volume in Compose?
A bind mount maps a specific host filesystem path into a container, giving direct control over the host location. A named volume is managed by Docker and is referenced by a logical volume name. Named volumes are often convenient for persistent service data such as database files. Bind mounts are particularly useful when development code or specific host files need to be shared with a container.
How would you use Compose for a Java backend that depends on PostgreSQL and Redis?
I would define separate services for the Java application, PostgreSQL, and Redis, with explicit environment configuration for their connection details. The application would use service names such as `db` and `redis` rather than host-specific addresses. PostgreSQL would use a named volume and a health check, while the application would tolerate dependency restarts through connection retries. I would keep secrets out of committed configuration and use environment-specific secret injection where appropriate.