Chapter 37Lesson 01~170 minutes

Developer Workflows, Dev Containers, Inner Loop Optimization, Compose-Based Labs, and Local Parity: Concepts, Architecture, and Mental Model

Use containers to make development environments reproducible while keeping host coupling, ownership, performance, secrets, tool versions, and production-parity limits visible and testable.

Developer workflowDev ContainersComposeInner loopParity

Learning objectives

  • Explain what a reproducible development environment does—and does not—make reproducible.
  • Trace source, development metadata, mounts/sync, tool versions, caches, ports, secrets, and generated files across host and container boundaries.
  • Distinguish a development container from the production image and from the production host/kernel.
  • Inspect development state before changing it, including context, Compose model, image identity, user identity, mounts, ports, and cache ownership.
  • Define a parity-gap record instead of claiming that a containerized local environment is identical to production.

1. The practical problem: fast development can still hide state

A containerized developer workflow can remove “install this exact compiler and database on every laptop” from onboarding, but it can also move hidden state into bind mounts, Desktop file sharing, anonymous tool downloads, editor extensions, named volumes, forwarded ports, and implicit credentials. The goal is not to hide those boundaries. The goal is to make them explicit, versioned, inspectable, and disposable.

Chapter 36 made the release artifact immutable. Chapter 37 applies the same discipline to the inner loop: record the source revision, define the developer environment, keep caches and secrets separate from source, and still build production through its own controlled target.

2. Mental model: development is a dataflow, not just a container

Development environment causality from source workspace to production boundary
flowchart TD
  A[Source workspace + exact revision] --> B[Dev Container / Compose definition]
  B --> C[Developer container + supporting services]
  A --> D[Bind mount or Compose Watch sync]
  D --> C
  C --> E[Tests, tools, hot reload]
  C --> F[Named cache / generated state]
  E --> G[Evidence: versions, UID/GID, ports, test results]
  A --> H[Production Dockerfile target]
  H --> I[Immutable production image]
  G --> J[Parity-gap record]
  I --> J
            

The source workspace is the human-edited truth. A Dev Container or Compose definition describes how tools/services are created. A bind mount or Watch rule determines how edits cross the host/container boundary. Tests and tools run in the developer container, while caches and generated files need an intentional home. Production is a separate build boundary: it consumes reviewed source and build inputs rather than inheriting a long-lived developer container filesystem.

3. State domains to record before changing anything

State Questions Read-only evidence
Source Which revision? dirty files? generated files? git rev-parse HEAD, git status --short
Context/daemon Which Docker host receives the commands? docker context show, docker version
Compose/dev metadata Which services, mounts, users, ports, watch rules? docker compose config; review devcontainer.json
Image/toolchain Which base/tool versions? docker image inspect; commands inside container
Identity Which UID/GID owns generated files? id inside and outside container
Workspace path Bind-mounted, synced, copied, or volume-backed? container Mounts and Compose model
Cache Who owns it, when does it expire? named-volume inspect; explicit cache path
Network What is published versus merely forwarded by an editor? Compose ports, docker port, dev-tool metadata
Secrets Where are credentials injected? file/env/provider inventory without printing values
Production boundary Does dev add tools or flags absent from prod? compare Dockerfile targets and runtime config

4. Dev Container Spec versus Compose

The Development Container Specification is development metadata. It can reference an image, Dockerfile, or Compose service and add concepts such as a workspace folder, remoteUser, tool/editor customizations, lifecycle hooks, Features, and forwarded ports. Compose describes container services, networks, volumes, builds, health, and development Watch rules. They can complement each other: Compose can own the multi-service runtime while devcontainer.json tells compatible development tools which service is the interactive workspace.

5. Bind mount versus Watch/sync

Mechanism What changes Strength Caution
Bind mount container sees host path through daemon/VM sharing simple; immediate bidirectional edits host coupling, permissions, cross-OS performance
Compose Watch sync Compose copies changed source into running container can avoid large bind-mounted trees container target must be writable by configured user
Watch rebuild image is rebuilt and service recreated correct for dependency/build-input changes slower; creates new image/runtime state
Named volume Docker-managed persistent data good for dependency caches/databases not human-edited source; lifecycle must be explicit

6. Why non-root still matters in development

A development container that runs every tool as root often leaves root-owned artifacts on native Linux bind mounts and grants unnecessary authority inside the container. Prefer an explicit non-root user whose writable directories are known. On native Linux, numeric UID/GID alignment can improve bind-mounted ownership. Docker Desktop inserts a VM/file-sharing layer, so do not assume host numeric ownership behaves exactly like native Linux.

7. Caches are performance state, not source state

Dependency caches can make the inner loop dramatically faster, but they should not be committed to source or mistaken for reproducible build evidence. Put package-manager caches in a named volume or explicit cache directory, scope them to the project/toolchain, and be able to delete only that cache when diagnosing corruption. Databases also generally belong in named volumes rather than large Desktop bind mounts.

8. Ports: published runtime port versus development forwarding

A Compose ports entry publishes a host socket through Docker networking. A Dev Container forwardPorts request is metadata interpreted by a supporting development tool and can behave differently across implementations. Record which mechanism exposes the service, the bound interface, and who can reach it. Development convenience must not silently become production exposure policy.

9. Features and tool versions are supply-chain inputs

Dev Container Features are convenient reusable installation units, but they are still external code and artifacts. Use reviewed publishers, constrain versions, retain the generated lockfile where supported, and update deliberately. “Feature major version 2” is a human-friendly request; the resolved digest in a lockfile is stronger evidence of what was actually installed.

10. Production parity means controlled differences

Dimension Development may intentionally differ What must remain explicit
Tools debugger, shell, compiler, editor helper tool versions and absence from prod target
Filesystem source bind/sync; writable caches production image is immutable except intended data paths
Ports loopback development publish/forward production ingress and TLS are separate architecture
Kernel/host Desktop VM, laptop Linux, WSL production kernel/runtime controls are not simulated perfectly
Secrets fake/local development values real provider/rotation/authorization path differs
Scale single local instance production replicas, load balancing, external dependencies

11. Read-only preflight

docker context show
docker version
docker compose version
docker buildx version
docker info --format 'os={{.OperatingSystem}} driver={{.Driver}} cgroup={{.CgroupVersion}}'
git rev-parse HEAD 2>/dev/null || true
git status --short 2>/dev/null || true
docker compose config 2>/dev/null || true

Run inspection against the intended project only. Do not clean images, volumes, or caches because an onboarding check looks unfamiliar.

12. Chapter evidence contract

For every lab, retain the source revision, Compose/devcontainer definitions, resolved base-image identity when practical, container user, workspace mount/sync mechanism, tool versions, cache names, local port exposure, test result, production image digest/ID, and a short list of known dev-versus-production differences.

Knowledge check

Does a Dev Container guarantee production parity?

Why might a named volume be better than a bind mount for a package cache on Docker Desktop?

What does Compose Watch sync change?

Is forwardPorts identical to Compose ports?

What evidence proves the production image is separate from the dev container?

Next lesson

Next: Developer Workflows, Dev Containers, Inner Loop Optimization, Compose-Based Labs, and Local Parity: Guided Hands-On Workflow and Core Operations

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

Baseline checked:

2026-09-22. Compose Develop/Watch requires Compose 2.22+; current Compose adds actions beyond the original sync/rebuild pair. Dev Container behavior can vary by supporting tool, so this chapter distinguishes specification metadata from Docker runtime state.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.