Troubleshooting¶
Common issues and how to resolve them. Check the backend logs first:
Startup issues¶
Backend fails to start — JWT_SECRET_KEY missing¶
Symptom: Container exits immediately with:
Cause: The JWT_SECRET_KEY variable is empty or missing from backend.env.
Fix:
# Generate a strong key
openssl rand -hex 32
# Add to backend.env:
JWT_SECRET_KEY=<generated-value>
docker compose up -d backend-api
ClamAV takes 60+ seconds to start¶
Symptom: The health endpoint returns "clamav": {"ok": false} for up to
2 minutes after startup.
Cause: ClamAV loads ~800 MB of signatures into memory on first start. This is normal behavior.
Fix: Wait. Subsequent starts are faster because the database is cached in
the repos/clamav-db/ volume. If the problem persists after 5 minutes, check:
Database error: connection refused / password authentication failed¶
Symptom:
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) connection to server at "db" ... refused
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) FATAL: password authentication failed for user "repod"
Cause: backend-api started before PostgreSQL finished initializing, or
DATABASE_URL (in backend.env) does not match POSTGRES_PASSWORD (in .env)
used to initialize the db container.
Fix:
# Check the db container is healthy
docker compose ps db
docker compose logs db --tail=50
# Verify DATABASE_URL matches the credentials the db volume was initialized with
docker exec backend-api env | grep DATABASE_URL
docker exec repod-db env | grep POSTGRES_PASSWORD
# Restart backend-api once db is healthy
docker compose restart backend-api
Changing POSTGRES_PASSWORD after first start has no effect
PostgreSQL only applies POSTGRES_PASSWORD when initializing an empty
postgres_data volume. If you change it afterwards, update the password
inside PostgreSQL too (ALTER USER repod WITH PASSWORD '...') or the
DATABASE_URL in backend.env will stop matching.
Upload failures¶
"Erreur serveur" on package upload¶
Symptom: The web interface shows "Erreur serveur" after selecting a file.
Cause: Nginx's client_max_body_size is set below the file size. The default
in some configurations is 1 MB, which blocks any real package upload.
Diagnosis:
docker compose logs frontend-ui | grep 413
# Or check in the browser Network tab — look for HTTP 413 on the upload request
Fix: The nginx.conf in frontend/nginx.conf should contain:
Upload rejected — "Erreur réseau: NetworkError"¶
Symptom: Upload fails with a network error, before the server even receives the file.
Cause (most common): The frontend was built with REACT_APP_API_URL=http://localhost:8000,
causing the browser to try to reach localhost:8000 on the client machine rather
than the server.
Diagnosis:
Fix: Set the correct URL in .env and rebuild:
# .env
REACT_APP_API_URL=http://192.168.1.100:8000 # or your server's actual address
docker compose build frontend-ui
docker compose up -d frontend-ui
Package stuck in pending_review¶
Symptom: A package was uploaded successfully but does not appear in the APT/RPM
repository. The web interface shows Status: pending_review.
Cause: The CVE scan found vulnerabilities matching the configured review policy.
The package is stored but not published until approved.
Fix: 1. Open Security → Review queue in the web interface 2. Review the CVE findings for the package 3. Click Approve to publish, or Reject to remove from the queue
Alternatively via API:
curl -X POST http://localhost:8000/api/v1/security/packages/mypackage/1.0.0/decide \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"approve","justification":"Vulnerability does not apply to our use case"}'
APT client issues¶
apt update fails — NO_PUBKEY¶
Symptom:
W: GPG error: http://repod.example.com:80 jammy InRelease: The following signatures
couldn't be verified because the public key is not available: NO_PUBKEY ABCD1234...
Cause: The repository's GPG public key is not trusted by the client machine.
Fix:
# Re-import the key from the repository
curl -fsSL http://repod.example.com:80/repos/dists/jammy/InRelease \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/repod.gpg > /dev/null
sudo apt update
apt update fails — 404 Not Found¶
Symptom:
Cause: The distribution was not initialized, or the distribution name in
sources.list does not match a supported codename.
Fix:
# Re-initialize distributions
curl -X POST http://localhost:8000/api/v1/distributions/init \
-H "Authorization: Bearer $TOKEN"
# Verify the distribution exists
ls /repos/dists/
RPM client issues¶
dnf install fails — GPG key retrieval failed¶
Symptom:
warning: /var/cache/dnf/repod/packages/mypackage-1.0.0.rpm: Header V4 RSA/SHA256
Signature, key ID ABCD1234: NOKEY
Cause: The GPG key was not imported before enabling gpgcheck=1.
Fix:
createrepo_c errors in backend logs¶
Symptom:
Cause: The RPM distribution directory does not exist or has wrong permissions.
Fix:
# Re-initialize RPM distributions
curl -X POST http://localhost:8000/api/v1/distributions/init \
-H "Authorization: Bearer $TOKEN"
# Check permissions
docker exec backend-api ls -la /repos/almalinux9/
Sync / import issues¶
"Erreur inconnue" during sync (ImportPage)¶
Symptom: The sync stream shows "Erreur inconnue" immediately when started.
Cause (common): The frontend was built with an older bundle that used incorrect
API URLs (missing /api/v1 prefix). The browser is cached on the old build.
Fix: Hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R). If the problem
persists, clear the browser cache or rebuild the frontend:
Index sync returns 0 packages¶
Symptom: The sync completes without errors but no packages appear in the catalog.
Cause (APT): The external source's Release file cannot be fetched (network
restriction, wrong URL, or missing GPG key for the source).
Cause (RPM): The source's repodata/repomd.xml is unreachable, or the
PostgreSQL package index table (package_index) is empty/out of sync for
that source.
Diagnosis:
Fix (re-run sync):
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"YourPassword"}' | jq -r .access_token)
curl -X POST http://localhost:8000/api/v1/import/sync/start \
-H "Authorization: Bearer $TOKEN"
Authentication issues¶
Login fails with valid credentials¶
Symptom: The login page returns "Identifiants incorrects" despite using the correct username and password.
Possible causes:
1. The ADMIN_PASSWORD_HASH in backend.env contains unescaped $ signs (only applies if pre-provisioning an admin — most deployments instead create the first admin via the setup wizard, POST /api/v1/setup)
2. The users table is empty (PostgreSQL not yet initialized, or DATABASE_URL points at the wrong database)
3. The account is disabled (is_active: false)
4. No admin account exists yet — check GET /api/v1/setup/status; if admin_exists: false, create one via POST /api/v1/setup
Diagnosis:
# Check the users table directly in PostgreSQL
docker exec repod-db psql -U repod -d repod -c \
"SELECT username, role, is_active FROM users;"
Fix for hash escaping:
# In backend.env: replace every $ with $$
# Wrong: ADMIN_PASSWORD_HASH=$2b$12$...
# Correct: ADMIN_PASSWORD_HASH=$$2b$$12$$...
MFA code rejected¶
Symptom: The TOTP code entered during MFA authentication is rejected despite being generated by the correct authenticator app.
Cause: The server's system clock differs from the client's by more than 30 seconds. TOTP codes are time-based and fail if clocks are not synchronized.
Fix:
# Check server time
docker exec backend-api date
# Sync the host clock (if using NTP)
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd
Performance issues¶
Backend is slow or unresponsive¶
Symptom: API requests take more than 10 seconds, or the health endpoint times out.
Cause (most common): ClamAV or Grype is performing a database update. This is CPU-intensive and can saturate the container's CPU allocation.
Diagnosis:
Fix: The default resource limits in docker-compose.yaml are 1.5 CPUs and 2.5 GB RAM.
Increase if your hardware allows:
Viewing logs¶
# All containers
docker compose logs -f
# Backend only, last 200 lines
docker compose logs backend-api --tail=200
# Filter for errors
docker compose logs backend-api 2>&1 | grep -i "error\|exception\|traceback"
# Audit log (last 50 entries)
docker exec backend-api tail -n 50 /repos/audit/$(date +%Y-%m-%d).jsonl | python3 -m json.tool