Environment Variables¶
All runtime configuration is injected via environment variables. The backend reads
them at startup from the backend.env file (loaded by Docker Compose via
env_file), plus a handful set directly in docker-compose.yaml (paths,
REPO_FORMAT, DATABASE_URL). The frontend reads a small subset baked in at
build time.
Docker Compose variable escaping
In any file loaded with env_file:, literal $ characters must be escaped as $$.
This affects bcrypt hashes ($$2b$$12$$...) and any value containing a dollar sign.
Backend variables¶
Database¶
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
DATABASE_URL |
string |
— | Yes | PostgreSQL connection string: postgresql://repod:<password>@db:5432/repod. db/engine.py raises RuntimeError at startup if unset. All relational data (users, manifest index, inventory, install jobs, package search, ssh_known_hosts) lives here, managed via SQLAlchemy Core + Alembic. |
Repository format¶
| Variable | Type | Default | Description |
|---|---|---|---|
REPO_FORMAT |
string |
apt |
Read once at startup by format_router.py. One of apt, rpm, apk, both (apt+rpm), or all (apt+rpm+apk). Any other value is rejected (warning logged, falls back to apt). |
Authentication & security¶
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
JWT_SECRET_KEY |
string |
— | Yes | HMAC-SHA256 signing key for JWT tokens. Generate with openssl rand -hex 32. Minimum 32 characters. The application refuses to start in production if this is empty or set to a known-weak default. |
JWT_EXPIRE_MINUTES |
integer |
60 |
No | Token lifetime in minutes. Tokens expire and must be re-issued after this period. API tokens have their own expiry set at creation time. |
SETTINGS_ENCRYPTION_KEY |
string |
falls back to JWT_SECRET_KEY |
No (recommended) | Dedicated key used to encrypt secrets stored in settings.json (SMTP/LDAP password, OIDC client_secret). Generate with openssl rand -hex 32. Keeping it separate from JWT_SECRET_KEY means rotating the JWT key doesn't break decryption of these stored secrets. |
ADMIN_USERNAME |
string |
— | No | Username of an admin account to pre-provision at startup. Only takes effect together with a valid ADMIN_PASSWORD_HASH. If unset, no admin is seeded and the first-run setup wizard (/api/v1/setup) is used instead. |
ADMIN_PASSWORD_HASH |
string |
— | No | bcrypt hash of the admin password (must start with $2a$, $2b$, or $2y$). Generate with python -c "from passlib.hash import bcrypt; print(bcrypt.hash('password'))". Escape every $ as $$ in the env file. If empty or not a valid bcrypt hash, no admin row is seeded — the setup wizard remains available (safe default). |
SETUP_TOKEN |
string |
— | No | If set, POST /api/v1/setup requires a matching X-Setup-Token header (compared with hmac.compare_digest), closing the race window between container startup and first-admin creation. GET /api/v1/setup/status is unaffected. |
WEBHOOK_SECRET |
string |
— | Yes (production) | HMAC secret used to verify signatures on /webhooks/github and /webhooks/kev, and validated by the SSRF guard for outbound notification webhooks. Generate with openssl rand -hex 32. The application refuses to start in production if this is empty or set to a known-default placeholder. |
REPOD_LICENSE_VENDOR_KEY |
string |
— | Yes (production, Enterprise) | HMAC-SHA256 secret for signing/verifying Enterprise license keys. The application refuses to start in production if this is the default value. |
TRUSTED_PROXIES |
string |
127.0.0.1,172.16.0.0/12,192.168.0.0/16 |
No | Comma-separated list of IP addresses or CIDR ranges trusted to set X-Forwarded-For. Used by the rate limiter (slowapi) to extract the real client IP when behind a reverse proxy. |
CORS_ORIGINS |
string |
http://localhost:3003 |
No | Comma-separated list of allowed CORS origins. Include your frontend URL in production (e.g. https://repod.example.com). |
Storage paths¶
These variables control where Repod stores its data. The defaults match the Docker Compose volume mounts and should not be changed unless you know what you are doing.
| Variable | Type | Default | Description |
|---|---|---|---|
POOL_DIR |
path |
/repos/pool |
Canonical package store — all validated .deb/.rpm binaries land here on upload. Served read-only by the repository Nginx containers. |
MANIFEST_DIR |
path |
/repos/manifests |
Directory for per-package JSON manifests and the central index.json catalog. |
STAGING_INCOMING |
path |
/repos/staging/incoming |
Temporary landing zone for uploaded files before validation. Never served over HTTP. |
STAGING_QUARANTINE |
path |
/repos/staging/quarantine |
Destination for files that failed validation or were blocked by CVE policy. |
AUDIT_DIR |
path |
/repos/audit |
Append-only JSONL audit log files, one per day (YYYY-MM-DD.jsonl). |
INDEX_PATH |
path |
/repos/manifests/index.json |
Path to the central package index JSON file (atomic writes via tempfile + os.replace()). |
INDEX_DIR |
path |
/repos/package-index |
Working directory for sync/import indexing. The package-index full-text search data itself lives in PostgreSQL. |
IMPORTS_DIR |
path |
/repos/imports |
Working directory for packages imported from external sources (sync/mirror). |
SETTINGS_PATH |
path |
/repos/settings.json |
Persistent settings file (LDAP config, webhook, CVE policy, retention, mirror, backup, etc.). |
SECURITY_DIR |
path |
/repos/security |
Directory for CVE decisions, CISA KEV cache, and EPSS cache. |
GNUPG_HOME |
path |
/repos/gnupg |
GPG keyring directory shared between the backend and the repository containers (depot-apt, depot-rpm). |
NGINX_LOGS_DIR |
path |
/repos/logs |
Directory where the repository Nginx containers write access.log. Parsed by the download statistics endpoint. |
CLAMAV_DB_DIR |
path |
/var/lib/clamav |
ClamAV signature database directory. Populated by freshclam at startup and updated daily. |
GRYPE_DB_CACHE_DIR |
path |
/repos/grype-db |
Grype vulnerability database cache. Updated automatically when stale (> 24 h). |
SSH_KEY_PATH |
path |
/home/appuser/.ssh/id_ed25519 |
Ed25519 private key used by ssh_scanner.py for SSH-based inventory scanning (Enterprise). |
SBOM_DIR |
path |
/repos/sboms |
Stored CycloneDX SBOMs captured at scan time (services/component_sbom.py), used for CVE re-matching without reopening the original package file. |
MANIFEST_CACHE_TTL |
integer |
30 |
In-memory manifest cache TTL, in seconds (services/manifest.py). |
OCI registry (Zot)¶
Used when the docker-compose.oci.yml overlay is deployed.
| Variable | Type | Default | Description |
|---|---|---|---|
ZOT_URL |
string |
http://depot-oci:5000 |
Internal URL of the Zot OCI registry container. Backend admin/read calls only — docker push/pull never transit through the backend. |
OCI_SIGNING_KEY_PATH |
path |
/repos/oci/keys/private.pem |
RSA private key used to sign OCI Bearer tokens (RS256). Generate with scripts/gen-oci-signing-keys.sh. |
OCI_TOKEN_ISSUER |
string |
repod-oci-registry |
iss claim on issued OCI tokens. Must match Zot's configured service value. |
OCI_TOKEN_TTL_SECONDS |
integer |
300 |
Lifetime, in seconds, of an issued OCI Bearer token. |
TRIVY_SERVER_URL |
string |
http://depot-trivy:4954 |
URL of the Trivy server sidecar used by the dual-scan cross-verification feature (services/dual_scan.py). SaaS only — only set by docker-compose.saas.yml, absent in EE on-premise/CE. |
Upstream cache (pull-through proxy)¶
Used when the depot-cache service is deployed.
| Variable | Type | Default | Description |
|---|---|---|---|
UPSTREAM_CACHE_DATA_DIR |
path |
/repos/upstream-cache/data |
Cached upstream package/index files. Backend-writable, so a purge can delete cached data. Mounted at the same absolute path into depot-cache. |
UPSTREAM_CACHE_CONF_DIR |
path |
/repos/upstream-cache/conf |
Generated per-target Nginx config fragments, watched by scripts/cache-reload-watcher.sh inside depot-cache to trigger a reload. |
License server integration & HA (Redis)¶
| Variable | Type | Default | Required | Description |
|---|---|---|---|---|
LICENSE_SERVER_INTERNAL_TOKEN |
string |
— | Yes (DEPLOYMENT_MODE=saas) |
Shared secret checked on GET /internal/tenants, called by the standalone license-server/ vendor tool for its read-only SaaS status panel. Must match the same variable in license-server/.env. |
REDIS_URL |
string |
— | No | redis://host:6379/0. Backs services/cache.py's response cache. Unset means an in-memory cache (the default everywhere today — the redis:7-alpine Compose service is opt-in via the ha-active-active profile). Fail-soft: a connection error at startup falls back to in-memory. |
JOB_STATE_BACKEND |
string |
local |
No | local or redis. Selects the state backend for scan_manager/install_manager/mirror_manager/sync_manager (services/job_state_backend.py), sse_bus's pub/sub backend (services/sse_redis_bus.py), and logging_config's pub/sub + bounded-history backend (services/logs_redis_bus.py). redis requires JOB_STATE_REDIS_URL/REDIS_URL to be reachable at startup, or each component independently falls back to local (logged at ERROR; visible per-flow at GET /health → checks.info.ha.job_state_backend). |
JOB_STATE_REDIS_URL |
string |
falls back to REDIS_URL |
No | Optional override of REDIS_URL specifically for JOB_STATE_BACKEND=redis, for operators who want job-coordination traffic on a separate Redis instance/DB from the response cache. |
APT-mode paths¶
Used when REPO_FORMAT is apt, both, or all.
| Variable | Type | Default | Description |
|---|---|---|---|
REPREPRO_BASE |
path |
/repos |
Base directory passed to reprepro -b. Reprepro expects conf/, db/, dists/, and pool/ under this path. Also used by services/reprepro.py:add_package() to invoke reprepro includedeb directly (no shell wrapper). |
DISTS_DIR |
path |
/repos/dists |
APT distribution tree managed by reprepro. Served by depot-apt. |
CONF_DIR |
path |
/repos/conf |
Reprepro configuration directory. Contains the distributions file generated at initialization. |
RPM-mode paths¶
Used when REPO_FORMAT is rpm, both, or all.
| Variable | Type | Default | Description |
|---|---|---|---|
ADD_RPM_SCRIPT |
path |
/scripts/add-rpm.sh |
Path to the shell script that copies the .rpm to the distribution directory and invokes createrepo_c --update. |
REPO_BASE |
path |
/repos/rpm |
Base directory for RPM distribution trees. Each distribution/arch gets a <codename>/<arch>/ subdirectory under this path, served by depot-rpm. |
APK-mode paths¶
Used when REPO_FORMAT is apk or all.
| Variable | Type | Default | Description |
|---|---|---|---|
APK_REPO_BASE |
path |
/repos/apk |
Base directory for Alpine repositories. Each distribution gets a <codename>/main/<arch>/APKINDEX.tar.gz tree, served by depot-apt under /apk/. |
External integrations¶
| Variable | Type | Default | Description |
|---|---|---|---|
LDAP_URL |
string |
— | LDAP server URL (e.g. ldap://dc.example.com:389 or ldaps://dc.example.com:636). Leave empty to disable LDAP authentication. Configured via Settings → LDAP, stored in settings.json — listed here for reference only. |
SMTP_* |
string |
— | SMTP host/port/user/password/from address for email notifications (SLA alerts). Configured via Settings → Email, stored encrypted in settings.json. |
WEBHOOK_URL |
string |
— | URL to POST JSON event payloads on security events (CVE blocks, SLA breaches, sync/mirror results). Configured via Settings → Notifications, validated against the SSRF guard before each request. |
Network / outbound proxy¶
| Variable | Type | Default | Description |
|---|---|---|---|
HTTP_PROXY / HTTPS_PROXY |
string |
— | Standard proxy environment variables, honored automatically by the Python HTTP client, Grype, and the Docker CLI/skopeo (used for OCI import) — set in backend.env if the server has no direct internet access. |
NO_PROXY |
string |
— | Comma-separated list of hosts to reach directly, bypassing the proxy (typically internal LDAP/SMTP servers, your license-server if self-hosted). |
ClamAV needs its own configuration
freshclam (ClamAV signature updates) does not read HTTP_PROXY/HTTPS_PROXY
— it requires a proxy directive in its own config file. See
Proxy configuration for the full list of
external dependencies and a worked example with Squid.
Runtime behavior¶
| Variable | Type | Default | Description |
|---|---|---|---|
ENV |
string |
development |
Set to production to enable strict mode (rejects weak JWT_SECRET_KEY/WEBHOOK_SECRET/REPOD_LICENSE_VENDOR_KEY, disables Swagger UI). |
APP_VERSION |
string |
dev |
Version string returned by the /health endpoint. Set to a semver tag in production images. |
Frontend variables (build-time)¶
Build-time only
React environment variables prefixed with REACT_APP_ are embedded in the
JavaScript bundle at docker build time. Changing them after the image is
built has no effect. Pass them as Docker Compose build.args.
| Variable | Default | Description |
|---|---|---|
REACT_APP_API_URL |
(empty) | Base URL of the backend API. Must stay empty in normal deployments — the frontend then uses relative URLs (/api/v1/...) and relies on its Nginx reverse proxy. Setting it bakes an absolute URL into the bundle and breaks cross-host access. |
REACT_APP_REPO_URL |
http://localhost |
Base URL of the APT/APK repository server, shown to users in client setup instructions. |
REACT_APP_RPM_REPO_URL |
http://localhost:8080 |
Base URL of the RPM repository server, shown to users in client setup instructions. |
Docker Compose variables (.env file)¶
These variables are interpolated by Docker Compose itself before launching
containers. Place them in a .env file next to docker-compose.yaml
(cp .env.example .env).
| Variable | Default | Description |
|---|---|---|
BIND_HOST |
0.0.0.0 |
Host interface to bind all published ports to. Set to 127.0.0.1 in production when a reverse proxy handles external traffic. |
BACKEND_PORT |
8000 |
Host port mapped to the backend-api container. |
FRONTEND_PORT |
3003 |
Host port mapped to the frontend-ui container. |
APT_PORT |
80 |
Host port mapped to depot-apt (serves .deb + .apk). |
RPM_REPO_PORT |
8080 |
Host port mapped to depot-rpm (serves .rpm). |
POSTGRES_PASSWORD |
repod_dev_password |
Password for the repod PostgreSQL user — must match the password in DATABASE_URL (backend.env). Change in production. |
APT_TLS_PORT |
8085 |
Used by docker-compose.tls.yml; depot-apt falls back to this port when nginx-proxy takes port 80 for HTTPS redirection. |
REACT_APP_API_URL |
(empty) | Passed as a build argument to the frontend image — keep empty. |
REACT_APP_REPO_URL |
http://localhost |
Passed as a build argument to the frontend image. |
REACT_APP_RPM_REPO_URL |
http://localhost:8080 |
Passed as a build argument to the frontend image. |
Example: production backend.env¶
# ── Database ─────────────────────────────────────────────────────────────────
DATABASE_URL=postgresql://repod:CHANGE_ME@db:5432/repod
# ── Repository format ───────────────────────────────────────────────────────
REPO_FORMAT=all
# ── Security ─────────────────────────────────────────────────────────────────
JWT_SECRET_KEY=a3f8c2e1d4b7a9f0e2c5d8b1a4f7e0c3d6b9a2f5e8c1d4b7a0f3e6c9d2b5a8f1
JWT_EXPIRE_MINUTES=60
SETTINGS_ENCRYPTION_KEY=7f1e4a9c2b8d5f0a3e6c9b2d5f8a1c4e7b0d3f6a9c2e5b8d1f4a7c0e3b6d9f2a
WEBHOOK_SECRET=9d2f5b8e1a4c7f0d3b6e9c2a5f8d1b4e7c0a3f6d9b2e5c8f1a4d7b0e3c6f9a2d
# Optional: pre-provision the first admin (otherwise use the setup wizard,
# POST /api/v1/setup, on first access — recommended)
#ADMIN_USERNAME=admin
#ADMIN_PASSWORD_HASH=$$2b$$12$$exampleHashHere...
# Optional: protect POST /api/v1/setup until the first admin is created
#SETUP_TOKEN=4e7a0d3f6b9c2e5a8d1f4b7e0c3a6d9f2b5e8c1a4d7f0b3e6c9a2d5f8b1e4c7a
# ── Environment ──────────────────────────────────────────────────────────────
ENV=production
APP_VERSION=v1.2.0
# ── Reverse proxy ────────────────────────────────────────────────────────────
TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8
CORS_ORIGINS=https://repod.example.com
LDAP, SMTP, webhook URL, mirror sources, retention, and CVE policy are configured
through Settings in the web UI and stored (encrypted where relevant) in
settings.json — see Configuration reference.