Skip to main content
zenith-compose.yml is a Compose file with an x-zenith section. This page lists the fields Zenith accepts and explains how they behave when you deploy an app. The CLI’s zenith check and zenith up also enforce the preview source-build policy. The quickstart and manifest guide cover setup, testing, and publishing.

File requirements

For publishing, the selected commit must be available in the public GitHub repository. It need not be the current default-branch commit. Zenith fetches the root manifest at that exact commit. Later changes to zenith-compose.yml are not automatically pulled and will not change an existing submission until you submit a new revision. zenith up instead uploads your local project for temporary testing. It does not submit a catalogue listing or establish that the later GitHub publish build is identical. Unknown fields inside x-zenith fail parsing. Standard Compose fields are parsed separately. The repository schema may accept fields outside Compose compatibility, but only the listed fields have defined Zenith runtime behavior. Zenith admission also rejects the inputs listed under Zenith runtime restrictions.

Document shape

This fragment shows the document structure. A valid manifest also defines the web service referenced by expose.
zenith-compose.yml

catalog

catalog describes the app listing. alternatives and the other optional fields can be omitted.

expose

For setup examples, see Public URLs and domains. Each expose entry maps a service port to a public HTTP hostname. An entry without label uses the app’s primary hostname. Labelled entries use separate endpoint hostnames.
The primary endpoint uses the deployment’s canonical 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 at least one entry. Use an entry without label for the main hostname and custom-domain target. Labels must be unique.
  • forward_auth, when present, must be a boolean. Omission and false both leave the endpoint without Zenith-provided forward authentication.
  • Development preview sessions reject forward_auth: true.
The exposed endpoint uses HTTP routing with TLS terminated by Zenith. web: false does not create a raw TCP or UDP endpoint.
The primary endpoint is also the target for a verified custom domain.

Forward authentication

x-zenith.expose[].forward_auth: true requires the deployment owner’s Zenith account before requests reach that HTTP endpoint. The following fragment protects the primary endpoint and a labelled endpoint. It leaves a third endpoint without Zenith forward authentication:
The fragment omits the required catalogue and service definitions. Omitting forward_auth on the api entry has the same effect as setting it to false.

Sign-in and request behavior

  • An unauthenticated browser navigation goes to Zenith sign-in. After authentication, the owner confirms the destination with Open app before returning to the original app URL.
  • An incorrect password stays on the login page with the normal credential error. A valid sign-in from a different account receives access denied.
  • Unauthenticated API requests and WebSocket upgrades are denied rather than redirected to an interactive login. Do not use this option on public webhook endpoints or APIs that need unauthenticated clients.
  • Existing app cookies and application Authorization headers are preserved. A Zenith dashboard session is not an API credential for the protected endpoint.
  • If Zenith cannot validate access, requests are denied rather than sent to the app without authentication.
  • Signing out of all Zenith sessions revokes protected-app access. Ordinary dashboard sign-out does not revoke an existing protected-app session.
Treat every exposed hostname as a separate access decision. An unprotected endpoint can still reach the same private data as a protected endpoint. Forward authentication is not a substitute for reviewing your app’s routes and permissions.
For setup, see Protect an app with forward authentication.

storage

For setup and file-manager access, see Persistent storage and files. storage is a map keyed by a stable storage ID. Each value describes a top-level Compose named volume.
The schema defines the metadata shape only. Persistent-volume allocation, directory naming, and sizing are deployment behaviors outside this schema.

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.

x-zenith.configs

x-zenith.configs exposes specific files in named volumes through the deployment’s configuration editor. Follow Expose configuration files for setup instructions.
The example assumes app_settings is declared under top-level volumes. If it is mounted at /etc/example, /config.ini refers to /etc/example/config.ini in that container.

Configuration validation and behavior

  • storage must reference an existing storage entry whose volume is a declared, non-external named volume.
  • path must identify a file within that volume. /, parent traversal, repeated slashes, backslashes, control characters, trailing slashes, and the .zenith-files-tmp and .file-manager-tmp path segments are rejected.
  • Each volume and path pair must be unique, including across storage entries that reference the same volume.
  • The file must already exist and contain UTF-8 text without null bytes. Zenith does not create it from the declaration.
  • The volume can remain private. Configuration access covers only the declared file; storage.public: true separately enables the volume’s file manager.
  • Applying changes saves the file, then requests an app restart. If the restart request fails, the file remains saved and the owner can retry the restart.
  • Entries without storage remain legacy metadata and do not expose an editor.
This block is separate from standard Compose configs, which supplies read-only files from inline content. A file mounted directly from a Compose config is not writable through this editor.

env

For setup examples, see Environment variables and secrets. 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. Submission validation resolves declarations with deterministic test deployment data and no owner-entered values. An input marked required: true must therefore resolve non-empty from default, default_template, or a computed source. A required input with none of these is rejected before the deployment editor can ask the owner for it. A static default that uses an address at zenith.hosting, example.com, example.org, or example.net fails submission validation. To create 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. Independent capture groups contribute separately to generated secrets. Zenith currently does not enforce a minimum entropy threshold. Generation is deterministic for one deployment and pattern, but the stored value is authoritative. Normal rendering reuses the stored value.

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.
legacy applies to apps with deployments that use the pre-v1 token system. New apps do not need this field. 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. Follow SMTP email to configure the relay, authentication, and STARTTLS. The OIDC reference lists the OIDC credential built-ins. 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.

Sign in

x-zenith.sign_in registers your app’s OpenID Connect callbacks with Zenith. Your app must implement or enable an OIDC client. For setup, callback restrictions, credential built-ins, owner linking, and protocol behavior, see Sign in with Zenith.

Preview policy

zenith check --upload-size validates the local project against preview policy and estimates upload size. zenith up uploads it for server validation and a temporary build. These commands do not validate the entire published runtime contract below. Preview source builds require:
  • Exactly one service using build: . and the root Dockerfile, without image on that service.
  • Publicly pullable image references for the other services.
  • A primary x-zenith.expose entry with web: true and no label.
Dockerfile EXPOSE is optional. The expose entry declares the routed port. Previews support the public env, storage, and configs blocks, named volumes, and inline Compose configs. forward_auth: true is unsupported in previews. Compose interpolation uses an empty host environment. $$ preserves a literal dollar sign. Local test secrets come from .env.local, which the CLI validates and sends separately from the source archive. External file references, bind mounts, and unsupported build options fail validation. The CLI also rejects inline secret content and privileged: true. Acceptance of a Compose field does not guarantee runtime support. These limits do not redefine the shared schema or published runtime. In particular, an image-only published manifest is not a valid preview project just because its images are pullable. See temporary previews for the full workflow and lifecycle.

Compose compatibility

This section describes the published runtime, not the stricter preview policy. 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 uses a failure threshold of 3.
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.

Compose configs

For a complete app example, see Static configuration files. Use standard top-level Compose configs to deliver a static, non-secret file. Define each config with inline content, then reference it from every service that needs the file.
zenith-compose.yml
For an editable file, create a copy in a named volume and declare it under x-zenith.configs. See Expose configuration files.

Top-level config definition

Only the inline content source is supported. Compose rejects a config definition with no source, so content must be present even when its value is empty. Zenith rejects file, external, environment, driver, driver_opts, and template_driver config sources.

Service config reference

A service can use short or long syntax:
Each reference is service-scoped. A service receives only the configs it names. A config target must not collide with another config, volume, tmpfs, or secret mount in the same service. Unknown sources fail validation. The mounted file is read-only and uses subPath. Zenith performs no runtime substitution inside content, including no x-zenith.env substitution. Standard Compose interpolation still runs while the manifest loads, so write $$ when the mounted file must contain a literal $. Do not put passwords, tokens, or private keys in configs.content or inline secrets.content. Both are part of the public manifest. Use generated or owner-provided deployment environment values for private credentials. When Zenith applies an updated app definition, a content change restarts only the services that consume that config. The file is not hot-reloaded in an existing container. A Git push does not update a submission or deployment automatically. Submit and apply the new definition through the normal workflow.

Zenith runtime restrictions

Some valid Compose settings do not work on Zenith. Use the supported service fields above and check these restrictions before submitting. The preview policy adds further restrictions for zenith check and zenith up. Published runtime configuration cannot read repository or host files. The supported publishing build can read its pinned source, but that does not make file-backed runtime configuration available. Therefore:
  • Top-level secrets.<name>.file is rejected.
  • Top-level configs.<name>.file and every other non-inline config source are rejected.
  • Service env_file and label_file are rejected.
  • Service extends is rejected.
  • Top-level include is rejected.
  • Service privileged: true is rejected.
Other Compose fields may parse, but Zenith does not guarantee that they will work.
Files in your public repository are public, even if you put them in secrets.content. Never put passwords or keys in an inline config or secret. Use deployment environment values for private credentials.

Build and host features

The runtime runs images. It does not execute Compose builds. With zenith publish, an app using build: . and a root Dockerfile can be built from the pinned public GitHub commit before review. You do not need to publish that app image to GHCR yourself. A manifest using publicly pullable images remains supported.Do not infer support for arbitrary build contexts, alternate Dockerfiles, build options, or multiple build services from this publishing path. The CLI waits for the publish artifact before submitting. Zenith can reuse a successful preview image when its source matches the pinned commit.Bind mounts are rejected. The running deployment cannot read a path from the GitHub repository or Zenith host. Use an inline Compose config for static non-secret content, deployment environment values for credentials, or a named volume for mutable data. Do not commit secrets to the public repository.

Submission validation

CLI publishing checks the selected public GitHub commit and publishing authorization. When a publish build is required, the CLI waits for it to succeed before submitting the app for review. See publishing for account prerequisites, metadata, screenshots, and revision commands. Zenith also validates the manifest before accepting the app for review:
  1. Fetch the root zenith-compose.yml from the selected pinned commit.
  2. Enforce the non-empty 128 KiB file limit.
  3. Reject external file references, unsupported config sources, 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. Reject services with privileged: true.
  9. Render the complete app and reject invalid or duplicate runtime object names.
  10. Reject unresolved pre-v1 {{ZENITH_*}} tokens.

Schema validation

The schema requires these fields:
  • A top-level services map.
  • A top-level x-zenith map.
  • x-zenith.catalog with name, description, authors, tags, source, and license.
  • At least one x-zenith.expose entry.
  • x-zenith.expose entries with service, port, and web.
  • x-zenith.storage entries with volume.
  • x-zenith.configs entries with path.
  • x-zenith.env keys matching the environment-name pattern and declarations containing an input or one computed source.
The schema rejects unknown fields inside x-zenith, rejects unknown top-level fields unless they begin with x-, and enforces the documented name, URI, enum, uniqueness, and numeric range constraints. A deployment target may impose additional checks. Runtime validation also rejects:
  • An empty Compose service set.
  • A runtime service without an image after any supported publish build has been resolved.
  • Endpoint references to missing services.
  • Invalid or colliding service names.
  • Colliding persistent-volume directory names.
  • Unknown config references, unsupported config sources or ownership, invalid config targets, and config mount collisions.
  • Invalid public storage references.
  • Invalid environment declarations.
Passing validation makes the app eligible for review. It does not publish the app automatically.

x-zenith defaults

Build the file

Return to the step-by-step guide.