CLI
Every command in the current Meridian CLI. Run meridian COMMAND --help for the authoritative flag list.
Common Flags
Every config-backed command accepts these, so they are not repeated per command:
| Flag | Default | What it does |
|---|---|---|
--config PATH | .meridian/deploy.yml | Config file to load. |
-h, --help | n/a | Print help and exit. |
init takes no --config — it is what writes the config in the first place.
Exit Codes
0 means the command completed, anything else means it failed. Validation errors, SSH failures, preflight probe failures, missing secrets, healthcheck timeouts, and deploy-lock contention all return non-zero. Two groups deviate and say so in their own section: check returns 1 specifically when a probe fails, and the streaming commands pass the remote exit code through.
Target Selectors
Commands that operate on configured role hosts can narrow the target set.
| Flag | Default | What it does |
|---|---|---|
--role ROLE | all roles | Select every host of one configured role. |
--host HOST | all selected hosts | Select one configured host. |
--primary | false | Select the first host of the web role. Cannot be combined with --role or --host. |
--role and --host together select exactly that pair, and fail if the host is not configured for the role.
init
Generates .meridian/deploy.yml for the current project by detecting the framework.
bash
meridian init
meridian init --force--force overwrites an existing config. Without it, an existing .meridian/deploy.yml is left alone and the command fails.
Writes .meridian/deploy.yml and .meridian/.gitignore locally. No host is contacted, no runtime state is written.
See service, image, servers.<role>, ssh.
server bootstrap
Provisions a fresh Debian or Ubuntu server so later commands can run as the deploy user. Expects root SSH with password login still enabled, and turns both off when it finishes.
bash
meridian server bootstrap --host 203.0.113.10
meridian server bootstrap --host prod-01.example.com --root-user ubuntu --deploy-user deploy| Flag | Default | What it does |
|---|---|---|
--host HOST | inferred only when the config has exactly one host | Server IP or hostname to provision. |
--port PORT | ssh.port or 22 | SSH port for the initial root connection. |
--root-user USER | root | Privileged user used before the deploy user exists. |
--deploy-user USER | ssh.user | User to create for future Meridian commands. |
--accept-new-host-key | enabled | Trust new SSH host keys. |
--no-accept-new-host-key | disabled | Require the host key to already be known. |
--enable-auto-updates BOOL | yes | Enable unattended security updates. |
--passwordless-sudo BOOL | yes | Allow passwordless sudo for the deploy user. |
--rootless-low-ports BOOL | yes | Allow rootless containers to bind ports such as 80 and 443. |
--rootless-port-start PORT | 80 | Lowest port rootless containers may bind. |
Installs Podman, UFW, transfer tools, and rootless prerequisites; creates the deploy user; installs your SSH key; enables lingering; configures low-port binding and SSH hardening. It writes no service runtime state.
See ssh, transfer, and kamal-proxy bind permission denied if low ports stay blocked afterwards.
setup
Installs or refreshes the shared host-level proxy and the networks it needs. Run it once per service, before the first deploy; it is safe to re-run.
bash
meridian setup
meridian setup --config config/production.ymlUploads and starts <service>.network on configured service hosts and on service-networked accessory hosts, uploads meridian-proxy.network and kamal-proxy.container to web hosts, creates proxy.data_dir, runs systemctl --user daemon-reload, and starts kamal-proxy. An already-running legacy proxy is connected to the shared network. No per-service release state is written.
See proxy and servers.<role>.proxy. Failures usually land in bind permission denied or Lets Encrypt issuance hangs.
proxy remove
Removes this service's kamal-proxy routes and its manifest, then removes the shared proxy if no other Meridian service is registered on the host.
bash
meridian proxy remove
meridian proxy remove --force--force removes the shared proxy even when other service manifests exist. That is a destructive host-level action and can interrupt other apps on the same server.
Appends an audit entry either way.
See proxy, servers.<role>.proxy, assets, and manifest-collisions: fail for ownership problems.
check
Runs read-only preflight probes against the selected hosts. Changes nothing.
bash
meridian check
meridian check --role web --host prod-01.example.comAccepts the target selectors.
Probes SSH, Podman, lingering, Quadlet directories, transfer tools, Podman secrets, local image availability for registry-free transfer, readability of every local files: source, kamal-proxy, the shared proxy network, accessory readiness, and same-host manifest collisions.
Two probes have detail worth knowing. A local files: source must be a readable regular file — the same thing the deploy reads — so a directory is reported as a failure. Accessory readiness is reported twice: every accessory sharing the service network gets a local row proving its readiness contract resolves, and accessories pinned to a checked host additionally get a live probe against that host.
Exit code 1 means at least one probe failed. Parse and config errors return other non-zero codes.
See transfer, env, proxy, servers.<role>.proxy.healthcheck, accessories.<name>.ready, and the pre-flight checklist for what check cannot infer.
deploy
Deploys the configured application to the selected hosts. Run setup first for a new service — deploy fails if the service network is missing.
bash
meridian deploy
meridian deploy --role web --host prod-01.example.comAccepts --role and --host from the target selectors, except under strategy: recreate, where any subset is rejected before SSH.
Runs local validation and pre_deploy, then acquires the remote deploy lock, verifies the service network, runs remote hooks, transfers images, uploads app Quadlets/files/asset units, and starts new units. Proxied managed roles then switch kamal-proxy and write active-color plus release-state.json; other managed roles restart their stable <service>-<role> unit without proxy state. Finally writes manifest.json, appends audit entries, and releases the lock in an ensure block.
With strategy: recreate, Meridian first transfers every role image and uploads all new Quadlets on the service's single host. On a redeploy it then runs kamal-proxy stop, stops active secondary roles, and stops the old web colour before starting anything new. The new web colour must pass its direct container healthcheck before secondary roles start. Traffic resumes only after every role, the proxy target, and runtime state are ready. Accessories remain running.
If Recreate fails after maintenance begins, Meridian deliberately leaves the route stopped and never restarts the old image. The error names the units and logs to inspect and prints the manual kamal-proxy resume command.
Lock contention is reported as a normal failed deploy, not a separate numeric code.
See servers.<role>, boot, transfer, registry, files, assets, hooks. The common first-deploy failures are stale deploy lock, healthcheck timeout, image not known, and Hostname Lookup ... Try Again.
rollback
Restores the previously deployed release on each proxied web host.
bash
meridian rollback
meridian rollback --config config/production.ymlThe old container does not survive a successful deploy, so rollback reconstructs it: it reads release-state.json, regenerates the Quadlet for the recorded image and color, uploads it, reloads the user systemd daemon, and starts the unit fresh. kamal-proxy switches back only after the reconstructed release passes the regular container health check. Then the rolled-back-from release is stopped and its Quadlet removed, active-color and release-state.json are rewritten (current and previous swap), and an audit entry is appended.
If the health check or proxy switch fails, the candidate is torn down and the currently active release keeps serving. On legacy hosts without release state, rollback falls back to restarting the surviving inactive-color container.
Two limits decide whether rollback is available at all:
- Only the proxied web role is rolled back. Secondary roles go back by deploying the previous image.
- The previous release's image must still be on the host. With a reused tag such as
latestthe next deploy retags or prunes it, and rollback refuses to run.
Non-image configuration — env, volumes, ports, command — comes from the current config file, not from the previous release.
strategy: recreate is rejected before SSH. Recreate releases may have migrated persistent data, so an image-only rollback is unsafe; restore the image, database, and persistent volumes together from a matching backup.
See servers.<role>.proxy and proxy.
status
Shows deployed service state for every selected role. Reads only.
bash
meridian status
meridian status --primaryAccepts the target selectors.
Columns are role, host, release, deployment, and state. It reads user systemd state and, for proxied roles, service-scoped release-state.json when present. Non-proxied managed roles report their stable role unit; unmanaged roles summarize their configured units. Recreate services report recreate in the deployment column rather than blue/green, including their secondary role rows.
See servers.<role> and boot.
logs
Streams journalctl --user logs for the selected service units.
bash
meridian logs
meridian logs --host prod-01.example.comAccepts the target selectors.
Proxied roles select both colour units, non-proxied managed roles select <service>-<role>.service, unmanaged roles select their configured units.
Follow-only: there is no --lines and no --no-follow. For a historical slice, SSH in directly — healthcheck timeout shows the journalctl invocation. Returns the remote journalctl exit code.
See servers.<role>.
exec
Runs a command inside the already running container for one role.
bash
meridian exec web -- bin/rails db:migrate:status
meridian exec web --host prod-01.example.com -- printenv MARTEN_ENV--host HOST picks which configured role host to exec into; it defaults to the first host of the role.
Proxied roles resolve their active colour, non-proxied managed roles target <service>-<role> directly. Unmanaged roles are rejected — a list of arbitrary systemd units does not identify one container name.
Meridian changes no state, but the command you run may mutate application data. Returns the streamed remote command's exit code. Use status and logs when the active container cannot be resolved.
See servers.<role>.
run
Runs a one-off command in a fresh container on the service network — the difference to exec, which reuses the running one.
bash
meridian run web -- bin/rails db:migrate
meridian run workers --host prod-02.example.com -- crystal eval 'puts 1'--host HOST picks which configured role host runs the container; it defaults to the first host of the role.
The container joins the setup-created <service> Podman network and is removed on exit. Meridian writes no runtime state, but the command may mutate application data or connected services. Returns the remote podman run exit code.
Run setup first if the service network does not exist yet.
See image, env, accessories, and Hostname Lookup ... Try Again for dependency startup problems.
quadlet
Renders Quadlet files locally so you can read them before a deploy writes them. No host is contacted.
bash
meridian quadlet --color green
meridian quadlet --color blue --output-dir ./tmp/quadlets| Flag | Default | What it does |
|---|---|---|
--color COLOR | required | Deployment color to render, blue or green. |
--output-dir DIR | ./quadlet-preview | Directory for generated preview files. |
Compare the output against what is a Quadlet.
See servers.<role>, volumes, ports, files, assets.
accessory start
Uploads and starts one configured accessory. Accessories are never started by deploy; this is the only command that starts them.
bash
meridian accessory start postgres
meridian accessory start dragonflyFor accessories that reference network: <service>.network, it first verifies that setup has materialized the service network. Then it uploads the accessory Quadlet to the accessory host, reloads user systemd, starts <name>.service, and appends an audit entry. App active-color and release-state.json are untouched.
See accessories, accessories.<name>.ready, and Hostname Lookup ... Try Again for readiness and DNS symptoms.
accessory stop
Stops <name>.service on its configured host and appends an audit entry. The Quadlet file and the app's runtime state are left alone.
bash
meridian accessory stop postgresSee accessories.
accessory logs
Streams journalctl --user logs for one accessory. Follow-only, like logs. Returns the remote journalctl exit code.
bash
meridian accessory logs postgresSee accessories.
secret gen
Generates a random Podman secret and stores it on every host in the target role.
bash
meridian secret gen SECRET_KEY_BASE
meridian secret gen JWT_SECRET --format base64url --role workers| Flag | Default | What it does |
|---|---|---|
--length N | 32 | Random byte length before encoding. |
--format FORMAT | hex | Encoding: hex, base64, or base64url. |
--print | false | Print locally instead of storing on remote hosts. |
--force | false | Rotate an existing remote secret. Cannot be combined with --print. |
--role ROLE | web | Target role. |
Without --force the command refuses to overwrite an existing name. Run secret ls first when rotating.
See env.secret.
secret set
Creates or replaces a Podman secret with a value you supply. Unlike secret gen it always replaces.
bash
printf '%s\n' "$DATABASE_URL" | meridian secret set DATABASE_URL
meridian secret set API_TOKEN --value 's3cr3t' --role workers| Flag | Default | What it does |
|---|---|---|
--value VALUE | read from stdin | Secret value to store. |
--role ROLE | web | Target role. |
Prefer stdin over --value — a value passed as a flag ends up in your shell history.
Removes any existing remote secret with the same name, then creates the replacement on every host in the role. Missing deploy-time secrets surface in check.
See env.secret.
secret ls
Lists Podman secrets on every host in one role. --role ROLE defaults to web.
bash
meridian secret ls
meridian secret ls --role workersSee env.secret.
secret rm
Removes one Podman secret from every host in one role. --role ROLE defaults to web.
bash
meridian secret rm OLD_TOKEN
meridian secret rm OLD_TOKEN --role workersIt does not edit deploy.yml. Remove the name from env.secret yourself, otherwise the next check fails on the now-missing secret.
See env.secret.
lock status
Shows whether the remote deploy lock is held, by reading ~/.local/state/meridian/services/<service>/lock/meta.json on the lock host.
bash
meridian lock statusSee Stale deploy lock.
lock acquire
Manually acquires the remote deploy lock. While held, deploys and other acquisitions fail.
bash
meridian lock acquire --message 'database maintenance'--message MESSAGE is recorded in the lock metadata and shown by lock status. Appends an audit entry.
lock release
Removes the remote lock directory and appends an audit entry.
bash
meridian lock releaseOnly run this after confirming no deploy, rollback, or proxy mutation is still active — see Stale deploy lock for how to check.
audit
Prints recent Meridian audit entries per host by reading audit.log on each selected host.
bash
meridian audit
meridian audit --host prod-01.example.com --lines 50| Flag | Default | What it does |
|---|---|---|
--host HOST | all configured server and accessory hosts | Limit output to one configured host. |
--lines N | 20 | Entries to show per host. |
Entries cover deploy, rollback, proxy, accessory, and lock operations. This is the first thing to read when diagnosing a stale deploy lock.
See service, servers.<role>, accessories.
plan
Prints the resolved deploy intent from local config only. No SSH, no registry calls, and secret values are never printed. Run it after every config edit.
bash
meridian plan
meridian plan --config config/production.ymlIt loads the same strict schema as deploy, so a config error shows up here first. The header includes the effective strategy: blue_green, recreate, or restart_in_place. Every field in deploy.yml affects the output.
