Concepts
Meridian is an imperative deploy tool: one CLI process connects over SSH, writes Podman Quadlets, asks user systemd to reload, and uses kamal-proxy for proxied blue/green cutovers. This page is the mental model for reading deploy logs, debugging failures, and running multiple apps on one host.
Deploy Flow
Proxied web deploys follow this sequence on each selected host:
operator
|
| meridian deploy
v
lock -> verify service network -> transfer image -> upload Quadlets/files/assets -> daemon-reload
-> wait_for_accessories -> before_start hooks -> asset build
-> start new color -> temporary health probe
-> before_switch hooks -> kamal-proxy deploy
-> after_switch hooks -> stop old color
-> record active color + release + manifest -> after_deploy hooks
-> release lock- Acquire the deploy lock before remote mutation starts.
- Verify the setup-created service network.
- Transfer the selected image by registry pull,
stream, orincremental. - Upload the new color
.container, file syncs, and asset units. - Run
systemctl --user daemon-reload. - Wait for co-network accessories to pass readiness probes.
- Run remote
before_starthooks. - Run the asset builder when
assets:is configured. - Start the inactive color, for example
my-app-green.service. - Poll the new container from a temporary probe container on
meridian-proxyuntilhealthcheck.required_successesconsecutive HTTP successes pass. - Run remote
before_switchhooks. - Run
kamal-proxy deployto atomically switch traffic to the new color. - Run remote
after_switchhooks. - Stop the old color and remove its inactive Quadlet file.
- Record
active-color,release-state.json, andmanifest.json. - Run remote
after_deployhooks and release the deploy lock.
For field-level details, see servers.<role>.proxy.healthcheck, accessories.<name>.ready, and hooks.
What Is A Quadlet
A Quadlet is a declarative Podman file under ~/.config/containers/systemd/. After systemctl --user daemon-reload, systemd generates normal user units from those files, so containers are started, stopped, logged, and restarted through systemd.
~/.config/containers/systemd/
my-app.network
my-app-blue.container
my-app-green.container
my-app-postgres.container
my-app-assets.volume
my-app-assets-builder.container
my-app-assets-server.container
kamal-proxy.container
meridian-proxy.network| File | Purpose |
|---|---|
<service>.network | Private Podman network for one app and its accessories. |
<service>-<color>.container | Blue or green app container for a managed role. |
<accessory>.container | Standalone accessory service such as Postgres or Redis. |
<service>-assets-* | Asset volume, builder, and static-server units when assets: is configured. |
kamal-proxy.container | Shared host-level proxy container. |
meridian-proxy.network | Shared network kamal-proxy and proxied app containers join. |
Use meridian quadlet to preview generated files locally. meridian setup owns uploading and starting <service>.network on every host that needs the private service network; deploys, one-off runs, and service-networked accessories verify that the materialized Podman network <service> exists before they use it.
Deploy-Managed Static Assets
When deploy.yml declares an assets: block, Meridian publishes your built front-end bundle as part of the deploy:
- A one-shot builder container runs
assets.commandin the app image. - Its
assets.output_diroutput is copied into a timestamped release directory on the<service>-assetsvolume. - A
currentsymlink is repointed to the new release. - A generated Caddy static server serves
currentand is registered with kamal-proxy under the<service>-assetsroute onassets.host.
Old releases are retained (assets.retain_releases) so fingerprinted URLs from the previous version keep resolving during the rollout window. The framework's asset URL setting must point at assets.host; see assets and, for fingerprinted-URL mistakes, CSS url() 404s.
Per-Service Runtime State
Meridian stores runtime state per service, not globally:
~/.local/state/meridian/services/my-app/
active-color
manifest.json
release-state.json
lock/
meta.json
audit.log| File | Purpose | Read by | Written by |
|---|---|---|---|
active-color | Current proxied color, blue or green. | status, logs, exec, rollback | deploy, rollback |
manifest.json | Ownership manifest for proxy routes, assets, ports, accessories, generated files, and state paths. | check, proxy remove | deploy |
release-state.json | Current and previous rollback-safe releases. | status, rollback | deploy, rollback |
lock/meta.json | Deploy lock holder, timestamp, and optional message. | lock status, deploy | deploy, lock acquire, lock release |
audit.log | Line-oriented history of deploy, rollback, proxy, accessory, and lock operations. | audit | mutating commands |
This layout lets multiple Meridian services share a host without sharing state.
Same-Host Multi-App Topology
Each app owns its private service network. Proxied app containers also join the shared meridian-proxy network, where one kamal-proxy can reach all apps.
public HTTP(S)
|
v
kamal-proxy.container
|
meridian-proxy.network
/ \
my-app-green my-blog-blue
| |
my-app.network my-blog.network
/ \ / \
postgres dragonfly sqlite redisAccessories attach to their app's private network, not to meridian-proxy, unless you explicitly configure something else. manifest.json collision checks make meridian check fail if two services claim the same proxy host/path, asset host, published host port, accessory name, generated file, or state path.
For a worked setup, see Multi-App Hosting.
Blue/Green
Meridian keeps one active color and one candidate color for proxied managed roles. If active-color says blue, the next deploy starts green; if it says green, the next deploy starts blue.
before deploy: active-color=blue proxy -> my-app-blue
during deploy: blue serves traffic, green starts and passes health
after switch: active-color=green proxy -> my-app-green
cleanup: old blue unit is stopped and its Quadlet is removedThe inactive Quadlet is removed after a successful switch, but release metadata keeps the previous rollback-safe release. meridian rollback reads release-state.json, starts the previous color if needed, runs kamal-proxy in reverse, rewrites active-color, swaps current/previous release metadata, and records an audit entry.
