Skip to content

docker.yml / docker.local.yml

Compose execution policy for the DWE project.

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")]
CommandPurpose
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 filesShow active compose file list (diagnostic).
dwe compose argvShow 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.

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: false
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:

docker.local.yml
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:

KeyDefault
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 default
args:
up: ["--no-deps"] # Replaces default, no merge

When 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-in

Image 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 --all flag pulls against all configured overlays, regardless of local enable state, without modifying workspace/local.yml.

  • dwe docker build [--all] [--force] [services...] — Build images for services. Default behavior same as pull. The --force flag appends --no-cache --pull to bypass Docker’s layer cache and re-pull base layers. --all and --force can 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.

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:
hidden: [redis-insight-setup]
FieldDescription
hiddenCompose 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.

Declares Docker resources that must exist before certain commands.

resources:
volumes:
composer_cache:
name: dwe_composer_cache
shared: true
ensure_before: [up, deploy]
FieldDescription
volumes.<key>.nameBase 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>.sharedWhen 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_beforeTriggers 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
FieldDescription
prepull_basesWhen 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 config call fails, or its JSON output cannot be parsed) → one warning: 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 inspect predating --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 --force re-pull of an already-present base, it is a softer notice and the cached base is used.

Local overrides for the docker policy. Gitignored. Use workspace/docker.local.example.yml as a starting template.

docker.local.yml vs local.yml → compose.extra. For compose policy (project name, per-subcommand args, process env) → docker.local.yml (this file). For compose service overlays (extra -f files injecting env vars, volumes, ports on containers) → local.yml’s compose.extra / services.<name>.compose.extra. They are independent surfaces — see workspace.md.

Common overrides:

# Override project name
project_name: "personal-laravel"
# Remove --progress tty (unsupported in some terminals)
args:
global: ["--ansi", "always"]
# Suppress Docker hints
process_env:
DOCKER_CLI_HINTS: "false"
  • Direct docker compose in Makefiles or YAML — always use dwe docker. Direct calls bypass policy args, project name, and .env auto-generation.
  • Adding compose flags in Make recipes — flags belong in docker.yml args section, not in Make. Make lifecycle targets call dwe docker with no flags.
  • Overriding args partiallyargs.up in docker.local.yml replaces the tracked list, not appends to it. Include all flags you need.
  • Expecting to pre-generate .env in CI.env is always regenerated before {up, run, exec, restart, build} and this cannot be disabled (see Purpose). Any pre-generated .env will be overwritten; there is no config toggle for it.
  • dwe docker up|down|stop|restart|logs|ps|exec|run|pull|build — lifecycle and image-management commands (up accepts --wait to block until services are healthy)
  • dwe compose files — show active compose file list
  • dwe compose argv — show full effective argv
  • dwe render env — manually regenerate .env