Skip to content

Environments and the two-layer config

shipped 1.0.0

Two files, both YAML, both maintained by cwp init and cwp config set rather than by hand.

The project — .cwp.yml, committed

version: 1
name: example
docroot: public
php: "8.3"
plugin_slug: example-site          # the site plugin's directory

environments:
  dev:
    cloudron_app: dev.example.com
    url: https://dev.example.com
    package: managed               # managed | developer
    protected: false
  prod:
    cloudron_app: example.com
    url: https://example.com
    package: managed
    protected: true                # refuses upward writes without --force
  vps:
    provider: ssh                  # a plain host
    ssh: deploy@example.com        # what you would type after `ssh`
    path: /var/www/example         # the docroot on that host
    url: https://example.com
    protected: true

tracked:                           # the paths push and deploy send upward
  - wp-content/plugins/example-site

pull:
  uploads: proxy                   # sync | skip | proxy
  scrub: true
  keep_admin_email: you@example.com
  keep_roles: [administrator, editor]

builder:
  id: bricks                       # none | bricks
  options:
    child_theme: example-child
    export_dir: bricks
    wp_user: 1                     # optional — who ability calls run as

hooks_dir: .cwp/hooks

The environment map describes your remotes. The local site is not in it, and that is why omitting the environment on cwp bricks, cwp content, cwp plugins, cwp ability, cwp mcp and cwp open means local rather than dev.

The machine — ~/.config/cwp/config.yml, never committed

version: 1
cloudron_host: my.example.com

defaults:
  uploads: proxy
  scrub: true
  backup_before_push: true
  snapshot_before_pull: true
  snapshot_before_bricks_write: true

projects:
  example: ~/Code/example/example.com

The projects registry is written by cwp init and read by cwp projects.

Which layer wins, and how to find out

pull.uploads is project config and defaults.uploads is machine config. They spell the same idea deliberately: one is the default the other starts from, and a flag beats both.

The layer is never a guess. cwp knows which file each key lives in, and an unknown key is refused with the near misses rather than written to whichever file happened to be open. cwp config get <key> --explain prints the chain and marks the winner:

✓ pull.uploads — proxy
pull.uploads resolves to proxy from the project layer:
    flag     —
  → project  proxy
    machine  —
    default  proxy

Two things the config deliberately does not hold

Defaults you did not set. cwp config set writes back your file with one key changed, never a serialised copy of the parsed config — otherwise every schema default would freeze into the file the first time you set anything, and a default cwp later corrects would never reach you. That is not hypothetical: it is exactly how a set of table names that had never existed survived in real projects.

ssh settings. ssh: is passed through verbatim, so ports, identity files and jump hosts live in ~/.ssh/config, where they cannot disagree with the rest of your machine.