8. Deploy

maand deploy pushes job trees to workers and runs lifecycle Makefile targets.

maand deploy
maand deploy --jobs api
maand deploy --dry-run
maand deploy --force --jobs api

Prerequisites: successful maand build, SSH access, host tools (ssh, rsync, python3).


Deploy phases (simplified)

Once per deploy (before any deployment_seq wave):
  reconcile     → stop removed/disabled allocations
                  after_allocation_stopped hooks
                  cleanup (removed job files; purge KV when job fully removed)
  health_check  → jobs that stopped a running allocation and still have survivors

For each deployment_seq wave (dependency order):
  For each job in the wave:
    pre_deploy  → hook (CLI host; runs during plan-hash refresh, before skip check)
    stage       → tmp/workers/<ip>/jobs/<job>/ (plan hash / skip detection)

  When at least one job in the wave still needs work:
    update_seq (+1 once per deploy, before first rsync in this run)
    prepare worker.json / jobs.json / bin/

  For each job that needs rollout or post_deploy retry:
    stage       → tmp/workers/<ip>/jobs/<job>/ (again, with rendered .tpl + certs)
    rsync       → /opt/worker/<bucket_id>/
    plan hash   → update current_hash from staged tree
    rollout     → batched make start | restart | reload
                  after_allocation_started → promote batch → health_check
    post_deploy → hook (CLI host; job-wide wave)
    promote     → idempotent sweep (batches already promoted during rollout)

After all waves:
  final rsync   → per successfully deployed job (refresh jobs.json on workers)
  commit        → partial deploy allowed (failed jobs retry on next run)

Rolling happens within rollout using max_concurrent_starts / max_concurrent_upgrades. Promote runs per batch during rollout, not only after post_deploy. post_deploy runs after rollout completes (or alone when retrying a failed post_deploy).

Reference: deploy.md · deployment-sequence.md.


First deploy vs upgrade

Allocation state After rsync Lifecycle
New (no promoted hash) Files on worker make start
Existing, content or version changed Updated files make restart or make reload per policy
Unchanged hash + version skip (unless --force)

Inspect skip vs rollout:

maand deploy --dry-run
maand cat deployments --jobs api

restart_policy

Set in manifest.json:

Policy On upgrade
always (default) make restart
reload make reload, unless a changed file matches restart_globs → then restart
never rsync only; no lifecycle

Config-only push without lifecycle for one deploy:

maand deploy --sync-only --jobs api

Then manually: maand job run api --target reload if needed.

Reference: deploy.md.


Rolling batches

Manifest field Controls
max_concurrent_starts Batch size for start on first rollout
max_concurrent_upgrades Batch size for restart / reload on upgrades

Within each batch, order comes from rollout_order (KV key, overridable in pre_deploy).

Guide: rolling-deploy.md.


Deploy hooks (preview)

Event Runs on Typical use
pre_deploy CLI host, per allocation Secrets, put_rollout_order, migrations prep
post_deploy CLI host, per allocation Notify, KV updates
after_allocation_started CLI host Post-start validation
job_control CLI host Replace default start/restart with custom script

Details: 10-hooks.md.


Version tracking

Each allocation tracks:

A version bump can trigger rollout even when file hash is unchanged. Makefile gets CURRENT_VERSION and NEW_VERSION.


When deploy goes wrong

maand deploy --dry-run
maand cat deployments

Guide: debugging-deploy.md.


Next

09 — Configuration, KV, and templates.