Skip to content

Configuration

Holden is configured via environment variables on the container and an apps.yml file in /data/.

Set these on the Holden container:

VariableRequiredDefaultDescription
HOLDEN_BASE_DATA_DIRYesBase path for app data. Each app gets {base_data_dir}/{app-id}/
HOLDEN_TRAEFIK_NETWORKYesDocker network where Traefik runs
HOLDEN_TRAEFIK_ENTRYPOINTNowebsecureTraefik entrypoint name for HTTPS traffic
HOLDEN_TRAEFIK_CERTRESOLVERNoletsencryptTraefik certificate resolver name for TLS
HOLDEN_POLL_INTERVALNo300How often to re-reconcile all apps (seconds). 0 to disable. See Polling
HOLDEN_DANGLING_IMAGESNofalsePrune dangling Docker images after each cycle. Warning: affects ALL dangling images on host
HOLDEN_PUBLIC_DOMAINNoDomain for Holden’s webhook endpoint (port 6020). Generates Traefik labels if set
HOLDEN_EXTERNALLY_MANAGEDNofalseDisables Overseer and self-update
HOLDEN_WEBHOOK_SECRETNoHMAC secret for webhook signature validation. Min 32 characters. If not set, the webhook endpoint returns 503
HOLDEN_API_KEYNoShared secret for management API routes (future dashboard). Min 32 characters. If not set, management routes return 503
HOLDEN_MAINTENANCE_SCHEDULENo0 3 * * *Cron expression for maintenance window
HOLDEN_BACKUP_DIRNoDirectory for backup snapshots. If not set, backups are skipped
HOLDEN_MAX_BACKUPSNo10How many backups to keep per app
GITHUB_USERNAMENoGitHub username for GHCR image pulls
GITHUB_PATNoPAT for private GitHub repos and GHCR images
GITLAB_PATNoPAT for private GitLab repos
BITBUCKET_PATNoPAT for private Bitbucket repos

See Fetch Configs for PAT details.

Dangling images are untagged images left behind when pulling a new version of the same tag. When you pull myapp:latest and a new image comes down, the old image loses its tag and becomes “dangling.”

Why disabled by default: docker image prune affects all dangling images on the host, not just Holden-managed ones. If you run other Docker workloads alongside Holden, enabling this could remove their images. Only enable if Holden is the sole Docker workload on the host.

Set to true if Holden’s container is managed externally (e.g., Pulumi, Ansible, or another orchestrator). Holden skips all Overseer checks and never recreates its own container.

When false (the default), Holden manages its own lifecycle — spawning an Overseer on boot and after maintenance to pull the latest image and recreate itself.

The domain where Holden’s webhook endpoint is accessible. When set, the Overseer generates Traefik labels to route traffic to Holden’s webhook port (6020).

If not set, you’ll need to configure your own routing to Holden’s webhook endpoint.


App registrations live in /data/apps.yml. This file is managed via the CLI (holden app add/remove) or the internal API.

apps:
my-app:
repo: https://github.com/you/my-app
branch: main
path: ./

The key (my-app) becomes the app ID.

FieldRequiredDescription
repoYesGit repository URL
branchNoBranch to track (default: repo’s default branch)
pathNoPath to holden.yml within the repo (default: ./)

Monorepos — multiple apps can point to the same repo with different path values:

apps:
frontend:
repo: https://github.com/you/monorepo
path: ./apps/frontend
backend:
repo: https://github.com/you/monorepo
path: ./apps/backend
Terminal window
# Add an app
holden app add my-app --repo https://github.com/you/my-app
# List registered apps
holden app list
# Remove an app
holden app remove my-app

See CLI Reference for details.


Holden stores all persistent state in /data/:

/data/
apps.yml # App registrations
password_seed # Auto-generated, derives need passwords
age.key # Auto-generated, encrypts/decrypts secrets

Mount this as a volume. password_seed and age.key auto-generate on first boot.


Holden validates configuration at startup and refuses to start if critical settings are invalid:

ErrorBehavior
HOLDEN_BASE_DATA_DIR missingHolden exits with error message
HOLDEN_TRAEFIK_NETWORK missingHolden exits—this field is required
App repo unreachableLogged, app skipped, retried on next trigger

App-level failures (unreachable repos, invalid holden.yml) don’t prevent Holden from starting. They’re logged and the app is retried when next triggered by polling, webhook, or maintenance.