Skip to content

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:

FlagDefaultWhat it does
--config PATH.meridian/deploy.ymlConfig file to load.
-h, --helpn/aPrint 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.

FlagDefaultWhat it does
--role ROLEall rolesSelect every host of one configured role.
--host HOSTall selected hostsSelect one configured host.
--primaryfalseSelect 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
FlagDefaultWhat it does
--host HOSTinferred only when the config has exactly one hostServer IP or hostname to provision.
--port PORTssh.port or 22SSH port for the initial root connection.
--root-user USERrootPrivileged user used before the deploy user exists.
--deploy-user USERssh.userUser to create for future Meridian commands.
--accept-new-host-keyenabledTrust new SSH host keys.
--no-accept-new-host-keydisabledRequire the host key to already be known.
--enable-auto-updates BOOLyesEnable unattended security updates.
--passwordless-sudo BOOLyesAllow passwordless sudo for the deploy user.
--rootless-low-ports BOOLyesAllow rootless containers to bind ports such as 80 and 443.
--rootless-port-start PORT80Lowest 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.yml

Uploads 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.com

Accepts 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.com

Accepts --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.yml

The 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 latest the 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 --primary

Accepts 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.com

Accepts 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
FlagDefaultWhat it does
--color COLORrequiredDeployment color to render, blue or green.
--output-dir DIR./quadlet-previewDirectory 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 dragonfly

For 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 postgres

See accessories.

accessory logs

Streams journalctl --user logs for one accessory. Follow-only, like logs. Returns the remote journalctl exit code.

bash
meridian accessory logs postgres

See 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
FlagDefaultWhat it does
--length N32Random byte length before encoding.
--format FORMAThexEncoding: hex, base64, or base64url.
--printfalsePrint locally instead of storing on remote hosts.
--forcefalseRotate an existing remote secret. Cannot be combined with --print.
--role ROLEwebTarget 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
FlagDefaultWhat it does
--value VALUEread from stdinSecret value to store.
--role ROLEwebTarget 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 workers

See 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 workers

It 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 status

See 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 release

Only 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
FlagDefaultWhat it does
--host HOSTall configured server and accessory hostsLimit output to one configured host.
--lines N20Entries 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.yml

It 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.

MIT License