docker.yml / docker.local.yml
Compose execution policy for the DWE project.
Contents
Section titled “Contents”- Purpose
- dwe docker vs dwe compose
- Structure
- Field reference
- docker.local.yml
- Common pitfalls
- Related commands
Purpose
Section titled “Purpose”workspace/docker.yml controls how dwe docker builds and executes docker compose commands: the project name, per-subcommand args, and process environment. The .env file is automatically regenerated by the CLI before {up, run, exec, restart, build} — this behavior is not configurable.
This file is loaded on its own and does not participate in the 3-layer config merge.
Local overrides go in workspace/docker.local.yml (gitignored). Template in workspace/docker.local.example.yml. Local overrides are deep-merged into docker.yml before unmarshalling — local wins on key conflicts, lists replace.
flowchart LR
A["workspace/docker.yml"] --> M(["deepMerge"])
B["workspace/docker.local.yml<br/>optional, gitignored"] --> M
M --> P(["resolveVarTemplate<br/>$#123;...#125; against DweConfig.Raw"])
P --> R[("DockerConfig")]
dwe docker vs dwe compose
Section titled “dwe docker vs dwe compose”| Command | Purpose |
|---|---|
dwe docker <subcommand> | Public lifecycle API. Policy args applied. Use in Makefiles, deploy steps, and YAML commands. |
dwe compose raw <args...> | Low-level diagnostic pass-through. No policy args. Use for debugging only. |
dwe compose files | Show active compose file list (diagnostic). |
dwe compose argv | Show full effective argv including policy args (diagnostic). |
Only dwe docker subcommands are allowed in Makefiles, YAML command definitions, and deploy steps. Direct docker compose calls bypass policy and must not appear in any automation.
Structure
Section titled “Structure”project_name: "${project.prefix}-${project.name}"
args: global: ["--ansi", "always", "--progress", "tty"] up: ["-d", "--remove-orphans"] logs: ["-f"] run: ["--rm"] pull: [] build: []
process_env: DOCKER_CLI_HINTS: "false"
topology: hidden: [redis-insight-setup]
resources: volumes: composer_cache: name: dwe_composer_cache shared: true ensure_before: [up, deploy]
build: prepull_bases: falseField reference
Section titled “Field reference”project_name
Section titled “project_name”project_name: "${project.prefix}-${project.name}"The Docker Compose project name passed as -p <name> to every compose invocation. Supports ${dot.path} lookups into the merged DWE config (see Templates — ${...} namespaces), resolved with its own stricter rule: any dot-path into Raw resolves here (no namespace whitelist), but an unresolved path is a hard error rather than a literal ${...} — a broken project name must fail loudly instead of silently reaching docker compose -p ${...}. Default resolves to dwe-laravel.
The resolved name is lowercased. Docker Compose requires a project name matching [a-z0-9][a-z0-9_-]* and rejects uppercase outright, while project.name, project.prefix, and this field are all free-form user text — so project.name: cueBreaker resolves to dwe-cuebreaker, and project_name: "MyApp" to myapp. The lowercased form is what docker compose -p receives, what dwe docker project-name prints, and what every derived name follows: container names (<project>-<service>), the non-shared volume prefix <project_name>_, and the com.docker.compose.project label filter used by status, per-service stop/restart, and reset. A project that previously ran under a name carrying uppercase should stop its stack with the older dwe version first — compose treats the old containers and volumes as belonging to a different project, so they would otherwise be left behind.
Override locally:
project_name: "my-custom-project"Per-subcommand arg lists. Each key is a docker subcommand name; global applies to every invocation before the subcommand-specific args.
args: global: ["--ansi", "always", "--progress", "tty"] up: ["-d", "--remove-orphans"] logs: ["-f"] run: ["--rm"] pull: ["--policy", "always"] build: ["--progress", "plain"]Available subcommand keys: global, up, down, stop, restart, logs, ps, exec, run, pull, build. (Container health checks use the docker_wait_healthy builtin in pipeline steps, which is configurable via timeout and interval parameters.)
Per-key defaults:
Four subcommands have built-in defaults applied automatically when the key is absent from both docker.yml and docker.local.yml:
| Key | Default |
|---|---|
up | ["-d", "--remove-orphans"] |
logs | ["-f"] |
run | ["--rm"] |
down | ["--remove-orphans"] |
Other keys (global, stop, restart, ps, exec, pull, build) have no defaults — they are nil if absent, empty if explicitly [], populated if explicitly set.
Nil vs. explicit empty:
Defaults are applied only when the key is absent from the YAML source. An explicit empty list (key: []) opts out of the default:
# Missing up key → use default [-d, --remove-orphans]args: logs: [] # Explicit empty → no default applied, stays []
# Explicit up → use the specified value, no merge with defaultargs: up: ["--no-deps"] # Replaces default, no mergeWhen overriding in docker.local.yml, the list replaces the tracked default entirely (lists do not merge):
# docker.local.yml — remove --progress tty (unsupported in some terminals)args: global: ["--ansi", "always"] # up, logs, run, down not specified → defaults still apply from docker.yml or built-inImage management subcommands (pull and build)
The pull and build subcommands include optional flags to control file set and cache behavior:
-
dwe docker pull [--all] [services...]— Pull images for services. By default, uses the active compose file set (base + enabled overlays). The--allflag pulls against all configured overlays, regardless of local enable state, without modifyingworkspace/local.yml. -
dwe docker build [--all] [--force] [services...]— Build images for services. Default behavior same as pull. The--forceflag appends--no-cache --pullto bypass Docker’s layer cache and re-pull base layers.--alland--forcecan be combined.
When configuring args.pull or args.build, they are applied before positional services or force flags. Example:
args: pull: ["--policy", "always"] build: ["--progress", "plain"]The --all flag is a per-invocation override only — it does NOT modify workspace/local.yml and does not persist across commands.
process_env
Section titled “process_env”Environment variables passed to every docker compose child process. Does not affect the container environment — only the compose CLI process itself.
process_env: DOCKER_CLI_HINTS: "false"Useful for suppressing Docker CLI noise that appears even when output is piped.
topology
Section titled “topology”topology: hidden: [redis-insight-setup]| Field | Description |
|---|---|
hidden | Compose service names excluded from the topology tree and health checks |
Useful for init containers that run once and exit — hiding them prevents the docker_wait_healthy builtin from waiting on them.
resources
Section titled “resources”Declares Docker resources that must exist before certain commands.
resources: volumes: composer_cache: name: dwe_composer_cache shared: true ensure_before: [up, deploy]| Field | Description |
|---|---|
volumes.<key>.name | Base volume name. The actual Docker name depends on shared: shared volumes use name verbatim (write the full literal name there — no prefix is applied); non-shared volumes are stored as <project_name>_<name> so they share their lifecycle and scope with the compose project (matching the convention Docker Compose uses for named volumes declared inside compose.yaml). |
volumes.<key>.shared | When true, the volume is project-independent: the actual Docker name equals name and the volume persists across project resets. When false (default), the volume is project-scoped — the runtime prepends <project_name>_ and docker_remove_project_volumes (the reset builtin) cleans it up alongside the project. Here <project_name> is the resolved compose project name — project_name from this file if set, otherwise the default <prefix>-<name> — so non-shared volumes are prefixed consistently even when project_name is omitted. |
volumes.<key>.ensure_before | Triggers that idempotently create the volume if missing. Supported values: up, deploy. |
resources: volumes: composer_cache: # logical key name: dwe_composer_cache # actual Docker name (shared) shared: true ensure_before: [up, deploy]
build_artifacts: # actual Docker name = "<project_name>_build_artifacts" name: build_artifacts ensure_before: [deploy]docker_remove_project_volumes (the reset builtin) removes every volume whose name starts with <project_name>_, so non-shared volumes are reset with the project while shared ones survive.
build: prepull_bases: false| Field | Description |
|---|---|
prepull_bases | When true, dwe docker build and dwe docker up derive the external FROM base images used by the services about to build and docker pull any that are missing from the local image store before handing off to compose build/compose up. Default false. |
Why: Docker Desktop’s buildkit fetcher cannot always reach LAN/private registries (failed to fetch oauth token … no route to host), even though a plain daemon-side docker pull reaches them fine. The docker-driver buildkit shares the daemon’s image store, so once a FROM base is present locally, buildkit resolves it without touching the network. prepull_bases works around the fetcher by pre-populating the store via docker pull for exactly the bases a build needs.
Coverage: both dwe docker build [services...] and dwe docker up are covered — up builds any images missing from the store too, and the default deploy/lifecycle pipeline runs dwe docker up --wait, so first deploy on a clean machine benefits without extra configuration. build narrows derivation to the named services (or all, with no args); up always derives against every service in the active compose config, since up builds dependencies transitively.
Missing-only by default: an already-present base is never re-pulled — this only fixes the “base absent locally” failure mode without changing “stale cached base” semantics.
--force interplay: with the flag off, dwe docker build --force behaves exactly as before — compose gets --no-cache --pull. With the flag on, --force instead pulls every derived base unconditionally via the daemon and compose receives only --no-cache (no --pull) — buildkit’s own --pull hits the same LAN-registry fetch failure, so re-pulling daemon-side is the only reliable “refresh base images” path once the flag is enabled.
Advisory, never a hard failure: every step of prepull (deriving refs from compose config + the service Dockerfiles, checking whether a base exists locally, pulling) is best-effort, and the normal compose build/compose up always runs afterwards — enabling prepull_bases can never make a build worse than leaving it off. What surfaces on stderr is narrow:
- Derivation failure (the
compose configcall fails, or its JSON output cannot be parsed) → onewarning:naming the failure; prepull is skipped and the build proceeds. An individual unreadable or unparseable service Dockerfile is not a derivation failure: that one service is skipped silently (visible only under--debug), and the other services’ bases are still prepulled. - A base-existence probe that fails (missing binary, daemon unreachable, an
inspectpredating--platform) is treated as “missing” and silently triggers a pull — no warning; a needless-but-harmless pull is the only cost. - A successful pull is silent.
- A pull that fails → a
warning:. If the base was confirmed missing it says the build will likely fail (the failure is now foreseeable); if it was a--forcere-pull of an already-present base, it is a softer notice and the cached base is used.
docker.local.yml
Section titled “docker.local.yml”Local overrides for the docker policy. Gitignored. Use workspace/docker.local.example.yml as a starting template.
docker.local.ymlvslocal.yml → compose.extra. For compose policy (project name, per-subcommand args, process env) →docker.local.yml(this file). For compose service overlays (extra-ffiles injecting env vars, volumes, ports on containers) →local.yml’scompose.extra/services.<name>.compose.extra. They are independent surfaces — seeworkspace.md.
Common overrides:
# Override project nameproject_name: "personal-laravel"
# Remove --progress tty (unsupported in some terminals)args: global: ["--ansi", "always"]
# Suppress Docker hintsprocess_env: DOCKER_CLI_HINTS: "false"Common pitfalls
Section titled “Common pitfalls”- Direct
docker composein Makefiles or YAML — always usedwe docker. Direct calls bypass policy args, project name, and.envauto-generation. - Adding compose flags in Make recipes — flags belong in
docker.ymlargs section, not in Make. Make lifecycle targets calldwe dockerwith no flags. - Overriding args partially —
args.upindocker.local.ymlreplaces the tracked list, not appends to it. Include all flags you need. - Expecting to pre-generate
.envin CI —.envis always regenerated before{up, run, exec, restart, build}and this cannot be disabled (see Purpose). Any pre-generated.envwill be overwritten; there is no config toggle for it.
Related commands
Section titled “Related commands”dwe docker up|down|stop|restart|logs|ps|exec|run|pull|build— lifecycle and image-management commands (upaccepts--waitto block until services are healthy)dwe compose files— show active compose file listdwe compose argv— show full effective argvdwe render env— manually regenerate.env