ember [flags]
| Flag | Type | Default | Description |
|---|---|---|---|
--addr |
string (repeatable) | http://localhost:2019 |
Caddy admin API address (http://, https://, or unix//path). Repeatable in --daemon, --json, status, and wait modes to monitor multiple instances. Supports name=url aliases and per-instance suffixes (,ca=PATH, ,cert=PATH, ,key=PATH, ,insecure, ,interval=DURATION); see Multi-instance. |
-i, --interval |
duration | 1s |
Polling interval |
--timeout |
duration | 0 (none) |
Global timeout for the non-interactive modes and subcommands (--json, --daemon, status, wait). The interactive TUI ignores it, and version --check uses a fixed 10s timeout. 0 means no timeout. |
--slow-threshold |
int | 500 |
Slow request threshold in milliseconds. Requests above this are highlighted yellow; above 2x are red. |
--frankenphp-pid |
int | 0 (auto) |
FrankenPHP PID. Auto-detected if not set. |
--json |
bool | false |
JSON output mode (streaming JSONL). See JSON Output. |
--once |
bool | false |
Output a single snapshot and exit. Requires --json. See JSON Output. |
--expose |
string | (none) | Start Prometheus metrics endpoint on this address (e.g. :9191). See Prometheus Export. |
--daemon |
bool | false |
Headless mode: no TUI. Requires --expose. See Prometheus Export. |
--metrics-prefix |
string | (none) | Prefix for exported Prometheus metric names. See Prometheus Export. |
--log-format |
string | text |
Log format for daemon/json modes (text or json). JSON format is suitable for log aggregation systems. |
--ca-cert |
string | (none) | Path to CA certificate for TLS verification |
--client-cert |
string | (none) | Path to client certificate for mTLS; must be paired with --client-key |
--client-key |
string | (none) | Path to client private key for mTLS |
--insecure |
bool | false |
Skip TLS certificate verification |
-f, --config |
string | .ember.toml |
Path to the Ember config file (TOML). Read only when neither --addr nor EMBER_ADDR is set. See Config file. |
--metrics-auth |
string | (none) | Basic auth for the metrics endpoint (user:password). Requires --expose. See Prometheus Export. |
--log-listen |
string | (auto) | Bind a TCP listener at this address (e.g. :9210) and ask Caddy to push its logs to it via two hot-registered sinks (access + runtime). Required when Caddy is on a remote host; auto-bound on a free loopback port otherwise. See Logs. |
--stdin-logs, --from-stdin |
bool | false |
Read Caddy logs directly from stdin instead of registering a net_writer via Caddy's Admin API. Ideal for Kubernetes / unidirectional environments. |
--no-color |
bool | false |
Disable colors. Also enabled by the NO_COLOR env var (see no-color.org). |
--version |
Print version and exit |
Some flags can be set via environment variables. Explicit flags always take precedence over environment variables. A variable exported with no value is treated as unset, so a .env file or a ConfigMap carrying an empty key leaves the default in place instead of aborting the command.
| Variable | Flag | Example |
|---|---|---|
EMBER_ADDR |
--addr |
EMBER_ADDR=http://caddy:2019, EMBER_ADDR=unix//run/caddy/admin.sock, or semicolon-separated for multi-instance: EMBER_ADDR=web1=https://a;web2=https://b. The semicolon (not a comma) is the address separator so each entry can carry comma-separated TLS suffixes (e.g. web1=https://a,ca=/p/ca1.pem;web2=https://b,insecure). |
EMBER_INTERVAL |
--interval |
EMBER_INTERVAL=5s |
EMBER_EXPOSE |
--expose |
EMBER_EXPOSE=:9191 |
EMBER_METRICS_PREFIX |
--metrics-prefix |
EMBER_METRICS_PREFIX=myapp |
EMBER_METRICS_AUTH |
--metrics-auth |
EMBER_METRICS_AUTH=admin:secret |
EMBER_LOG_LISTEN |
--log-listen |
EMBER_LOG_LISTEN=:9210 |
EMBER_STDIN_LOGS |
--stdin-logs, --from-stdin |
EMBER_STDIN_LOGS=true |
CADDY_API_URL |
--addr |
CADDY_API_URL=http://localhost:2019 |
EMBER_CONFIG |
--config |
EMBER_CONFIG=/etc/ember/prod.toml |
CADDY_API_URL is read before EMBER_ADDR and wins when both hold a value; an empty or blank one is ignored so it cannot mask EMBER_ADDR. Either of them setting --addr also skips the config file, the same way an explicit --addr does.
This is especially useful in container deployments where flags are less convenient than environment variables. Using EMBER_METRICS_AUTH is recommended over the flag to avoid exposing credentials in ps output.
Instead of repeating --addr on every run, the fleet can be described once in a TOML file (.ember.toml in the current directory by default). Point at another file with -f/--config or EMBER_CONFIG, which is handy for per-environment fleets (.ember.prod.toml, .ember.staging.toml).
default = "production" # optional, used by the TUI only
# Top-level keys are global fallbacks for every endpoint.
interval = "2s"
ca_cert = "/etc/ca.pem"
[[endpoints]]
name = "production"
addr = "https://prod:2019"
[[endpoints]]
name = "staging"
addr = "https://staging:2019"
insecure = true # overrides the global for this instance onlyThe per-endpoint keys (ca_cert, cert, key, insecure, interval) mirror the --addr suffixes; a key set inside an [[endpoints]] table overrides the same top-level key, exactly like ,ca= overrides --ca-cert. Endpoint names must start with a letter and contain only letters, digits and underscores.
--addr > EMBER_ADDR > config file > built-in default (http://localhost:2019). If --addr or EMBER_ADDR is set, the file is ignored entirely (no merge). A missing default .ember.toml falls back silently to http://localhost:2019; an explicit -f/EMBER_CONFIG that is missing, or any malformed file, is a hard error.
- TUI (
ember): single-instance. Zero endpoints uses the built-in default; one endpoint uses it; two or more withdefaultset use that one; two or more withoutdefaultshow a small picker before the dashboard mounts. --daemon,--json,status,wait,init: the file expands to N endpoints, exactly as if each had been passed as--addr name=url.
diff and version never read the file.
# Default: connect to localhost:2019
ember
# Connect to a remote Caddy instance
ember --addr http://prod:2019
# Connect via Unix socket
ember --addr unix//run/caddy/admin.sock
# Pipe-friendly JSON output
ember --json
# Single JSON snapshot and exit
ember --json --once
# JSON output at a slower rate
ember --json --interval 5s
# TUI with Prometheus metrics on port 9191
ember --expose :9191
# Headless metrics exporter (no TUI)
ember --expose :9191 --daemon
# Stricter slow-request highlighting
ember --slow-threshold 200
# Prefixed Prometheus metrics
ember --expose :9191 --metrics-prefix myapp
# Explicitly specify a FrankenPHP PID
ember --frankenphp-pid 42A single ember --daemon (or ember --json) process can poll several Caddy instances and aggregate their metrics behind one Prometheus endpoint. This keeps the sidecar count low when running a fleet of small services.
# Explicit aliases
ember --daemon --expose :9191 \
--addr web1=https://web1.fr \
--addr web2=https://web2.fr
# Without aliases the host is slugified (web1.fr -> web1_fr)
ember --daemon --expose :9191 \
--addr https://web1.fr \
--addr https://web2.fr
# Semicolon-separated env var (each entry may carry comma-separated TLS suffixes)
EMBER_ADDR='web1=https://a;web2=https://b' ember --daemon --expose :9191When two or more --addr values are supplied, every emitted Prometheus metric (except ember_build_info) gains an ember_instance="<name>" label. With a single --addr the output is unchanged: no extra label.
The interactive TUI stays single-instance by design. Watching several Caddy hosts at the same time is what --daemon (Prometheus aggregation, then visualised with Grafana or any compatible dashboard) and --json (one JSONL line per instance per tick, ready for downstream tooling) are for. Passing repeated --addr to the default TUI mode fails fast with an explicit error pointing at those modes.
Each --addr value can carry its own TLS knobs as comma-separated suffixes after the URL. Any field omitted falls back to the corresponding global flag (--ca-cert, --client-cert, --client-key, --insecure).
| Suffix | Equivalent global flag | Notes |
|---|---|---|
,ca=PATH |
--ca-cert |
CA certificate used to verify the server |
,cert=PATH |
--client-cert |
Client certificate (mTLS); must be paired with key= |
,key=PATH |
--client-key |
Client private key (mTLS); must be paired with cert= |
,insecure or ,insecure=true|false |
--insecure |
Skip TLS verification; explicit =false overrides a global --insecure |
ember --daemon --expose :9191 \
--addr web1=https://a,ca=/etc/ca1.pem \
--addr web2=https://b,ca=/etc/ca2.pemOn SIGHUP, each instance re-reads its own certificate files independently, so per-instance certs can be rotated in place without restarting Ember.
Each --addr can override the global --interval with a ,interval=DURATION suffix. This matters for fleets where some instances are remote and benefit from a slower cadence without raising the global default.
ember --daemon --expose :9191 \
--addr web1=https://web1.fr \
--addr remote=https://far.example,interval=10sActive in --daemon and --json modes (each instance polls its own ticker), and in the single-instance TUI, which polls at its endpoint's interval. The /healthz and /healthz/<name> staleness threshold is computed per-instance, so a slow instance is not flagged stale just because its cadence is slower than the global default. status and wait keep using the global interval.
Constraints:
- File paths in suffixes cannot contain a literal comma; the comma is reserved as the suffix separator.
- TLS suffixes are rejected on
unix//addresses (no TLS over a Unix socket). - Per-instance
interval=is allowed onunix//addresses (it is not a TLS option). - Per-instance
interval=must be at least100ms. When--timeoutis set, it must be at least the largest effective polling interval (global or per-instance). --daemon,--json,status,wait,diff, andinit(with-y) accept multi-instance input. The TUI default mode is single-instance by design and refuses repeated--addrwith an explicit error pointing at--daemonand--json.- Instance names must match
[a-zA-Z_][a-zA-Z0-9_]*(Prometheus label rules: letters, digits and underscores only — no hyphens or dots). With more than one address, slugified names that start with a digit (typical for raw IPv4 hosts) require an explicitname=urlalias. --frankenphp-pidis ignored when--addris repeated. Local instances (localhost,127.0.0.1,::1orunix//) get per-instanceprocess_cpu_percent/process_rss_bytesvia auto-detection on the admin endpoint; remote instances silently fall back to theprocess_*metrics exposed by Caddy.- Plugins are skipped in multi-instance mode unless they opt in by implementing
plugin.MultiInstancePlugin. Non-aware plugins are disabled with a startup warning. See the Plugin Development Guide for the multi-instance contract.
See Prometheus Export for the resulting metric labels and /healthz body.
Checks that Caddy is properly configured for Ember and offers to enable HTTP metrics via the admin API if they are missing. Does not modify any files on disk.
What it checks:
- Admin API is reachable
- HTTP servers are configured
- HTTP metrics directive is enabled
- FrankenPHP presence, threads, and workers
- Metrics are actually flowing
What it can do:
- Enable the
metricsdirective viaPOST /config/apps/http/metrics(no Caddy restart needed)
Examples:
ember init
ember init --addr https://prod:2019 --ca-cert ca.pem
ember init -y # skip confirmation prompts
ember init -yq # skip prompts, suppress output
ember init -y --addr web1=https://a --addr web2=https://b # multi-instance--quiet (-q) also requires --yes (-y): the confirmation prompt would otherwise be written to a discarded stream while the command waits for an answer nobody can see it wants.
With multiple --addr values, the checklist is run against every instance, output is grouped per instance under a --- <name> --- header sorted by name, and the exit code is non-zero if any instance fails. Interactive prompts are disabled in multi-instance mode: --yes (-y) is required, otherwise the command errors out.
| Flag | Type | Default | Description |
|---|---|---|---|
-y, --yes |
bool | false |
Skip confirmation prompts (required when multiple --addr are provided) |
-q, --quiet |
bool | false |
Suppress output (errors still reported via exit code); requires -y, since a confirmation prompt cannot be shown |
Prints the current version. With --check, queries the GitHub Releases API to see if a newer version is available.
Examples:
ember version
ember version --check| Flag | Type | Default | Description |
|---|---|---|---|
--check |
bool | false |
Check for newer version on GitHub |
One-line health check of Caddy. Performs two fetches separated by the polling interval to compute derived metrics, then prints a compact status line and exits.
Exit code 0 means Caddy is reachable, 1 means unreachable.
Output format:
Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h
With FrankenPHP:
Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h | FrankenPHP 8/20 busy | 2 workers
If Caddy is unreachable:
Caddy UNREACHABLE | http://localhost:2019
Examples:
ember status
ember status --json
ember status --json | jq .rps
ember status --addr http://prod:2019
ember status --interval 2s
ember status --addr web1=https://a --addr web2=https://b # multi-instanceWhen --addr is repeated, ember status checks every instance in parallel and emits one block per instance, sorted alphabetically by name. Exit code is 0 only when all instances are reachable.
Text output:
[web1] Caddy OK | 5 hosts | 450 rps | P99 12ms | CPU 3.2% | RSS 48MB | up 3d 2h
[web2] Caddy UNREACHABLE | https://web2.fr
JSON output (--json):
{
"status": "degraded",
"instances": [
{"name": "web1", "addr": "https://web1.fr", "status": "ok", "rps": 450, "cpuPercent": 3.2, "rssBytes": 50331648, "p99": 12.3},
{"name": "web2", "addr": "https://web2.fr", "status": "unreachable", "rps": 0, "cpuPercent": 0, "rssBytes": 0}
]
}The top-level status is the worst of: ok (every instance reachable) < degraded (mixed) < unreachable (every instance down). FrankenPHP fields are emitted per instance when detected.
With --json (single-instance mode), the output is a JSON object:
{
"status": "ok",
"hosts": 5,
"rps": 450,
"p99": 12.3,
"cpuPercent": 3.2,
"rssBytes": 50331648,
"uptime": "3d 2h",
"frankenphp": { "busy": 8, "total": 20, "workers": 2 }
}| Flag | Type | Default | Description |
|---|---|---|---|
--json |
bool | false |
Output status as JSON |
The --addr, --interval, and --frankenphp-pid flags are also available.
Blocks until the Caddy admin API is reachable, then exits with code 0. If --timeout is set and Caddy is still unreachable, exits with code 1.
With repeated --addr, ember wait blocks until every instance is reachable (strict CI semantics). Pass --any to return as soon as the first one responds. On timeout, the error names the lagging instance(s).
Examples:
ember wait
ember wait --timeout 30s
ember wait -q --timeout 10s && ./deploy.sh
ember wait --addr http://prod:2019 && ember status
docker compose up -d && ember wait && ./deploy.sh
ember wait --addr web1=https://a --addr web2=https://b --timeout 30s
ember wait --any --addr http://primary --addr http://fallback| Flag | Type | Default | Description |
|---|---|---|---|
-q, --quiet |
bool | false |
Suppress output (exit code only) |
--any |
bool | false |
Return as soon as any instance is reachable (default: wait for all) |
The --addr, --interval, and --timeout flags are also available. --addr may be repeated to wait on multiple instances (with optional name=url aliases).
Compares two JSON or JSONL snapshots produced by ember --json --once and shows the deltas for key metrics. Useful for validating deployments and benchmarks.
Exit code 0 means no regressions detected, 1 means regressions found (>10% degradation on latency, error rate, or CPU; >10% drop on RPS).
A snapshot that recorded fetch errors and came back with no metrics is refused: comparing two captures Ember could not collect would report "no regressions" while it never reached Caddy. A capture whose metrics did arrive is still compared, whatever else failed alongside them.
When the cumulative counters (Requests, Avg (cumul.), Errors) went backwards between the two captures, Caddy restarted in between and those three lines are left out rather than scored as a 100% collapse. The gauges beside them still compare.
Examples:
ember --json --once > before.json
# ... deploy or benchmark ...
ember --json --once > after.json
ember diff before.json after.jsonSample output:
Global
Requests 1720 -> 2023 +17.6%
Avg (cumul.) 76.9ms -> 77.7ms +1.0%
Errors 0 -> 0 =
In-flight 3.0 -> 1.0 -66.7%
CPU 0 -> 0 =
RSS 39.5MB -> 39.5MB =
Per-host changes
api
In-flight 3.0 -> 1.0 -66.7%
app
In-flight 1.0 -> 0 -100.0%
No regressions detected
Multi-instance input:
When a snapshot was produced with repeated --addr (multi-instance JSONL with one line per instance per tick), ember diff groups lines by the instance field, keeps the last entry per instance in each file, and emits one diff block per instance. The exit code is non-zero if any single instance regresses. An instance present in only one of the two files is reported as absent rather than diffed against zero: missing from the second capture counts as a regression, present only in the second one does not.
ember --json --once --addr web1=https://web1.fr --addr web2=https://web2.fr > before.jsonl
# ... deploy ...
ember --json --once --addr web1=https://web1.fr --addr web2=https://web2.fr > after.jsonl
ember diff before.jsonl after.jsonl== web1 ==
Global
RPS 100/s -> 105/s +5.0%
Avg latency 20.0ms -> 19.0ms -5.0%
...
== web2 ==
Global
RPS 200/s -> 210/s +5.0%
Avg latency 15.0ms -> 14.0ms -6.7%
...
No regressions detected
Sets the top-level default key in the config file to <name>, so the single-instance TUI connects to that endpoint instead of showing the picker. Only the default line is rewritten; comments and formatting are left intact. <name> must match one of the file's endpoints.
ember config use production
ember -f .ember.staging.toml config use staging| Key | Action |
|---|---|
Up / Down / j / k |
Move cursor |
Enter / Right / l |
Open detail panel / expand node (Caddy Config tab) |
Left / h |
Collapse node (Caddy Config tab) |
Esc |
Close panel / clear search / go back |
Tab / Shift+Tab |
Switch tab |
1 / 2 / 3 |
Jump to tab |
t then 1..9 |
Tab-select prefix: arm tab-select mode, next digit switches tab (Esc cancels) |
Home / End |
Jump to first / last item |
PgUp / PgDn |
Page up / page down |
| Key | Action | Context |
|---|---|---|
s / S |
Cycle sort field forward / backward | Caddy / FrankenPHP tabs |
p |
Pause / resume polling | Any view |
/ |
Enter filter / search mode | Any tab |
e / E |
Expand / collapse all nodes | Config tab |
n / N |
Jump to next / previous search match | Config tab |
r |
Refresh config / restart workers | Config tab / FrankenPHP tab |
g |
Toggle full-screen graphs | Any view |
? |
Toggle help overlay | Any view |
q |
Quit | Any view |
ember completion bash > /etc/bash_completion.d/emberember completion zsh > "${fpath[1]}/_ember"ember completion fish > ~/.config/fish/completions/ember.fish