5. Jobs and lifecycle

A job is a deployable unit: a directory under workspace/jobs/<name>/ with a manifest, Makefile, and supporting files.


Minimum layout

workspace/jobs/api/
├── manifest.json    # version, selectors, resources, commands, …
├── Makefile         # start, stop, restart (required for default deploy)
└── …                # configs, scripts, docker-compose.yml.tpl, etc.

Scaffold a new job:

maand job create api --selectors worker

Makefile contract

Default deploy expects these targets on the worker (after rsync):

Target When maand calls it
start First deploy to a new allocation
stop Reconcile removed/disabled allocation
restart Upgrade when restart_policy: always, or when restart_globs match
reload Upgrade when restart_policy: reload and globs don't force restart
status maand job status (optional but useful)

During lifecycle targets, the Makefile receives:

Use them for migrations or version-specific scripts.

You can add other targets (test, backup, …) for maand job run or maand run_command — maand only invokes the lifecycle set above during deploy.


Docker Compose or systemd behind Make

Maand does not start containers or units itself. Your Makefile wraps them:

Compose:

COMPOSE := docker compose -f docker-compose.yml

start:
	$(COMPOSE) up -d --remove-orphans

stop:
	$(COMPOSE) down

restart: stop start

systemd:

UNIT := api.service

start:
	sudo systemctl start $(UNIT)

stop:
	sudo systemctl stop $(UNIT)

restart: stop start

Deploy runs make start / restart / reload over SSH on each allocation.


Manifest essentials

{
  "version": "1.2.0",
  "selectors": ["worker", "web"],
  "max_concurrent_upgrades": 2,
  "restart_policy": "always",
  "resources": {
    "memory": { "min": "256 mb", "max": "512 mb" },
    "cpu": { "min": "200 mhz", "max": "500 mhz" },
    "ports": { "http": {} }
  }
}
Field Meaning
version Release id (semver-style); drives rollout when it changes
selectors Worker labels required for placement (chapter 6)
max_concurrent_upgrades Rolling batch size for restart/reload during deploy
restart_policy always / reload / never — see chapter 8
resources Bounds validated against worker capacity; ports from pool or fixed

Full schema: manifest.md.


What not to commit in workspace

Build rejects runtime dirs inside the job workspace:

Those are created on workers by your Makefile or application at runtime (and are excluded from rsync on upgrade).


Next

06 — Allocations and placement — how maand picks which worker runs which job.