Commands
Every ecaddy subcommand and its flags. Commands that touch a project use a site name — a short identifier you supply with --site NAME (or -s NAME). The name determines the fragment filename and is how up, down, edit, and remove target the project.
ecaddy setup
One-time machine bootstrap. Install Caddy, scaffold the global config, trust the local CA, start the brew service.
ecaddy setup
Every step is idempotent — safe to re-run if a prior step failed or you've reinstalled Homebrew. See Getting Started › Step 2 for the per-step breakdown.
ecaddy run
Register a project Caddyfile, block, and unregister on shutdown. The Procfile-friendly variant — pair it with foreman, overmind, or bin/dev.
ecaddy run --config ./Caddyfile --site fishme
ecaddy run -c ./Caddyfile -s fishme
Flags
| Flag | Required | Description |
|---|---|---|
| --config, -c PATH | yes | Path to the project's source Caddyfile |
| --site, -s NAME | yes | Site identifier (fragment filename, registry key) |
Lifecycle
- Validate that
--configexists. - Run conflict detection against the registry — BLOCK on shared domain or port.
- Rewrite relative
output filelog paths to absolute paths (resolved from the directory of--config). - Write the result to
~/.config/caddy/sites/<name>.caddy. - Upsert the registry entry.
- Validate the global config with
caddy validateand reload Caddy. - Block on a signal trap.
- On
SIGTERM/SIGINT: delete the fragment, reload Caddy, exit 0.
/projects/fishme/Caddyfile; Caddy runs from brew services and has no relation to that directory. output file log/caddy.log would write somewhere unpredictable. ecaddy rewrites it to /projects/fishme/log/caddy.log before the fragment is installed.
ecaddy ensure
One-shot variant of run. Copy the Caddyfile, reload Caddy, exit immediately. The site stays registered across reboots until you run ecaddy down or ecaddy remove.
ecaddy ensure --config ./Caddyfile --site fishme
Use this in CI, shell scripts, or any setup where you want Caddy configured but don't need a foreground process tied to a dev loop.
ecaddy list
Show all registered sites in a table.
ecaddy list
ecaddy list --format json
┌─────────┬────────┬────────────────────────────────────────────┬────────────┬──────────────────────────┐
│ Name │ Status │ Domains │ Ports │ Source │
├─────────┼────────┼────────────────────────────────────────────┼────────────┼──────────────────────────┤
│ fishme │ up │ fishme.localhost, vite.fishme.localhost │ 3054, 3104 │ /projects/fishme/Caddyfile │
│ letly │ down │ letly.localhost, vite.letly.localhost │ 3050, 3100 │ /projects/letly/Caddyfile │
└─────────┴────────┴────────────────────────────────────────────┴────────────┴──────────────────────────┘
--format json emits a machine-readable list suitable for piping to jq.
ecaddy up NAME / ecaddy down NAME
Enable or disable a registered site without removing it. The fragment is moved between sites/ and disabled/ via Pathname#rename (atomic), the registry's enabled flag is flipped, and Caddy is reloaded.
ecaddy down fishme # sites/fishme.caddy → disabled/fishme.caddy, reload
ecaddy up fishme # disabled/fishme.caddy → sites/fishme.caddy, reload
Disabled fragments are preserved — their content stays on disk so re-enabling is instant.
ecaddy status
Show global Caddy state and per-site health (whether the upstream app is actually listening).
ecaddy status
Caddy service: running
Config: /Users/you/.config/caddy/Caddyfile
fishme up
fragment: /Users/you/.config/caddy/sites/fishme.caddy
source: /projects/fishme/Caddyfile
letly up (app not running)
fragment: /Users/you/.config/caddy/sites/letly.caddy
source: /projects/letly/Caddyfile
ecaddy doctor
Scan all registered sites cross-wise for port and domain conflicts, plus TCP-probe each upstream port.
ecaddy doctor
Exits 0 if all clear or only INFO findings. Exits 1 on any BLOCK.
| Severity | Meaning |
|---|---|
| BLOCK | Two sites share a port or domain — one will fail to bind |
| WARN | A port is bound by an unexpected process (not the registered upstream) |
| INFO | Upstream not listening (the project's app simply isn't running) |
ecaddy audit
Full system + TLS audit with optional --fix prompts. Where doctor looks at the registry, audit also probes the live Caddy service, brew-service state, TLS handshakes per domain, and the system-keychain trust state.
ecaddy audit # report-only
ecaddy audit --fix # prompt to run each suggested fix
ecaddy audit --site fishme # limit to one site
The --fix mode walks each collected finding, prints the proposed command, asks for confirmation, runs it, and re-verifies. If the primary fix doesn't resolve the finding, audit chains to a next_fix (e.g. caddy trust → sudo caddy trust).
Per-domain TLS checks also validate the leaf certificate's date window: a cert that is expired or not-yet-valid is reported as ERR_CERT_DATE_INVALID (rather than a misleading “browser-trusted”), with a fix that restarts Caddy to reissue it and escalates to ecaddy retrust. For a root-owned, unwritable log file, --fix offers an interactive choice — keep as-is, take ownership (sudo chown), or delete — and points to re-registering the site as the durable fix (the fragment is rewritten with mode 0660).
ecaddy edit NAME
Open a site's installed fragment in $EDITOR. Caddy is validated and reloaded after you save and exit the editor.
ecaddy edit fishme
ecaddy edit opens ~/.config/caddy/sites/fishme.caddy — the installed fragment, not your project's Caddyfile. Re-running ecaddy run (or ecaddy ensure) will overwrite your edits with the project source. Use this for quick experiments; for lasting changes edit the project Caddyfile and re-register.
ecaddy logs --site NAME
Tail the project's Caddy log files. ecaddy reads the fragment, extracts every output file PATH directive, and shells out to tail on them.
ecaddy logs --site fishme # tail -F (follow)
ecaddy logs --site fishme --lines 100 # tail -n 100, no follow
If the Caddyfile has no output file directives, ecaddy prints guidance and exits. Works for both enabled and disabled sites.
ecaddy remove NAME
Delete a site's fragment and registry entry entirely.
ecaddy remove fishme
ecaddy remove fishme --force # skip the confirmation prompt
ecaddy reload
Validate the global config with caddy validate and reload Caddy without restarting it. Equivalent to what run / ensure / up / down do internally after each change.
ecaddy reload
ecaddy retrust
Re-trust the local Caddy CA and reissue certificates. Run this when your browser shows net::ERR_CERT_DATE_INVALID or NET::ERR_CERT_AUTHORITY_INVALID on a *.localhost site — the cached leaf certificate has expired, or the local CA is missing from the system keychain.
ecaddy retrust
Runs caddy untrust (removes the old cert) then caddy trust (re-installs it), then restarts the Caddy service so it reissues the short-lived *.localhost leaf certificates. macOS prompts for your password for each keychain operation. Afterwards, fully reload your browser (or quit and reopen it) to drop the stale cached certificate.
ecaddy version
ecaddy version
# ecaddy 0.1.4
A note on the run command
run is a Thor reserved method name. Internally the method is caddy_run, exposed as run via map 'run' => :caddy_run in cli.rb. This is irrelevant from the CLI side — ecaddy run works as expected — but worth knowing if you crack open the source.