Skip to content

Spec schema

Desired-state contracts. Runtime fields (status, active_requests, last_access, backend_addr) are never part of these specs.

apiVersion: momo.dev/v1
kind: App
metadata:
id: api # DNS-label: ^[a-z][a-z0-9-]{0,62}$
placement:
node: node-a
# or automatic placement:
# selector:
# matchLabels:
# zone: east
routes:
- domain: api.example.com
target:
container: web # must exist in containers
port: http # port name or declared numeric port
containers:
- name: web
image: ghcr.io/acme/api:1.2.3
command: ["my-server"] # optional
args: ["--port", "8000"] # optional
working_dir: /app # optional
user: "1000:1000" # optional
ports:
- name: http
container: 8000
publish: loopback-dynamic # or omit / none
expose: [9000] # optional, not published to host
env:
DATABASE_URL: postgres://...
volumes:
- name: db-data
type: named # named | bind
target: /var/lib/data
# bind mounts require cluster policy allow_bind_mounts:
# - name: uploads
# type: bind
# source: /srv/uploads
# target: /uploads
networks:
- name: app-net
aliases: [api]
labels:
team: platform
resources:
memory_mb: 512
cpu: 1.0
pids: 512
health:
http:
path: /health
port: http
# or: tcp: { port: postgres }
dependencies:
- container: db
condition: running # running | tcp | http
idle: keep_warm # optional
restart_policy: unless-stopped # no | always | unless-stopped | on-failure
update_policy: recreate # recreate | restart
idle_timeout_secs: 300 # optional; node default if omitted
startup_timeout_secs: 60 # optional; node default if omitted

update_policy: recreate (default) rebuilds the Docker container when the create-shape fingerprint changes. The node stores that fingerprint as Docker label momo.spec_fp at create time (sha256 of image, command/args, ports, env, volumes, networks, resources, privileged/host_network/cap_add/devices, and user labels). Reconcile compares the label to the desired spec — it does not round-trip Docker inspect fields. Image retarget always recreates. Mutable restart_policy uses a Docker update. update_policy: restart ignores create-shape drift (except image).

Unknown top-level fields are rejected. Duplicate container or port names are rejected. Route targets must resolve to an existing container and port.

See examples/momo/app.yml in the repo.

Unsafe Docker options (rejected by default)

Section titled “Unsafe Docker options (rejected by default)”
Option Policy flag on ClusterSpec.policies
Bind mounts allow_bind_mounts
Privileged mode allow_privileged
Host network allow_host_network
Docker socket mounts allow_docker_socket (also needs bind mounts)
Device mounts allow_devices
cap_add allow_raw_capabilities
raw_host_config allow_raw_host_config

All policy flags default to false.

Generated per node from assigned apps. Stored under file://…/hosts/<node-id>.json.

Field Description
node_id Owning node
revision Monotonic; bumps on effective change
apps[] Assigned apps with containers + route target metadata
networks[] Required Docker networks
volumes[] Required named volumes

No-op applies do not bump revision unless --force-revision.

Registered via momo node link --node-id … --grpc-endpoint … --store ….

Field Description
node_id Unique cluster identity
grpc_endpoint Gateway dial address
labels Optional labels
drain active or draining (draining rejects new placements)
capacity Optional capacity hints

ClusterSpec holds revision, nodes[], routes[], and policies.

Routes map domainapp_id + target + node_id. Duplicate domains are rejected. Gateway Host lookup normalizes case and strips an optional port (api.example.com:443api.example.com).

URI: file://./momo-state (development / single-machine JSON tree).

momo-state/
cluster.json
apps/<app-id>.json
hosts/<node-id>.json
status/nodes/<node-id>.json # observation heartbeat (not desired state)
audit/audit.jsonl # mutating CLI actions

Writes are atomic (temp + fsync + rename). Corrupt files fail validation and do not replace the last good revision.

MOMO_ENV=production refuses file stores unless MOMO_ALLOW_FILE_STORE=1.

URI Use when
sqlite://./momo.db Single-host multi-process (CLI + gateway + nodes on one machine). WAL mode + poll watches.
postgres://user:pass@host/db / postgresql://… Multi-host shared store. LISTEN/NOTIFY + poll catch-up.

Both SQL backends share the same logical schema (cluster, apps, hosts, node_status, audit_log), transactional mutate with optimistic concurrency on cluster.revision, and migrate-on-open. Concurrent conflicting writers receive momo_conflict.

Watch lag is exported as Prometheus gauge momo_store_watch_lag_seconds.

  • Explicit placement.node or CLI --node wins when the node is linked and not draining.
  • placement.selector.matchLabels picks among Active nodes with matching labels (least-loaded by app count; capacity hints break ties).
  • Decision is audited and shown in momo inspect / status JSON.

Unsafe Docker options default off (allow_bind_mounts, allow_privileged, …). reject_plaintext_secret_env defaults on — env keys matching PASSWORD/SECRET/TOKEN/API_KEY/PRIVATE_KEY with non-empty values are rejected unless the policy is disabled.

Terminal window
momo validate -f app.yml
momo node link --node-id node-a --grpc-endpoint http://127.0.0.1:50051 \
--label zone=east --capacity cpu=8 --store file://./momo-state
momo apply -f app.yml --node node-a --store file://./momo-state
momo doctor --store file://./momo-state --gateway-url http://127.0.0.1:8080
momo export --store file://./momo-state -o backup.yaml
momo gateway --store file://./momo-state

JSON success envelopes include schema_version: "momo.cli.v1". See also CLI reference.