Configuration
Holden is configured via environment variables on the container and an apps.yml file in /data/.
Environment Variables
Section titled “Environment Variables”Set these on the Holden container:
| Variable | Required | Default | Description |
|---|---|---|---|
HOLDEN_BASE_DATA_DIR | Yes | — | Base path for app data. Each app gets {base_data_dir}/{app-id}/ |
HOLDEN_TRAEFIK_NETWORK | Yes | — | Docker network where Traefik runs |
HOLDEN_TRAEFIK_ENTRYPOINT | No | websecure | Traefik entrypoint name for HTTPS traffic |
HOLDEN_TRAEFIK_CERTRESOLVER | No | letsencrypt | Traefik certificate resolver name for TLS |
HOLDEN_POLL_INTERVAL | No | 300 | How often to re-reconcile all apps (seconds). 0 to disable. See Polling |
HOLDEN_DANGLING_IMAGES | No | false | Prune dangling Docker images after each cycle. Warning: affects ALL dangling images on host |
HOLDEN_PUBLIC_DOMAIN | No | — | Domain for Holden’s webhook endpoint (port 6020). Generates Traefik labels if set |
HOLDEN_EXTERNALLY_MANAGED | No | false | Disables Overseer and self-update |
HOLDEN_WEBHOOK_SECRET | No | — | HMAC secret for webhook signature validation. Min 32 characters. If not set, the webhook endpoint returns 503 |
HOLDEN_API_KEY | No | — | Shared secret for management API routes (future dashboard). Min 32 characters. If not set, management routes return 503 |
HOLDEN_MAINTENANCE_SCHEDULE | No | 0 3 * * * | Cron expression for maintenance window |
HOLDEN_BACKUP_DIR | No | — | Directory for backup snapshots. If not set, backups are skipped |
HOLDEN_MAX_BACKUPS | No | 10 | How many backups to keep per app |
GITHUB_USERNAME | No | — | GitHub username for GHCR image pulls |
GITHUB_PAT | No | — | PAT for private GitHub repos and GHCR images |
GITLAB_PAT | No | — | PAT for private GitLab repos |
BITBUCKET_PAT | No | — | PAT for private Bitbucket repos |
See Fetch Configs for PAT details.
Dangling Images
Section titled “Dangling Images”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.
Externally Managed
Section titled “Externally Managed”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.
Public Domain
Section titled “Public Domain”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.
apps.yml
Section titled “apps.yml”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.
| Field | Required | Description |
|---|---|---|
repo | Yes | Git repository URL |
branch | No | Branch to track (default: repo’s default branch) |
path | No | Path 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/backendManaging Apps
Section titled “Managing Apps”# Add an appholden app add my-app --repo https://github.com/you/my-app
# List registered appsholden app list
# Remove an appholden app remove my-appSee CLI Reference for details.
/data/ Directory
Section titled “/data/ Directory”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 secretsMount this as a volume. password_seed and age.key auto-generate on first boot.
Error Behavior
Section titled “Error Behavior”Holden validates configuration at startup and refuses to start if critical settings are invalid:
| Error | Behavior |
|---|---|
HOLDEN_BASE_DATA_DIR missing | Holden exits with error message |
HOLDEN_TRAEFIK_NETWORK missing | Holden exits—this field is required |
| App repo unreachable | Logged, 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.