Skip to main content
zenith-compose.yml is a Docker Compose file with one Zenith extension: x-zenith.

File requirements

Zenith fetches the exact default-branch commit selected during submission. Later changes to zenith-compose.yml are not automatically pulled and will not change an existing submission until you submit a new revision. Unknown fields inside x-zenith fail parsing. Standard Compose fields are parsed separately; only the fields listed under Compose compatibility have defined Zenith runtime behavior.

Document shape

zenith-compose.yml

catalog

Set catalog.name in the repository. It is the only catalogue field required before submission.

expose

expose maps one service port to the app’s public HTTP hostname. The entry produces a route from / to the selected service and port.
The endpoint uses the deployment’s normal hostname:

Endpoint validation

  • service must name an existing Compose service.
  • port must be between 1 and 65535.
  • web must be present, even when its value is false.
  • expose must contain one entry.
The exposed endpoint uses HTTP routing with TLS terminated by Zenith. web: false does not create a raw TCP or UDP endpoint.
The endpoint is also the target for a verified custom domain.

storage

storage is a map keyed by a stable storage ID. Each value describes a top-level Compose named volume.
Zenith combines the app’s persistent volumes into one deployment allocation. Each named, anonymous, or persistent directory mount contributes to its total size. A named volume uses the matching default_size; an unsized volume contributes 100Mi. The stored directory for a named volume is derived from its Compose volume name with _ changed to -. Two volume names that normalize to the same directory, such as app_data and app-data, fail validation.

Public storage validation

When public is true:
  • volume must be set.
  • The named volume must exist in the top-level Compose volumes map.
  • The volume cannot be external.
  • No other public storage entry may reference the same volume.
The volume does not need to be mounted by a service for the manifest to validate.
Use public: true only for files an app owner should browse or edit. Do not expose database files, indexes, or credentials.

configs

configs accepts a list of configuration-file descriptors.
Zenith currently parses these fields but does not mount, edit, or transform the referenced file during deployment. Treat configs as reserved metadata. Do not depend on it for app configuration.
This block is separate from the standard Compose configs key. Developer submissions cannot use configs.file, and the current Zenith renderer does not mount Compose config references.

env

env is a map from a container environment variable name to an environment declaration.
The map key is the literal variable name injected into the container. It must match:
The name must not collide with a Zenith-reserved variable. Zenith also rejects PATH, every LD_* name, and environment variables that can make a loader or interpreter execute user-selected code: NODE_OPTIONS, PYTHONPATH, PYTHONSTARTUP, PYTHONHOME, BASH_ENV, ENV, JAVA_TOOL_OPTIONS, _JAVA_OPTIONS, RUBYOPT, PERL5LIB, and PERL5OPT. Set a fixed value under the service’s Compose environment block when the app requires one of these names.

Environment declaration

Every declaration must contain input or exactly one computed source: generate, template, or alias. The computed source fields are mutually exclusive. A bare string is shorthand for alias:
This is equivalent to:

input

default and default_template are mutually exclusive. Neither may be used when the same declaration has generate, template, or alias. A static default that uses an address at zenith.hosting, example.com, example.org, or example.net fails submission validation. To seed an account with the deployment owner’s address, use default_template: '{ZENITH_OWNER_EMAIL}'. An input attached to a computed source adds display metadata and validation. It also makes the field available in the deployment editor. A generated input accepts an owner replacement because its stored raw value is the source. An alias or template remains source-driven and resolves again on every render. An input-only declaration uses the stored owner value or its default. Environment values are limited to 4096 bytes when set through the deployment editor. Clearing an input-only value resets it to its declared default on the next render. A computed input cannot be cleared to an empty value; it must be replaced with another value. A declaration without input is not owner-editable.

Resolution sources

Generated and defaulted values are stable for the lifetime of a deployment. Changing a generator pattern or input default does not rotate a value already stored by an existing deployment.
Aliases and templates are dynamic. They track changes such as a newly verified custom domain. Resolution is dependency-aware and order-independent. An alias or template may reference a declaration written later in the YAML map. Unknown references and dependency cycles fail validation.

generate

generate accepts an RE2 regular expression. Zenith expands it into a deterministic value and stores the result.
The pattern must be non-empty, compile under RE2, and produce a non-empty value. Use several independent capture groups for secrets. Zenith currently does not enforce a minimum entropy threshold. Generation is deterministic for one deployment and pattern, but the stored value is authoritative. It is not re-generated during normal rendering.

template

template substitutes {NAME} references with declarations or built-ins on every render.
Reference names use the same environment-name grammar. Write {{ when you need a literal { before text that could otherwise be parsed as a reference. An empty template fails validation.

alias

alias copies another declaration or built-in on every render.
The target must exist and must not create a dependency cycle.

services

Without services, Zenith injects a declared value into every Compose service. With services, every listed name must exist and the value is injected only into those services.
An x-zenith.env value replaces a Compose environment value with the same name in every targeted service. Owners may also add undeclared environment variables to a deployment. Zenith injects those variables only into services marked by an expose entry with web: true. If no exposed endpoint has web: true, Zenith injects them into every service.

transform

Transforms run after the raw source is resolved and validated. The raw value remains stored and visible according to the input rules. The transformed value is injected and is the value referenced by downstream aliases and templates. Use urlencode on one URL component, such as an SMTP username or password. Do not apply it to a complete URL. An unknown transform fails validation. argon2id on alias or template also fails validation.

legacy

legacy migrates a stored value from Zenith’s pre-v1 {{ZENITH_*}} token system into a generated declaration.
Use it only when migrating an app that already has Zenith deployments. New apps must omit it. The supported migration value is the old token name without braces. The old inline token syntax is no longer expanded. A rendered value containing {{ZENITH_ fails submission validation.

Built-ins

Built-ins are resolved for each deployment. Zenith does not inject them automatically. Declare an alias or template when the app needs one. ZENITH_OWNER_EMAIL can be empty. Use it with default_template when creating an owner account. The value is stored only after it resolves non-empty.

Compose compatibility

Zenith uses the Compose model for service definitions, then maps a defined subset to its runtime. Every service becomes one independently managed workload.

Service fields

Services resolve each other by service name inside the deployment. ports does not limit which ports sibling services can reach.

Health checks

Compose health checks are exec-based in Zenith:
  • CMD runs the argument list directly.
  • CMD-SHELL runs through /bin/sh -c.
  • interval, timeout, and retries map to probe timing.
  • start_period creates a startup probe budget.
  • Readiness always uses a failure threshold of 3 so unhealthy services stop receiving public traffic promptly.
Public traffic is sent only to service replicas that pass readiness. A dependency using condition: service_healthy waits for the same readiness signal.

Persistent volumes

Top-level, non-external named volumes share one persistent allocation. Each volume receives its own stable subdirectory. Anonymous volume mounts are also persistent and receive stable subdirectories derived from the service name and target path. External volumes do not create Zenith storage. Do not rely on an external volume being present in the deployment.

Secrets and configs

Developer submissions cannot read repository or host files. Therefore:
  • Top-level secrets.<name>.file is rejected.
  • Top-level configs.<name>.file is rejected.
  • Service env_file and label_file are rejected.
  • Service extends is rejected.
  • Top-level include is rejected.
Compose config references are not mounted by the current runtime. Prefer fixed environment values, x-zenith.env, or configuration baked into the image.

Unsupported build and host features

Zenith does not build images during deployment. A service with build still needs image; the declared image is what runs.Bind mounts are rejected. The deployment cannot read a path from the GitHub repository or Zenith host. Bake required files into the image, use an inline secret for sensitive static content, or use a named volume for mutable data.

Submission validation

Zenith performs these checks before accepting the app for review:
  1. Fetch the root zenith-compose.yml from the selected default-branch commit.
  2. Enforce the non-empty 128 KiB file limit.
  3. Reject external file references and bind mounts.
  4. Load the Compose project with an empty host environment and discard environment files.
  5. Parse x-zenith strictly.
  6. Validate required catalogue and endpoint fields.
  7. Resolve every x-zenith.env declaration against deterministic test deployment data.
  8. Render the complete app and reject invalid or duplicate runtime object names.
  9. Reject unresolved pre-v1 {{ZENITH_*}} tokens.
Validation also catches:
  • An empty Compose service set.
  • A service without image.
  • Endpoint references to missing services.
  • Invalid or colliding service names.
  • Colliding persistent-volume directory names.
  • Invalid public storage references.
  • Invalid environment declarations.
Passing validation makes the app eligible for review. It does not publish the app automatically.

Defaults summary

Build the file

Return to the step-by-step guide.