---
title: "Naming conventions"
summary: "How Onebox names things: casing, number, mood, and which words are reserved. Written down so a new field or flag has an answer before the review."
description: "The rules every command, flag, field and error code in Onebox follows."
status: shipped
read_when:
  - "Adding a field, flag, command or error code and choosing what to call it"
  - "Wondering why two similar things are named differently"
---
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.

## 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` `backupTargets` `externalServices` | `runtime` `deployment` `proxy` |

Arrays are plural too: `routes`, `envFiles`, `volumes`, `publishedPorts`,
`needs`, `checks`, `env_checks`. Four are not, because the singular reads
as the thing being stated: `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`).

```yaml
allow_agent_proposals: true      # grants
require_migration_backup: true   # demands
managed: false                   # state
```

**One 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

**`ob` is the command; `onebox` is everything it puts anywhere else.** Only
what an operator types or keeps next to the project uses `ob`: the command, the
project file `ob.yml`, and the artifacts passed between commands
(`ob-plan.json`, `ob-approval.json`, `ob-backup-report.json`). Every name Onebox
creates on a host, inside a container, or for its own use begins with
`onebox`, and the separator says what kind of name it is:

| Form | For | Examples |
| --- | --- | --- |
| `onebox-<name>` | named things on the host and in containers | `onebox-postgres`, `onebox-ingress`, `onebox-job-nightly`, `onebox-run`, `/tmp/onebox-drain` |
| `onebox_<a>_<b>` | names joined from several identifiers, which must stay unambiguous | `onebox_postgres_data`, `onebox_services`, `onebox_web_r0` |
| `onebox.<key>` | labels | `onebox.app`, `onebox.release` |
| `ONEBOX_` | environment variables | `ONEBOX_RESULT_FILE` |
| `/var/lib/onebox/…` | paths | `/var/lib/onebox/app` |
| `.onebox-<name>` | Onebox's own files inside a directory it shares | `.onebox-schedule.lease`, `.onebox-tmp` |

The application's name appears only in the author's own workload containers
and Compose project, and where the application has to be identified beyond the
host: the owner record, the backup repository path and the database name. A
test fails the build when code introduces any other `ob-`, `ob_` or `ob.` name.

**Every workload container is `<app>-<component>-<replica>`.** Replica
ordinals are one-based and never omitted, including for a singleton, because
replicas can change and a rollout moves containers between numbered slots. A
three-replica `server` workload in the `monk` application is therefore
`monk-server-1`, `monk-server-2`, and `monk-server-3`; a singleton workload is
`monk-feed-1`.

**Every container Onebox runs from its own images is `onebox-<component>`.**
There is no ordinal, because none of them has replicas. Managed services are
`onebox-postgres` and `onebox-redis`; a restore drill beside one is
`onebox-postgres-restore`; the host proxy is `onebox-proxy` and its discovery
controller `onebox-discovery`. The application is not in these names: a host
runs one application, and the `onebox.app` label records which. So `docker ps`
reads as two groups: your images under your application's name, and what
Onebox provides under `onebox-`.

The same namespace covers the host's other shared resources: the ingress network
the proxy and routed workloads join is `onebox-ingress`. Because the namespace
is Onebox's, an application may not be called `onebox` or begin with `onebox-`,
and `proxy`, `discovery`, `ingress` and `services` are not valid service
names.

**Everything else Onebox puts on the host is in the same namespace, without the
application.**

| What | Name |
| --- | --- |
| State directory | `/var/lib/onebox/app` (releases, `current`, journal, services, backup) |
| Host state | `/var/lib/onebox/_host` (owner record, host lock, host journal, proxy), whatever `basePath` says |
| Scheduled job units | `onebox-job-<job>.service` and `.timer` |
| Backup units | `onebox-backup-<service>-<operation>` |
| Proxy routers and services | `onebox_<workload>_r<n>`, `onebox_<workload>` |
| Ownership labels | `onebox.app`, `onebox.workload`, `onebox.service`, `onebox.release`, … |

The `onebox.app` label, not any name, is what records which application owns a
resource. Names can drop the application because a host has exactly one: the
owner record is at a fixed path, so a different `basePath` does not give a
second application a second record. Custom Traefik objects in a proxy
configuration directory may not begin with `onebox_`.

**Names do not carry the environment.** Staging and production of `shop` both
derive `shop-web-1` and `onebox-postgres`. What keeps them apart is the host:
its owner record names one application and one environment, and preflight
refuses a deploy of any other.

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.

**Every other resource Onebox derives is `onebox_<component>_…`.** Volumes,
service Compose projects, the service network and restore-drill resources:
`onebox_postgres_data`, `onebox_web_uploads`, `onebox_postgres`,
`onebox_services`. They do not carry the application: a host has one, the
`onebox.app` label records which, and `ob destroy` releases the host only after
removing them. They join with underscores because a component or volume name
cannot contain one, so each name maps back to exactly one component and
volume: a hyphen, which names may contain, could not promise that. The one
exception is the application's own Compose network, `shop_default`, which is
the name Compose gives it.

| Namespace | Separator | Holds |
| --- | --- | --- |
| `<app>-` | hyphen | your workload containers |
| `onebox-` | hyphen | containers and the ingress network Onebox runs on the host |
| `onebox_` | underscore | volumes, service projects and networks |

## Commands

**A command is an imperative verb, or a noun that prints.**
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; no flag turns it off.)

## Flags

**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

`<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_unreachable
protection_disable_pending
drill_schedule_too_sparse
route_collision
```

## 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: `backupTargets`, `backup.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

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.