Skip to content

Configuration reference

Repod is configured through two files: .env (Docker Compose variables) and backend.env (backend application settings). This page documents every available option.


.env — Docker Compose variables

These variables control how the containers are built and exposed. Place a .env file next to docker-compose.yaml (cp .env.example .env).

Variable Default Description
BIND_HOST 0.0.0.0 IP address Docker binds container ports to. Set to 127.0.0.1 if behind a reverse proxy on the same host.
APT_PORT 80 Host port for depot-apt — serves .deb (APT) and .apk (Alpine) repositories.
RPM_REPO_PORT 8080 Host port for depot-rpm — serves .rpm repositories.
BACKEND_PORT 8000 Host port for the FastAPI backend.
FRONTEND_PORT 3003 Host port for the React web UI.
REACT_APP_API_URL (empty) URL the browser uses to call the backend API. Must stay empty so calls remain relative (/api/v1/...) and are proxied by the frontend's Nginx — see Frontend.
REACT_APP_REPO_URL http://localhost URL the browser uses to display APT/APK repository URLs to users.
REACT_APP_RPM_REPO_URL http://localhost:8080 URL the browser uses to display RPM repository URLs to users.
POSTGRES_PASSWORD repod_dev_password Password for the repod PostgreSQL user. Change this in production and keep it in sync with DATABASE_URL in backend.env.
APT_TLS_PORT 8085 Used by docker-compose.tls.ymldepot-apt falls back to this port when nginx-proxy takes over port 80 for HTTP→HTTPS redirection.

BIND_HOST in production

If you expose Repod directly (no reverse proxy), keep BIND_HOST=0.0.0.0. If you run a reverse proxy on the same host, set BIND_HOST=127.0.0.1 to prevent direct access to backend ports. See Reverse proxy guide.


backend.env — Application settings

Database

Variable Required Description
DATABASE_URL PostgreSQL connection string: postgresql://repod:<password>@db:5432/repod. db/engine.py raises RuntimeError at startup if unset. The db service (PostgreSQL 16) is defined in docker-compose.yaml; the password must match POSTGRES_PASSWORD in .env. Schema is managed by Alembic migrations in backend/alembic/versions/.

Repository format

Variable Default Description
REPO_FORMAT apt Read once at startup by format_router.py. Controls which package format(s) this instance manages:
Value .deb (reprepro) .rpm (createrepo_c) .apk (apk index)
apt (default)
rpm
apk
both
all

Any unrecognized value is rejected (warning logged, falls back to apt).

Authentication

Variable Required Description
JWT_SECRET_KEY HS256 signing secret for JWT tokens. Generate with openssl rand -hex 32. The application refuses to start in production if this is the default value.
JWT_ALGORITHM JWT algorithm. Default: HS256. Do not change unless you know what you're doing.
JWT_EXPIRE_MINUTES JWT token lifetime in minutes. Default: 60.
SETTINGS_ENCRYPTION_KEY recommended Dedicated key encrypting secrets stored in settings.json (SMTP/LDAP password, OIDC client_secret). Generate with openssl rand -hex 32. Falls back to JWT_SECRET_KEY if unset (a startup warning is logged) — keeping it independent means rotating JWT_SECRET_KEY doesn't break decryption of existing secrets.
WEBHOOK_SECRET HMAC secret for /webhooks/github and /webhooks/kev. Generate with openssl rand -hex 32. The application refuses to start in production if this is empty or a known default.

Initial admin account

By default no admin account is created — use the first-run setup wizard (POST /api/v1/setup, exposed in the web UI on first access) to create it interactively.

Variable Required Description
ADMIN_USERNAME Username of an admin account to pre-provision at startup (e.g. for automated deployments). Only takes effect together with a valid ADMIN_PASSWORD_HASH.
ADMIN_PASSWORD_HASH bcrypt hash of the admin password ($2a$/$2b$/$2y$). Generate: docker run --rm python:3.12-slim python -c "from passlib.hash import bcrypt; print(bcrypt.hash('YourPass1!'))". Escape $ as $$ in .env files. If empty or invalid, no admin is seeded and the setup wizard remains available.
SETUP_TOKEN Optional. If set, POST /api/v1/setup requires a matching X-Setup-Token header — closes the bootstrap race window before the first admin is created.

Environment mode

Variable Default Description
ENV production Set to development to enable Swagger UI (/docs), hot-reload, and relaxed proxy trust. Never set to development in production.
APP_VERSION Version string returned by /health.

Paths (pre-configured via Docker volumes — do not change)

Variable Value Description
POOL_DIR /repos/pool Canonical package store — .deb/.rpm files land here on upload
MANIFEST_DIR /repos/manifests Package manifest JSONs + index.json
STAGING_INCOMING /repos/staging/incoming Upload landing zone
STAGING_QUARANTINE /repos/staging/quarantine Quarantined packages
AUDIT_DIR /repos/audit Audit log JSONL files
INDEX_PATH /repos/manifests/index.json Aggregated package index file
INDEX_DIR /repos/package-index Full-text search working files
IMPORTS_DIR /repos/imports Working directory for sync/mirror imports
SETTINGS_PATH /repos/settings.json Application settings file
SECURITY_DIR /repos/security CVE decisions, CISA KEV and EPSS caches
GNUPG_HOME /repos/gnupg GPG keyring shared with depot-apt / depot-rpm
NGINX_LOGS_DIR /repos/logs Nginx access logs (for download stats)
CLAMAV_DB_DIR /var/lib/clamav ClamAV signature database
GRYPE_DB_CACHE_DIR /repos/grype-db Grype vulnerability database cache
REPREPRO_BASE /repos -b path for reprepro — contains conf/, dists/, db/, pool/ (APT mode)
DISTS_DIR /repos/dists reprepro-managed Release + Packages trees (APT mode)
CONF_DIR /repos/conf reprepro distribution configuration (APT mode)
REPO_BASE /repos/rpm createrepo_c directories per <distro>/<arch>/ (RPM mode)
ADD_RPM_SCRIPT /scripts/add-rpm.sh Script called by backend to index new .rpm packages
APK_REPO_BASE /repos/apk Alpine repositories — APKINDEX.tar.gz + .apk files (APK mode)
SSH_KEY_PATH /home/appuser/.ssh/id_ed25519 Ed25519 key for SSH-based inventory scanning

Network & proxy

Variable Default Description
TRUSTED_PROXIES 127.0.0.1,172.16.0.0/12,192.168.0.0/16 Comma-separated list of trusted reverse proxy IPs or CIDR ranges. Used by uvicorn --forwarded-allow-ips to trust X-Forwarded-For headers.
CORS_ORIGINS Comma-separated allowed origins (e.g. https://repod.example.com). Must include every host/IP+port used to reach the frontend.

SMTP (email notifications)

Configured via the web UI (Settings → Email) and stored encrypted in settings.json. Not set via environment variables.

LDAP

Configured via the web UI (Settings → LDAP) and stored in settings.json. See Configure LDAP.


settings.json — Runtime configuration

The file at /repos/settings.json (mounted volume) stores runtime settings modified through the web UI. It is read at startup and updated when you save changes in the UI. Secrets inside it (SMTP/LDAP password, OIDC client_secret) are encrypted with SETTINGS_ENCRYPTION_KEY.

Info

You can edit settings.json directly as a text file if the web UI is unavailable, but encrypted fields must be set through the API/UI (plaintext values written by hand will not decrypt). Restart the backend container after manual edits: docker compose restart backend.

Full default structure

settings.json (defaults)
{
  "app_url": "http://localhost:3003",

  "sync": {
    "enabled": true,
    "hour": 3,
    "minute": 0
  },

  "sources": {
    "ubuntu-jammy": true,
    "ubuntu-jammy-updates": true,
    "ubuntu-noble": true,
    "ubuntu-focal": true,
    "debian-bookworm": true,
    "ubuntu-jammy-security": true,
    "ubuntu-noble-security": true,
    "ubuntu-focal-security": true,
    "debian-bookworm-security": true
  },

  "mirror": {
    "enabled": false,
    "hour": 4,
    "minute": 30,
    "max_packages_per_run": 200,
    "max_runtime_minutes": 60,
    "min_free_disk_gb": 5,
    "sources": {}
  },

  "notifications": {
    "webhook_url": "",
    "webhook_enabled": false,
    "webhook_min_packages": 1
  },

  "email": {
    "enabled": false,
    "smtp_host": "",
    "smtp_port": 587,
    "smtp_user": "",
    "smtp_password": "",
    "from_address": "",
    "to_addresses": "",
    "use_tls": true
  },

  "ldap": {
    "enabled": false,
    "host": "",
    "port": 389,
    "use_ssl": false,
    "use_starttls": false,
    "bind_dn": "",
    "bind_password": "",
    "base_dn": "",
    "user_filter": "(sAMAccountName={username})",
    "attr_username": "sAMAccountName",
    "attr_email": "mail",
    "attr_fullname": "displayName",
    "attr_groups": "memberOf",
    "group_admin": "",
    "group_maintainer": "",
    "group_uploader": "",
    "group_auditor": "",
    "group_reader": "",
    "default_role": "reader",
    "auto_provision": true,
    "verify_cert": true,
    "ca_bundle_path": ""
  },

  "retention": {
    "audit_days": 90,
    "import_cleanup_days": 30
  },

  "backup": {
    "enabled": false,
    "hour": 4,
    "minute": 30
  },

  "validation": {
    "sha256_check": true,
    "clamav_scan": true,
    "grype_scan": true,
    "grype_fail_on": "critical",
    "max_upload_size_mb": 500
  },

  "cve_policy": {
    "critical":          "block",
    "high":              "review",
    "medium":            "warn",
    "low":               "allow",
    "negligible":        "allow",
    "sla_critical_days": 0,
    "sla_high_days":     30,
    "sla_medium_days":   90,
    "auto_enrich":       true
  }
}

Section reference

Section Key Default Description
sync enabled true Whether the daily security sync cron runs (Enterprise only)
sync hour 3 Hour of day for the sync cron (Europe/Paris timezone)
sync minute 0 Minute for the sync cron
sources <source-id> true Toggle each upstream APT source on/off
mirror enabled false Whether the daily bulk mirror cron runs (Enterprise only)
mirror sources {} Per-source bulk mirror configuration, opt-in
notifications webhook_url "" Slack/Teams/Mattermost incoming webhook URL — validated by the SSRF guard before use
notifications webhook_enabled false Enable webhook notifications
notifications webhook_min_packages 1 Minimum packages changed to trigger a webhook
email enabled false Enable SMTP email notifications
email smtp_host "" SMTP server hostname
email smtp_port 587 SMTP port (465 → implicit TLS, 587/25 → STARTTLS)
email use_tls true Use STARTTLS
ldap enabled false Enable LDAP/AD authentication
ldap user_filter (sAMAccountName={username}) LDAP search filter; {username} is replaced at login
ldap default_role reader Role assigned when no group mapping matches
ldap auto_provision true Create a local user record on first LDAP login
ldap verify_cert true Validate the LDAP server TLS certificate
retention audit_days 90 Days to keep audit log files (Enterprise only)
retention import_cleanup_days 30 Days to keep import staging files (Enterprise only)
backup enabled false Whether the daily backup cron runs (Enterprise only)
validation clamav_scan true Enable antivirus scan on upload
validation grype_scan true Enable CVE scan on upload
validation max_upload_size_mb 500 Maximum package file size accepted
cve_policy critical block Action for Critical CVEs: block, review, warn, allow
cve_policy high review Action for High CVEs
cve_policy medium warn Action for Medium CVEs
cve_policy low allow Action for Low CVEs
cve_policy negligible allow Action for Negligible CVEs
cve_policy sla_critical_days 0 SLA days for Critical CVE decisions (0 = immediate)
cve_policy sla_high_days 30 SLA days for High CVE decisions
cve_policy sla_medium_days 90 SLA days for Medium CVE decisions
cve_policy auto_enrich true Fetch EPSS scores and CISA KEV flags at upload time

Resource limits (Docker Compose)

The backend container has default resource limits:

docker-compose.yaml (excerpt)
deploy:
  resources:
    limits:
      memory: 2.5g
      cpus: "1.5"

The memory limit is 2.5g because the clamd daemon requires approximately 800 MB to load the ClamAV signature database into memory, on top of the Python process and Grype's working set. Increase further for large repositories or high upload throughput. Grype and ClamAV scans are CPU-intensive.


ClamAV daemon configuration

ClamAV scanning uses the clamd daemon, which is started by entrypoint.sh at container boot. Key details:

  • appuser is added to the clamav group so it can write to /var/lib/clamav.
  • The Unix socket is at /var/run/clamav/clamd.ctl.
  • Signatures (~700 MB) are loaded once at container start; clamscan is used as a fallback only if the daemon is unavailable.
  • Manual signature updates are available via Settings → ClamAV in the web UI.

GPG configuration

GPG key generation uses --pinentry-mode loopback and requires gpg-agent, which is included in the Docker image.

GNUPG_HOME defaults to /repos/gnupg — a volume shared between the backend and the repository containers (depot-apt, depot-rpm).


Community Edition — excluded modules

The Community Edition does not include the following service modules; the code is simply absent and REPOD_EDITION has no effect:

Module Description
cve_enrichment EPSS/KEV enrichment at upload time
security_decisions CVE review workflow (pending/approve/reject)
sla_alerts SLA deadline tracking and alerts
sbom SBOM export endpoints (returns HTTP 402)
ldap_auth LDAP/AD authentication (returns HTTP 401)
email_notifications SMTP email alerts
security_sync / mirror Scheduled and manual CVE database sync / bulk mirroring
retention Audit log and import cleanup retention policy
inventory SSH-based machine inventory and remote install
leader_election (HA) Multi-replica active-passive scheduling

Changing REPOD_EDITION in your environment file has no effect — the modules above are not compiled into the Community image.


Environment: production vs. development

Behaviour ENV=production ENV=development
Swagger UI (/docs, /redoc, /openapi.json) Disabled (404) Enabled
JWT secret validation Refuses to start if missing or default Warning log only
Source code mount Not mounted (image is immutable) ./backend:/app mounted via docker-compose.dev.yml
Docker socket Never mounted Not mounted (GPG uses shared volume in all modes)

To run in development mode:

docker compose -f docker-compose.yaml -f docker-compose.dev.yml up