Project file
onebox.run/v1alpha1 is the contract for one application on one host. It goes in
ob.yml at the root of your repository. ob.yaml is accepted automatically
when ob.yml is absent, and -c accepts either spelling or any explicit path.
The resource requires apiVersion, kind, metadata.name, and spec.
Inside spec, both environments and workloads are required. There are no
top-level workload shorthands or legacy aliases.
Start with the schema reference so your editor can help while you type:
# yaml-language-server: $schema=https://onebox.run/schemas/application/v1alpha1/application.schema.jsonapiVersion: onebox.run/v1alpha1Use the published schema at
https://onebox.run/schemas/application/v1alpha1/application.schema.json for
editor completion and validation.
ob schema --out onebox.schema.json writes a local copy, and ob init puts the
published reference on the first line of a scaffolded project.
Route declarations
Section titled “Route declarations”Every ingress rule is explicit and uses hostname for both exact hosts and
wildcard hosts. Routes live under a workload in spec.workloads:
spec: workloads: web: image: nginx routes: - {hostname: shop.example.com, port: 3000} - {hostname: "*.preview.example.com", port: 3000}The loader rejects retired route fields instead of silently changing their
meaning. The workload-level port field remains available as the default
container port for HTTP health checks; it no longer creates an ingress route.
The block map
Section titled “The block map”| Block | What it says | Fields |
|---|---|---|
app | The application’s name. Every derived name carries it. | Top level |
environments | Where it runs, and the policy that governs deploying there. | environments |
workloads | The containers that are yours. | workloads |
services | The supporting services Onebox runs for you. | services |
deployment | Release order, retention, migration policy. | deployment |
proxy | Who runs the proxy and what routes. | proxy |
runtime | Environment files and local environment-file checks. | runtime |
hooks | Commands at lifecycle seams. | hooks |
checks | What must be true for a release to activate, grouped by kind. | checks |
externalServices | Dependencies operated outside Onebox, and how a workload reaches them. | externalServices |
backupTargets | Off-host repositories a protected service writes its backups to. | backupTargets |
registries notifications | Named maps. | registries · notifications |
externalServices and backupTargets are published in the JSON Schema and
accepted by ob validate, but the lifecycle behind them is not shipped:
declaring a backup target creates no repository and no schedule, and an external
service’s health probe never runs. See
Shipped vs proposed.
Any mapping also accepts x- keys. They are carried nowhere and never change the
generated runtime.
Shorthand
Section titled “Shorthand”A scalar form accepted by a contract version remains accepted for that version. These are contract, not convenience that changes within a version.
| Written as | Means |
|---|---|
image: nginx | image: {reference: nginx} |
health: /healthz | health: {http: /healthz} |
server: [email protected] | server: {user: root, host: 203.0.113.10} |
jump: [email protected]:2222 | jump: {user: deploy, host: bastion.example.com, port: 2222} |
needs: [postgres] | needs: [{name: postgres}] |
services: {postgres: 17} | services: {postgres: {version: 17}} |
envFiles: [.env] | envFiles: [{file: .env}] |
hooks: {PostDeploy: "echo hi"} | hooks: {PostDeploy: {run: "echo hi"}} |
build: . | build: {context: .} |
Environment values: two rules
Section titled “Environment values: two rules”Which list a workload resolves. Exactly one, from the most specific declaration present — override, then workload, then environment, then project. Lists replace rather than extend.
What wins inside the container. Lowest first: a compose: workload’s own
env_file; the resolved envFiles entries in order; managed-service connection
files; the service’s environment.
Full explanation, including why level four outranks the rest: Handle secrets.
What Onebox generates
Section titled “What Onebox generates”Names — workload containers are uniformly numbered: shop-web-1 and
shop-web-2. Containers Onebox runs from its own images are unnumbered:
onebox-postgres for a managed service and onebox-proxy for the proxy. Persistent and provider names include onebox_postgres,
onebox_postgres_data, shop_default (the external application network),
onebox_services (the external service network), and onebox-ingress. These are contract:
once a persistent resource exists its name cannot change without migration. A
foreign resource already holding a derived name is refused, not adopted.
Layout — /var/lib/onebox/app/releases/<id>, plus current, journal, and
services. basePath moves this per environment. Host state — the owner
record, host lock and proxy — stays in /var/lib/onebox/_host whatever
basePath says, so that one host has one owner.
Bind-mount lifetimes
Section titled “Bind-mount lifetimes”A workload bind source has one of two lifetimes:
workloads: web: image: ghcr.io/acme/shop:1.4.0 volumes: # Versioned configuration shipped inside this release. - {source: ./config, path: /etc/shop, mode: ro} # Writable state managed outside the release store. - {source: /srv/shop/uploads, path: /var/lib/shop/uploads}A relative source begins with ./ (or is .) and resolves inside the release
directory. It must be read-only because that directory belongs to the
release: the next deploy ships a new one, and deployment.retainReleases
cleanup removes an expired release once no container still mounts it, so
anything written there is lost. Use it for versioned configuration, never
changing data.
An absolute source is external host state. Onebox mounts it but neither creates, backs up nor removes it; provision and protect that path separately. For data Onebox should own, prefer a managed named volume instead.
The proxy — if anything is routed, Onebox runs Traefik and writes its static
configuration. When Onebox owns that static configuration, HTTPS routes receive
managed response compression by default; Traefik skips bodies below its
threshold, responses already encoded, and text/event-stream so streaming is
not buffered. Declare proxy.config with dynamic YAML or TOML files to extend
that managed configuration. Include traefik.yml or traefik.yaml in the same
directory only when you need to own the static configuration too. The external
proxy.network may be changed, but default is reserved for the application’s
own Compose network. The derived <app>_default and onebox_services names are
reserved too; routed projects must use a distinct ingress network.
Managed Traefik runs as a non-root user and never receives the Docker socket. An isolated Onebox discovery controller observes only this application’s container lifecycle, routing labels and Docker health. It runs as root only to open the host’s root-owned socket, but has no network or listener; it atomically publishes a sanitized dynamic configuration that Traefik mounts read-only. This preserves health-aware rolling and draining without placing host control in the internet-facing process.
Deploy converges this managed proxy after its read-only preconditions and before application mutation. Updating the Onebox runner therefore upgrades the default proxy boundary automatically; an operator does not need to edit the project or run a separate migration command.
proxy.config keeps this boundary and has two levels. With dynamic YAML or TOML
alone, Onebox continues to write the static configuration and stages those files
into its watched directory. This is the normal way to add middleware, TLS
options, transports, or other routing policy without duplicating the managed
entrypoints, health check, provider, and ACME configuration.
If the directory includes traefik.yml or traefik.yaml, that file becomes the
project-owned static configuration. It must not enable providers.docker and
must configure providers.file.directory: /etc/traefik/dynamic. Watching must
remain enabled, and providers.file.filename cannot be combined with that
directory. A proxy .env is accepted with project-owned static configuration
or with proxy.dns_challenge; it may carry ordinary DNS-provider credentials,
but not TRAEFIK_* static overrides.
In both modes, the filenames onebox.yml, onebox.yaml, and
onebox-managed.yml, the dynamic/ mountpoint, the onebox-compress
middleware, and HTTP or TCP router or service names beginning with the derived
<app>_ prefix are reserved for generated output. ob deploy, ob bootstrap,
and ob proxy apply refuse an incompatible configuration with migration
guidance rather than silently starting a proxy with missing or stale routes.
Wildcard host routes
Section titled “Wildcard host routes”Use a wildcard hostname when one HTTP workload should receive every immediate
subdomain below a suffix. The complete left-most label is *, matching the
convention used by TLS certificates and common ingress APIs:
proxy: config: traefik dns_challenge: provider: cloudflare resolvers: ["1.1.1.1:53"]
workloads: preview: image: ghcr.io/acme/preview:1.0.0 routes: - {hostname: "*.preview.example.com", port: 3000}This matches branch.preview.example.com, but not the suffix itself or
a.branch.preview.example.com. Declare the apex as a separate exact hostname
route when it should be served too. Onebox accepts only exact hostnames or a
complete left-most wildcard label; it does not accept authored regular
expressions, partial wildcards, or a bare HTTP catch-all. It renders the
wildcard hostname as the anchored Traefik rule
HostRegexp(`^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.preview\.example\.com$`).
Exact and wildcard claims
that overlap on the same entrypoint,
protocol, and path are refused instead of relying on implicit proxy priority.
The existing hostname: "*" form remains available only for a plaintext or TLS
passthrough TCP route (protocol: tcp with tls: none or tls: passthrough),
where Traefik requires HostSNI(`*`);
it overlaps every TCP host claim on the same entrypoint and path.
Terminating TLS for a wildcard requires ACME DNS-01. proxy.dns_challenge
keeps the static Traefik configuration managed by Onebox while selecting the
DNS provider. Put the provider’s required environment variables in
proxy.config/.env; the directory may contain only that file. Onebox stages it
with mode 0600 and mounts it only into Traefik. Do not commit provider tokens.
Exact routes continue to use the separate letsencrypt HTTP-01 resolver, so
adding a wildcard does not change issuance or renewal for existing domains.
The provider names and credential variables are defined by
Traefik’s DNS challenge provider documentation.
If proxy.config contains a project-owned
traefik.yml or traefik.yaml instead, configure DNS-01 under
certificatesResolvers.onebox-wildcard there and omit
proxy.dns_challenge.
Additional proxy entrypoints
Section titled “Additional proxy entrypoints”Declare an entrypoint when clients must reach the managed proxy on a port other
than HTTP 80 or HTTPS 443. Name the same entrypoint on the route that should
receive that traffic:
proxy: entrypoints: otlp-grpc: {port: 4317} otlp-http: {port: 4318}
workloads: telemetry: image: ghcr.io/acme/telemetry-gateway:1.0.0 routes: - hostname: telemetry.example.com entrypoint: otlp-grpc port: 4317 scheme: h2c - hostname: telemetry.example.com entrypoint: otlp-http port: 4318Each declared port is published on the host by the managed proxy. Use this for
remote clients; workloads on the same host can communicate over Onebox’s
internal networks without opening another host port. The route’s port is the
workload’s listening port, while entrypoint selects the proxy listener.
When Onebox writes the static proxy configuration, including when
proxy.config contains only dynamic extensions, it also writes these named
entrypoints. If proxy.config supplies your own traefik.yml or
traefik.yaml, Onebox still publishes the ports, but that configuration must
define matching entrypoint names and addresses.
Route middleware
Section titled “Route middleware”Managed HTTPS routes already use Onebox’s streaming-safe response compression.
No route declaration or proxy.config is needed for that default.
Attach dynamic proxy middleware to the exact route that needs it with an ordered, provider-qualified reference:
proxy: {config: traefik}
workloads: web: image: ghcr.io/acme/shop:1.4.0 routes: - hostname: shop.example.com path: /admin port: 8080 middlewares: - admin-auth@file - secure-headers@fileThe definitions belong to the provider named by the suffix. For example,
admin-auth@file names http.middlewares.admin-auth in a dynamic Traefik file
under proxy.config. Onebox supplies the watched file provider when the
directory contains only dynamic extensions; the project must supply it when the
directory also contains custom static configuration. A Onebox-managed proxy
therefore requires proxy.config when a route names middleware. With
proxy.managed: false, the operator-owned proxy provides the referenced
resources instead. Onebox preserves list order when it attaches the chain to
the generated router. HTTP and TCP routes both support middleware references;
the referenced middleware must match the route’s protocol.
Evolution
Section titled “Evolution”apiVersion: onebox.run/v1alpha1 is the pre-baseline contract. Before the
baseline is locked, normalization may be breaking and will invalidate config
digests and any plans or staged artifacts bound to the old bytes. Regenerate
those artifacts after changing the declaration. Within the locked baseline:
- A field is added, never repurposed.
- Only the documented resource shape is accepted; aliases and fallbacks are not part of the contract.
- A default may be added; an existing default’s value does not change.
- A constraint is not tightened against a project that already loads unless accepting it can silently lose data or produce an ambiguous runtime. A safety refusal names the migration required to make the project valid again.
The JSON Schema published by ob schema is generated from the same declarations
the loader enforces and is checked against the conformance corpus.