Docker Compose Foundations: Services, Images, Builds, Networks, Volumes, Environment, and Project Lifecycle: Configuration, Design Choices, and Tradeoffs
Choose intentionally between image and build, default and explicit networks, named volumes and bind mounts, environment sources, project naming, and foreground/detached workflows while tying each choice to observable state.
Learning objectives
-
Choose between an immutable
image:reference and a localbuild:definition based on release and developer workflow requirements. - Choose default versus explicit networks and named volumes versus bind mounts using portability, isolation, and data-ownership evidence.
- Distinguish Compose interpolation sources from container environment sources and secrets.
- Apply project-name precedence intentionally to avoid cross-project collisions.
- Choose foreground/detached operation based on observability and operator workflow rather than habit.
1. image: versus build:: artifact
consumption versus artifact production
image: identifies a runnable artifact. It can be a tag
or digest reference. build: describes how Compose
should build an image from source. A service may contain both;
current Compose then follows pull/build policy rules. If no
pull_policy is provided, Compose can attempt to pull
the image before building when both are present.
| Choice | Good fit | Evidence to retain | Tradeoff |
|---|---|---|---|
| Digest-pinned image | Controlled release/deployment input. | Registry digest, platform, pull evidence. | Requires an external build/promotion workflow. |
| Versioned image tag + digest record | Readable release alias with immutable audit evidence. | Tag resolution + digest at release time. | Tag can move unless policy prevents it. |
Local build:
|
Developer loop and source-controlled reproducible build recipe. | Source revision, normalized build config, BuildKit trace, output image ID/digest. | Build environment/cache become part of operational evidence. |
| Both image + build | Workflow that names built output and may pull/build under policy. | Resolved pull_policy, build/pull trace. |
Ambiguous intent if policy is not understood. |
2. Default network versus explicit networks
The default project network is often ideal for a small local application: every service joins one bridge and is discoverable by service name. Explicit networks become valuable when you need segmentation, different connectivity domains, or a deliberate external network boundary.
Do not confuse network declaration with application reachability. A container can be attached to the expected network while the application listens on the wrong interface/port. Conversely, a published host port is not required for service-to-service communication on the same project network.
3. Named volume versus bind mount
| Choice | Strength | Cost / coupling | Evidence |
|---|---|---|---|
| Named volume | Engine-managed lifecycle and portable service declaration. | Host storage location is abstracted; backup/ownership still must be planned. | Volume ID/name, Compose labels, mount destination, backup/restore proof. |
| Bind mount | Direct host-file visibility; useful for editable source/config in development. | Host paths, permissions, SELinux/AppArmor/platform semantics can couple the workload to one machine. | Exact host path, read/write mode, ownership/label assumptions. |
| Container writable layer | Automatic ephemeral runtime state. | Not a durable data strategy; disappears with container removal. |
Container ID, docker diff, storage driver
context.
|
A good rule: application data that must survive replacement needs an explicit durability design. Named volumes are convenient locally, but “named volume” is not synonymous with “backed up.”
4. env_file, environment, and
interpolation solve different problems
Interpolation parameterizes the Compose model
before it is sent to the Engine.
environment and
env_file populate the container
environment. The shell, --env-file, default
.env, Compose attributes, CLI runtime overrides, and
image ENV participate in different precedence chains.
For non-secret configuration, keep source ownership obvious: use
required-variable syntax for values that must be supplied and
docker compose config --environment to verify
interpolation. For secrets, use a secrets mechanism appropriate to
the runtime; do not commit secrets into .env simply
because Compose can read it.
5. Project naming: deterministic isolation versus accidental collisions
Explicit project naming is useful in CI, parallel feature branches, classroom labs, and shared developer hosts. If two invocations unintentionally resolve to the same project name, one can reconcile or tear down resources the other operator thought were separate.
# Highest-precedence explicit choice for one invocation:
docker compose -p da-compose15-a config --quiet
# Another isolated instance from the same Compose source:
docker compose -p da-compose15-b config --quiet
Record the project name in evidence. Do not diagnose a “missing service” until you have proved you are targeting the expected project and Docker context.
6. Foreground versus detached workflow
Foreground docker compose up streams attached logs and
is excellent for small development/test runs where terminal lifetime
is intentional. Detached up -d returns control to the
operator and requires explicit ps, logs,
health, and application probes. Neither mode makes the application
more production-ready by itself.
7. Decision table: choose based on state ownership
| Scenario | Recommended starting choice | Prerequisites | Observable justification |
|---|---|---|---|
| Developer editing source rapidly |
Local build:, default network, source bind
mount only if needed, named data volume.
|
Trusted local source; documented host-path semantics. | Build trace, normalized model, mount inspection, project labels. |
| Release candidate validation |
Digest-pinned image:, named volume or
disposable data, explicit project name.
|
Published verified image digest. | Image digest, project identity, health/application evidence. |
| Two parallel CI jobs on one Engine |
Unique -p value per job; avoid shared writable
volumes.
|
Authorized shared runner with resource limits. | Project labels prove isolation; no cross-project resource names. |
| Service must not be host-accessible |
No ports:; use project/internal network only.
|
Consumer runs on an attached network. | No host port binding; successful service-name request. |
| Data must be edited directly by host tools | Bind mount only when host coupling is accepted. | Stable host path/permissions/security labeling. | Exact source path and mount mode in inspect evidence. |
8. Worked scenario: a test environment must be repeatable and auditable
Assume CI receives a release image digest and must execute
integration tests. Prefer an explicit project name derived from a
non-secret job ID, the release image by digest, a project-local
default network, and a disposable named volume if persistence is
needed only within the job. Save
docker compose config and
compose ps --format json. Tear down the project after
evidence capture; delete the exact test volume only if the job's
data-retention policy says it is disposable.
This design avoids rebuilding a release during promotion, avoids mutable-tag ambiguity, and makes project ownership visible through labels.
9. Keep state layers separate while evaluating a Compose choice
- Host/client/context: Docker context, filesystem paths, CLI and Compose versions.
- Build/image: source revision, builder, image ID/digest, cache.
- Container/process: command, PID, exit state, health.
- Network: network attachment, DNS, published ports, application listener.
- Storage: writable layer, named volume, bind source, backup.
- Identity/trust: registry authentication/authorization and artifact verification.
A single Compose file references all of these layers, but it does not collapse them into one state.
Knowledge check
When is build: preferable to a digest-pinned
image:?
When producing an image from local/source-controlled inputs is part of the intended workflow, such as development. A release consumer should generally consume the already-built immutable artifact rather than rebuild it.
Why can an explicit project name improve CI reliability?
It prevents unrelated jobs from resolving to the same project identity and makes resource ownership observable through Compose labels.
Does using a named volume automatically solve backup/recovery?
No. It separates data from container replacement, but backup, restore, retention, and access controls remain separate responsibilities.
What is the difference between interpolation and
environment?
Interpolation resolves values in the Compose model before
application; environment sets values in the service
container environment.
Does omitting ports: prevent services on the same
Compose network from communicating?
No. They can normally communicate using service names and
container ports on the project network. ports: is
for host/external publication.
Official references and version notes
- Docker Docs — Compose application model: projects, services, networks, volumes, and lifecycle.
- Docker Docs — Compose Specification reference: current declarative model and attributes.
-
Docker Docs —
docker compose config: canonical rendering, interpolation environment, service/network/volume views, hashes and digest resolution. - Docker Docs — project naming: precedence and isolation.
- Docker Docs — interpolation and container environment precedence.
- Docker Docs — Compose networking: default network and service-name discovery.
-
Docker Docs —
docker compose down: default teardown and explicit volume deletion semantics. - Docker Compose v5.5.1 release (2026-09-03). Labs record the actually installed version and do not assume every host matches upstream.
Upstream
Compose is v5.5.1. The course still treats
docker compose version, docker version,
docker info, and the active context as execution
evidence. The top-level Compose version: field is
obsolete/informative; the current Compose implementation validates
against the current schema.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.