Configuration
Every PACKYARD_* environment variable, what it does, what defaults look like, and which ones the server refuses to start without.
Updated April 27, 2026
All Packyard configuration is via PACKYARD_* environment variables. The interactive wizard (packyard init) writes them to an env file (default /etc/packyard/packyard.env) that the systemd unit reads on boot. You can also export them in a shell, set them in a Docker --env-file, or pass them into a Kubernetes Deployment.
The server refuses to start if a required value is missing — there's no silent default for anything security-relevant.
Server
| Variable | Default | Purpose |
|---|---|---|
PACKYARD_PORT |
9090 |
HTTP listen port. |
PACKYARD_BASE_URL |
(required) | Canonical public URL (e.g. https://repo.example.com). Embedded in Composer dist URLs — Composer clients fetch zip downloads from this hostname, so it must match the public-facing URL exactly. |
PACKYARD_LOG_LEVEL |
info |
One of debug, info, warn, error. |
Database
PACKYARD_DB_DRIVER=sqlite # sqlite | mysql | postgres
PACKYARD_DB_HOST=localhost
PACKYARD_DB_PORT=3306
PACKYARD_DB_NAME=packyard
PACKYARD_DB_USER=packyard
PACKYARD_DB_PASSWORD=
PACKYARD_DB_SSLMODE=disable
PACKYARD_DB_PATH=./packyard.db # SQLite only| Variable | Default | Purpose |
|---|---|---|
PACKYARD_DB_DRIVER |
sqlite |
Pick sqlite for a single host, mysql or postgres when you need a managed database (e.g. multi-instance deploys, separate DB host). |
PACKYARD_DB_PATH |
./packyard.db |
SQLite file path (relative to the working directory or absolute). Ignored for mysql / postgres. |
PACKYARD_DB_HOST / _PORT / _NAME / _USER / _PASSWORD |
— | Connection details for mysql / postgres. |
PACKYARD_DB_SSLMODE |
disable |
Postgres SSL mode (disable, require, verify-full, etc.). |
Migrations run automatically on startup against the configured driver. Run them ahead of time with packyard migrate if you'd rather not have a first request stall.
Storage
PACKYARD_STORAGE_TYPE=local # local | s3
PACKYARD_STORAGE_LOCAL_PATH=./data/packages| Variable | Default | Purpose |
|---|---|---|
PACKYARD_STORAGE_TYPE |
local |
local keeps zips on disk; s3 puts them in any S3-compatible bucket (AWS, Cloudflare R2, MinIO, Backblaze B2). |
PACKYARD_STORAGE_LOCAL_PATH |
./data/packages |
Directory for local storage. |
For s3:
PACKYARD_S3_BUCKET=packyard-packages
PACKYARD_S3_REGION=us-east-1
PACKYARD_S3_ENDPOINT= # e.g. https://s3.eu-central-003.backblazeb2.com
PACKYARD_S3_ACCESS_KEY=
PACKYARD_S3_SECRET_KEY=
PACKYARD_S3_FORCE_PATH_STYLE=false # true for MinIO, R2, BackblazeUse path-style addressing (PACKYARD_S3_FORCE_PATH_STYLE=true) for non-AWS providers that don't support virtual-host buckets.
Auth and sessions
| Variable | Default | Purpose |
|---|---|---|
PACKYARD_SESSION_SECRET |
(required) | 32+ char random string used to sign session cookies (HMAC-SHA256). The server refuses to start if this is shorter than 32 characters. Generate with openssl rand -hex 32. |
PACKYARD_SESSION_MAX_AGE |
86400 |
Session lifetime in seconds (default: 24 h). |
PACKYARD_COOKIE_DOMAIN |
empty | Session-cookie Domain attribute. Empty scopes the cookie to the exact hostname (correct when the dashboard and Composer endpoint share a host). Set to a parent domain like .example.com to share login state across subdomains. |
PACKYARD_COOKIE_SAMESITE |
strict |
strict (safest), lax, or none. Use lax if you've set PACKYARD_COOKIE_DOMAIN and need the cookie to survive cross-subdomain navigations. none requires HTTPS. |
PACKYARD_TRUSTED_PROXIES |
empty | Comma-separated CIDRs (or bare IPs) of reverse proxies whose X-Forwarded-For header is honored. Leave empty when the server is exposed directly — otherwise the per-IP rate limiter can be bypassed by header injection. Example: 10.0.0.0/8,127.0.0.1. |
PACKYARD_BCRYPT_COST |
12 |
bcrypt cost factor for password hashing. |
Performance and retention
| Variable | Default | Purpose |
|---|---|---|
PACKYARD_DOWNLOAD_RETENTION_DAYS |
90 |
A daily goroutine prunes download_events rows older than this. 0 keeps events forever (useful for audit trails; the table grows without bound). Per-version cumulative counters (download_count) are never affected — they persist regardless. |
PACKYARD_DIST_RATELIMIT |
120 |
Requests allowed per PACKYARD_DIST_RATELIMIT_WINDOW_SECONDS per client IP on the Composer /dist/ endpoint. Caps how fast a compromised token can inflate counters. Defaults accommodate normal composer install bursts. |
PACKYARD_DIST_RATELIMIT_WINDOW_SECONDS |
60 |
Rate-limit window. |
PACKYARD_STATS_CACHE_TTL_SECONDS |
60 |
How long /packages/stats responses stay cached in memory, per-org. Six DB queries per request collapse into one per TTL window. 0 disables caching. |
PACKYARD_SYNC_WORKERS |
2 |
Number of goroutines pulling sync jobs from the queue. 0 disables the worker pool entirely (useful for read-only replicas). |
PACKYARD_JOB_RETENTION_DAYS |
30 |
Daily prune of finished sync_jobs rows. Active (queued/running) jobs are never touched. 0 disables pruning. |
Git providers
Optional. Used when the dashboard syncs packages from a Git host's release pages.
PACKYARD_GITHUB_TOKEN= # GitHub personal access token
PACKYARD_GITLAB_TOKEN= # GitLab personal access tokenPublic repositories work without a token but are subject to lower rate limits.
Initial admin (first-run only)
PACKYARD_ADMIN_EMAIL=[email protected]
PACKYARD_ADMIN_PASSWORD=changemeUsed once on first boot to create the super-admin user. Subsequent runs ignore these values — change the admin password from the dashboard or with packyard admin user reset-password.
Verifying your config
Without starting the server:
packyard check-config # validates the env file
packyard check-db # confirms the DB is reachable
packyard healthcheck # probes /healthz on the configured portcheck-config is the one to run first after editing the env file — it surfaces missing required values, malformed durations, and bad SSL modes before you go to start the service.