Naming conventions
These describe what the surface already does, so a new field or flag has an answer before anyone has to review it. Where a rule has known exceptions they are named inline — a rule with a hidden exception is worse than no rule.
The project file
Section titled “The project file”Casing. Every field is snake_case. Abbreviations are admitted only where
the short form is the name people use: protocol and format tokens (tls,
http, tcp, udp, url), env, cron, args, config, and the recovery
term pitr. Invented shortenings are not — no cfg, no dir, no max.
Number follows shape. A field holding a map of things is plural; a field
holding one object is singular. The exceptions are mass nouns, where the plural
names a single settings object rather than a collection — resources,
credentials, overrides, logs, metrics, alerts — and env, which is a
map named for the thing it configures.
| Plural | Singular |
|---|---|
workloads services registries environments hooks notifications backup_targets external_services | runtime deployment proxy observability |
Arrays are plural too: routes, env_files, volumes, published_ports,
needs, verifications, env_checks. Four are not, because the singular reads
as the thing being stated rather than a list: order, on, require,
present.
Booleans announce their direction. A boolean that grants permission is
allow_*; one that imposes a requirement is require_*. A boolean describing
state is a bare adjective, or the name of the thing it switches on
(health.tcp).
allow_agent_proposals: true # grantsrequire_migration_backup: true # demandsmanaged: false # stateOne grammar for time. Every duration is a string with the same syntax —
30s, 5m, 1h30m, 14d. No field carries its unit in its name, and no
duration is an integer.
Runtime resources
Section titled “Runtime resources”Every application container is <app>-<component>-<replica>. Replica
ordinals are one-based and never omitted, including for a singleton. A
three-replica server workload in the monk application is therefore
monk-server-1, monk-server-2, and monk-server-3; singleton components are
monk-feed-1, monk-postgres-1, and monk-redis-1.
A hyphen authored inside app or a component is doubled in the runtime name,
so the separator remains unambiguous: app help-desk and component web-api
produce help--desk-web--api-1.
The temporary container joining a rollout is <app>-<component>-new. It takes
the stable numbered slot only after the previous occupant has drained and been
removed. The host-scoped managed proxy is onebox-proxy.
Container names are the human-facing runtime grammar. Persistent volumes, Compose projects, networks, and proxy-provider objects use their own derived names because they have different collision and data-lifetime constraints.
Commands
Section titled “Commands”A command is an imperative verb, or a noun that prints. Nothing in between.
The split is grammatical, not about mutation — preview and validate are
imperative verbs that change nothing.
| Verbs — you tell it to act | Nouns — you ask it for a fact |
|---|---|
deploy rollback abort resume approve bootstrap destroy eject exec init plan preview validate | status logs audit doctor schema version preflight canonical |
Groups that hold subcommands are nouns: job, proxy, secrets, and
service. A group is singular when it names a subsystem
(ob service apply converges the services: block) and plural only when its
subcommands genuinely act on the whole set at once (ob secrets push).
An artifact keeps one word across all three of its surfaces — the command that produces it, the flag that consumes it, and its filename.
| Producer | Consumer flag | Default filename |
|---|---|---|
ob plan | --plan | ob-plan.json |
ob approve | --approval | ob-approval.json |
ob job plan | ob job run --plan | ob-job-plan.json |
ob plan --backup-report-out | --backup-report | none — the path is required |
The first three have a default path, so --out is optional.
--backup-report-out is the exception: it has no default, and no report
template is written unless you name a path — and then only when the plan
actually requires one. ob-backup-report.json is a convention the guidance and
the error messages suggest, not a default the binary supplies. ob job plan
takes the same --backup-report-out, on the same terms.
The bypass gets harder as the blast radius grows. ob deploy takes -y to
skip its prompt. ob destroy has no flag that skips its confirmation: the
application name must be typed back. (It reads that from stdin, so it can still
be piped — the point is that no flag turns it off.)
Shape carries meaning for flags that change what a command includes or prints. Three forms, three behaviours:
| Form | Means | Example |
|---|---|---|
| bare noun | also do this — additive, and the only additive flags are destructive | --volumes --proxy |
-only suffix | print this instead of the default output | --digest-only --origins-only |
adjective or no- prefix | modify how the command runs | --raw --overwrite --redeploy --no-rollback |
Overrides name the exact boundary they cross. --break-lock breaks only a stale
operation or host lock after its holder is inspected.
--allow-destructive-mounts permits only the mount detachments named by the
service plan and cannot bypass an unsupported major version.
--break-migration-gate applies only to abort recovery, while
--override-migration-backup records the explicit reason for proceeding without
the report policy required. There is no generic --force switch.
--out is where output goes. Not --to, not --file. Machine-readable
output is the global --output, never a per-command --json.
Error codes
Section titled “Error codes”<subject>_<condition>, snake_case, so codes for one subsystem sort together.
The lifecycle contract follows this. The loader’s own codes largely predate it
and several are condition-first — unknown_field, no_environment,
unknown_service_driver — so the environment codes do not sort together today.
New codes should be subject-first.
backup_target_unreachableprotection_disable_pendingrestore_drill_schedule_too_sparserouting_incompleteReserved words
Section titled “Reserved words”One concept, one word. These three are the ones most easily confused, so they are pinned:
| Word | Means | Never means |
|---|---|---|
| server | the machine you deploy to, in the project file and in every artifact | a backup repository |
| target | a backup destination — backup_targets, protection.target | the machine you deploy to |
| workload | a container Onebox runs from your declaration | a supporting service, a Compose service |
ob exec and ob logs take a <workload|service> because those are two
different declared things, not two words for one.
Inherited vocabulary is exempt
Section titled “Inherited vocabulary is exempt”Where a word arrives from Docker or Compose it keeps Docker’s meaning, even when that collides with a word above. Users arrive already knowing these, and renaming them would cost more than the collision does.
build.target is a Dockerfile stage, not a backup destination.
registries.<name>.server is a registry hostname, from docker login <server>.
init, tty, stdin_open, working_dir, cpus, extra_hosts and platform
all mean what they mean in Compose.