Project file
onebox.run/v1alpha2 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.
Start with the schema reference so your editor can help while you type:
# yaml-language-server: $schema=https://onebox.run/schemas/application/v1alpha2/application.schema.jsonapiVersion: onebox.run/v1alpha2Use the published schema at
https://onebox.run/schemas/application/v1alpha2/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”Configuration blocks live under spec; the application name lives in
metadata.name.
| Block | What it says | Fields |
|---|---|---|
metadata.name | 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 declares dependencies operated outside Onebox;
backupTargets declares off-host backup destinations. Declaring a destination
does not enable protection by itself. Executable support depends on the service
and the operation; see Shipped vs proposed.
Unknown fields, including behavior-bearing x-* fields, are rejected. Store
opaque notes in metadata.annotations; annotations do not change the 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: root@203.0.113.10 | server: {user: root, host: 203.0.113.10} |
jump: deploy@bastion.example.com: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.dnsChallenge; 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 dnsChallenge: 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.dnsChallenge
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.dnsChallenge.
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.
The JSON Schema published by ob schema is generated from the same declarations
the loader enforces and is checked against the conformance corpus.