Skip to main content
Towbar v2 reads towbar.yml and recursively discovers .towbar/apps/**/*.app.yml and .towbar/resources/**/*.resource.yml. Each file declares one entity. Branch mappings are configured in Repository → Environments, not in YAML. Download the root example and app example. Editor schemas are available for the root, apps, and resources. Sync validates the effective configuration after merging defaults and environment overrides.

Minimal app

towbar.yml
.towbar/apps/web.app.yml
Register both IP addresses in Towbar. SSH credentials and preparation settings stay in the control plane. Map production to main and staging to develop when connecting the repository.

Stable identity

An entity’s id is stable across file moves and display-name changes. IDs must be unique within the app or resource namespace in a repository. Each environment gets a separate instance with its own deployment history, secrets, server, volumes, and operations. A resource cannot change its type under the same ID. The environments map explicitly selects where an entity exists. Common fields are merged with that environment’s overrides: objects merge recursively and arrays replace the entire common array. id, name, resource type, secrets, and preview belong at entity level and cannot be overridden. Removing an entity or its environment membership archives that instance after a successful sync of the affected environment. A failed sync preserves the prior configuration and secret values. Declaring a new environment in Git does not connect it automatically; connect and map its branch in Towbar.

Automatic deployments

Set autoDeploy: true to deploy on changes from the environment’s mapped branch. Apps can restrict relevant repository paths with autoDeploy.inputs:
The example is an entity fragment; retain the app’s Dockerfile, server, and environment settings. Input paths are relative to the repository. Initial connection, branch changes, and explicit sync import configuration without launching a deployment. A mapped-branch push can sync and schedule opted-in workloads. Manual deployment remains available separately.

Managed secrets

Declare required key names under top-level secrets.build, secrets.runtime, secrets.preDeploy, and secrets.postDeploy in each entity file. Values are entered in Towbar. New keys appear unset after sync, existing values and references are preserved, and removed declarations delete their stored values in the synced environment. Missing values block deployment, not sync. An explicitly saved empty string counts as a value. Resources accept only secrets.runtime; build and hook stages are rejected. See Secrets for reference syntax and reveal controls.

Apps and Resources

Apps declare an explicit deployment.type: dockerfile, static, image, railpack, nixpacks, or buildpack. Compose projects use their own manifest kind. Resource files declare type: image, postgres, mysql, mariadb, mongodb, redis, dragonfly, keydb, or clickhouse. Image deployments require an explicit non-latest tag or digest. See Deployment modes, Resources, and Backups.

Private networks and operator access

Set container.network to join a Docker network and container.networkAlias for private discovery. Resource aliases default to their entity ID; use distinct aliases or networks when environments share a server. Optional access.sshTunnel.hostPort exposes a resource port on server loopback for SSH tunnels. Alias and port conflicts are rejected during validation or deployment.

Preview deployments

Enable previews on a target environment in towbar.yml and opt an app in with preview.enabled, preview.domain, and an optional preview.ttlHours. Same-repository PRs targeting that environment’s mapped branch use the immutable PR head. Preview secrets are isolated as preview:<environment> and never fall back to persistent environment values. Resources do not create PR previews. See Previews.

Domains and TLS

Set domains per environment so production and staging do not claim the same hostname. Domain ownership is checked across the workspace. See Domains and TLS.

Field reference

Root fields

version: 2 and a nonempty environments map are required. Each environment can set previews.enabled. Branch names, server settings, and entity directory paths are not root configuration fields.

App fields

id, name, and environments identify the entity. The effective configuration requires server, dockerfile, and container.port. It can also configure context, health, domains, TLS, container resources, hooks, automatic deployment, and vulnerability scanning.

Resource fields

id, name, type, and environments identify the entity. The effective configuration requires a registered server IP and settings appropriate to its type. Backup destinations and schedules are supported for all eight managed engines.

Validate locally

Associate the matching JSON Schema with each file in your editor. Schemas check file structure; Towbar sync also checks merged configuration, server ownership, entity identity, and cross-entity conflicts at an immutable Git commit. Version 1 single-file manifests are not supported.

Persistent app storage

Declare Docker-managed named volumes under container.volumes in an app file:
A volume is a directory, so files stored underneath the mount path survive container restarts, redeployments, and app code rollbacks. Its stable identity belongs to the app’s environment instance and volume name. Staging and PR previews never share production volumes. Environment overrides replace the entire volume list. Names must be unique and mount paths must be absolute, canonical application directories without overlapping paths or system mounts. Host-directory bind mounts are not accepted. Create the mount directory in the Dockerfile with ownership for the runtime user, for example RUN mkdir /app/uploads && chown node:node /app/uploads before USER node. A new volume is initialized from the image directory once. Later images never overwrite existing files. See examples/persistent-files in the repository for a runnable upload example. When adding storage to an already deployed app, explicitly choose where the initial data comes from:
previous-container imports that directory from the previous container while it is stopped, preserving file ownership and permissions. image initializes from the image instead. Without an explicit choice, attaching a new volume to an existing app is blocked. The setting only applies when initializing a new volume; existing volumes are reused. New PR previews initialize from their own image even when the base app declares previous-container; production files are never copied to previews. An interrupted import never becomes a ready volume. A retry discards only the verified, uncommitted initializer and restarts the import from the previous container. Unrecognized volumes are retained and block initialization. A missing previously initialized volume blocks deployment rather than silently creating empty storage. For apps with persistent storage, Towbar builds the candidate first, stops the previous container, initializes storage, runs pre-deploy hooks, and starts the candidate. This prevents concurrent writers and creates a brief interruption. Pre- and post-deploy hooks see the same volumes. If candidate startup or its health check fails, Towbar attempts to restart the previous container. Data changes made by hooks or the candidate are retained; app rollback is not a data restore. The self-managed Towbar worker cannot use this stop/start strategy. The app’s Storage page lists configured and retained volumes. Mount status comes from the latest server check, which also detects missing or incorrect mounts. Removing a declaration, expiring a preview, or archiving an app does not remove its volumes. Explicit, confirmed server orphan cleanup can delete volumes belonging to removed apps; ordinary deployment cleanup does not delete data. Storage is local to its server. Repository sync and deployment execution block reassigning an app with storage history to another server. Cross-server transfer requires a separate, deliberate migration; Towbar does not copy files between servers automatically. Local persistence does not protect against disk or server loss. Back up persistent application files with storage or infrastructure tooling appropriate for the server; Towbar does not back up or restore app volumes.

Scheduled application jobs

Apps accept an optional jobs list with at most 20 unique names. Each job has name, command, and schedule.cron; optional fields are description, schedule.timezone (only UTC), enabled (default true), and timeoutSeconds (default 300, range 5–3600). Names contain lowercase letters, digits and hyphens, start with a letter, and are limited to 63 characters. Jobs inherit the deployed app image, runtime secret values, container resources, network and persistent volumes. The manifest contains no credential values. Environment overrides replace the entire jobs list. See Scheduled jobs for timing, concurrency, interruption, preview and manual-run behavior.

Log forwarding

Apps and resources accept a logDrains list of team integration names: newrelic, axiom, betterstack, datadog, otlp, and loki. Environment overrides replace the list; use [] to disable it for an environment. Credentials stay in Towbar. Forwarding changes take effect after deployment. See Log forwarding for provider setup and delivery limits.
Last modified on September 9, 2026