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 theweb 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
servicemust name an existing Compose service.portmust be between1and65535.webmust be present, even when its value isfalse.exposemust contain at least one entry. Use an entry withoutlabelfor the main hostname and custom-domain target. Labels must be unique.forward_auth, when present, must be a boolean. Omission andfalseboth 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.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:
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
Authorizationheaders 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.
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
Whenpublic is true:
volumemust be set.- The named volume must exist in the top-level Compose
volumesmap. - The volume cannot be external.
- No other public storage entry may reference the same volume.
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
storagemust reference an existing storage entry whosevolumeis a declared, non-external named volume.pathmust identify a file within that volume./, parent traversal, repeated slashes, backslashes, control characters, trailing slashes, and the.zenith-files-tmpand.file-manager-tmppath 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: trueseparately 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
storageremain legacy metadata and do not expose an editor.
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.
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:
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.
generate
generate accepts an RE2 regular expression. Zenith expands it into a deterministic value and stores the result.
template
template substitutes {NAME} references with declarations or built-ins on every render.
{{ 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.
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.
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 rootDockerfile, withoutimageon that service. - Publicly pullable
imagereferences for the other services. - A primary
x-zenith.exposeentry withweb: trueand nolabel.
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:CMDruns the argument list directly.CMD-SHELLruns through/bin/sh -c.interval,timeout, andretriesmap to probe timing.start_periodcreates a startup probe budget.- Readiness uses a failure threshold of
3.
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 Composeconfigs 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
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 forzenith 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>.fileis rejected. - Top-level
configs.<name>.fileand every other non-inline config source are rejected. - Service
env_fileandlabel_fileare rejected. - Service
extendsis rejected. - Top-level
includeis rejected. - Service
privileged: trueis rejected.
Build and host features
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:- Fetch the root
zenith-compose.ymlfrom the selected pinned commit. - Enforce the non-empty
128 KiBfile limit. - Reject external file references, unsupported config sources, and bind mounts.
- Load the Compose project with an empty host environment and discard environment files.
- Parse
x-zenithstrictly. - Validate required catalogue and endpoint fields.
- Resolve every
x-zenith.envdeclaration against deterministic test deployment data. - Reject services with
privileged: true. - Render the complete app and reject invalid or duplicate runtime object names.
- Reject unresolved pre-v1
{{ZENITH_*}}tokens.
Schema validation
The schema requires these fields:- A top-level
servicesmap. - A top-level
x-zenithmap. x-zenith.catalogwithname,description,authors,tags,source, andlicense.- At least one
x-zenith.exposeentry. x-zenith.exposeentries withservice,port, andweb.x-zenith.storageentries withvolume.x-zenith.configsentries withpath.x-zenith.envkeys matching the environment-name pattern and declarations containing an input or one computed source.
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.