Skip to content

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

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.