3. Bucket and workspace
A bucket is one maand project — a directory you cd into before running maand commands.
Create a bucket
mkdir my-cluster && cd my-cluster
maand init
maand init creates (or upgrades) layout, generates a bucket CA, encryption key for KV, and an empty workers.json.
Verify:
ls
# maand.conf data/ workspace/ secrets/ tmp/ logs/
Each bucket has a stable bucket_id (UUID). Workers store it in /opt/worker/<bucket_id>/worker.json so multiple buckets can coexist on one host under different IDs.
What you edit vs what maand owns
| Path | You edit? | Purpose |
|---|---|---|
workspace/ |
Yes (git) | Workers, jobs, optional disabled.json, bucket.conf |
maand.conf |
Yes | SSH user/key, sudo, cert TTL, environment selector |
secrets/ |
Carefully | SSH private key, CA, KV key — not usually in git |
data/maand.db |
No | SQLite catalog — output of build |
tmp/ |
No | Deploy staging (ephemeral) |
logs/ |
No | Maand command logs (deploy, rsync, SSH) |
Rule of thumb: if it's under workspace/, you change it and run maand build. If it's deploy output on workers, you change it via maand deploy (or job Makefile at runtime).
maand.conf essentials
ssh_user = "agent"
ssh_key = "worker.key" # file under secrets/
use_sudo = true # wrap remote commands in sudo when needed
# Optional:
job_config_selector = "prod" # loads bucket.jobs.prod.conf for reservations
log_format = "kv" # or json / jsonl for logs/*.log
Copy your SSH private key:
cp ~/.ssh/id_ed25519 secrets/worker.key
chmod 600 secrets/worker.key
The matching public key must be in authorized_keys for ssh_user on every worker.
Full reference: configuration.md.
Workspace layout
workspace/
├── workers.json # cluster nodes
├── disabled.json # optional: drain without delete
├── bucket.conf # optional: shared ports, settings
├── bucket.jobs.conf # optional: per-job CPU/memory reservations
├── bucket.jobs.prod.conf # optional: when job_config_selector = prod
└── jobs/
└── api/
├── manifest.json
├── Makefile
├── config.tpl # optional
└── _hooks/ # optional: hook scripts
Do not put runtime data/, logs/, or bin/ inside workspace job folders — build rejects them. Those directories exist on workers after deploy.
CLI host requirements
On the machine where you run maand:
maandbinary (built withCGO_ENABLED=1for SQLite)bash,ssh,rsync,python3(andbunif any hook uses it)
On workers (checked at deploy / run_command):
bash,sshserver,rsync,python3,make,timeoutonPATHsudoifuse_sudo = true
Two phases: catalog vs runtime
workspace ──build──► maand.db (catalog)
│
└──deploy──► workers (/opt/worker/...)
- Build never SSHs to workers (except
post_buildhooks run on CLI host only). - Deploy reads the catalog and pushes to workers.
This split lets you validate placement and ports locally before touching production hosts.
Next
04 — Workers — define your cluster nodes in workers.json.