dpndncY

Running dpndncY

Backups, upgrades, monitoring and hardening for an installation that is already up. The commands assume Docker Compose in /opt/dpndncy (as set up by deploy/install.sh); for Kubernetes, run the same steps with kubectl exec and your volume snapshot tooling.

#What to protect

All state is in the volume mounted at /app/data:

Contains Why it matters
dpndncY.db (with -wal and -shm files) Everything: tenants, users, projects, scans, findings, settings, audit log
licenses/ The installed licence
history/, sast/, caches Scan artifacts. Large, and partly rebuildable

Also keep your .env (Compose) or Helm values and Secret somewhere safe. APP_SECRET is in it: it encrypts the credentials stored in the database, so a restored database is only useful together with the same value.

#Backups

#Online backup of the database (no downtime)

SQLite can copy itself consistently while the service runs. This writes a copy inside the volume:

cd /opt/dpndncy
docker compose exec dpndncY node -e "
const fs = require('fs');
fs.mkdirSync('/app/data/backups', { recursive: true });
const name = '/app/data/backups/dpndncy-' + new Date().toISOString().replace(/[:.]/g, '-') + '.db';
new (require('better-sqlite3'))(process.env.DB_PATH || '/app/data/dpndncY.db')
  .backup(name).then(() => console.log('wrote', name));
"
docker compose cp dpndncY:/app/data/backups ./backups      # take it off the machine

Do not copy dpndncY.db itself while the service is running; without its -wal file the copy can be stale or inconsistent.

#Whole-volume backup (a few seconds of downtime)

docker compose stop dpndncY
docker run --rm -v dpndncy_dpndncy-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/dpndncy-data-$(date +%F).tgz -C /data .
docker compose start dpndncY

(docker volume ls shows the exact volume name; Compose prefixes it with the project folder name.)

Run one of these on a schedule, keep copies on other storage, and test a restore at least once.

#Restore

docker compose stop dpndncY
docker run --rm -v dpndncy_dpndncy-data:/data -v "$PWD":/backup alpine \
  sh -c "rm -rf /data/* && tar xzf /backup/dpndncy-data-2026-11-06.tgz -C /data"
docker compose start dpndncY

To restore only the database from an online backup, stop the service, copy the file over /app/data/dpndncY.db, delete dpndncY.db-wal and dpndncY.db-shm, and start it.

#Upgrading

  1. Read the changelog for the versions between yours and the target.
  2. Take a backup.
  3. Change the image tag (DPNDNCY_IMAGE in .env, or image.tag in Helm) to the new version.
  4. docker compose pull && docker compose up -d (or helm upgrade).
  5. Check /health reports the new version and sign in.

The database is migrated automatically at start-up. A migrated database is not guaranteed to work with an older version, so to roll back, restore the backup from step 2 together with the old image tag.

Browser sessions end when the server restarts; API tokens do not. Schedule upgrades outside working hours.

#Health and monitoring

What How
Is it up GET /health on the API address returns {"status":"ok","version":...,"uptime":...}. Use it for an uptime monitor; the container healthcheck and the Helm probes use it
Is it healthy Settings, System health shows live resource use, including event-loop lag, and the scan queue. Rising lag means the machine is too small for the load
Logs JSON lines on standard output: docker compose logs -f dpndncY, or kubectl logs. Ship them with your usual log agent
Who did what Settings, Activity (the operator) shows the audit log: sign-ins, setting changes, policy changes, scans

#Security checklist

  • HTTPS on both hostnames. Never run the evaluation HTTP mode for real data.
  • ENABLE_BOOTSTRAP_SUPERADMIN=false after the first sign-in.
  • Two-step verification enforced for each tenant (Settings, Sign-in security), or single sign-on.
  • A dedicated analyst service user for pipelines, one API token per pipeline system, short lifetimes where practical, and revoke tokens when a person or system leaves.
  • APP_SECRET generated randomly and stored in a secrets manager, not in a repository.
  • The image tag pinned to a version, and upgraded on purpose.
  • The API hostname restricted by your firewall to the networks your runners and agents come from, if they are not on the public internet. Everything except /health and /releases needs a token, but there is no reason to expose it more widely than required.
  • Backups encrypted and stored off the machine.
  • Outbound access limited to what is needed: api.osv.dev, nvd.nist.gov, api.github.com, the package registries your projects use. Air-gapped sites need none (Running dpndncY without internet access (air-gapped)).

#Rotating secrets

dpndncY has three kinds of secret, and they rotate differently.

Secret What it protects How it rotates
Signing key Sessions and API tokens Automatically, on a schedule, without signing anyone out
APP_SECRET Credentials stored in the database (git tokens, integration configurations, Maven repository password) With a tool, by hand, when you decide
Credentials you hold (operator password, API tokens, SMTP, NVD, GitHub, OIDC) Access to dpndncY and to other systems By hand, in the console or the other system

#A schedule to adopt

What How often How
Signing key Every 90 days Automatic. Set JWT_SECRET_ROTATION_DAYS=90 (the default in .env.example and the Helm chart)
API tokens Every 90 to 365 days Set API_TOKEN_MAX_DAYS so tokens expire; create the new token before the old one ends and update the CI secret
APP_SECRET Yearly, and at once if it may have leaked scripts/rotate-app-secret.js, below
Operator and user passwords Per your policy In the console. Keep ENABLE_BOOTSTRAP_SUPERADMIN=false, or a restart resets the operator password
SMTP, NVD, GitHub, OIDC secrets Per the issuer's policy Change the value and restart
TLS certificates Automatic Caddy renews them; with Kubernetes, cert-manager does

#The signing key

The signing key lives in a key ring in the database. A rotation adds a new key and retires the old one, which keeps verifying existing tokens until its window (JWT_SECRET_VERIFY_WINDOW_DAYS, default 400 days) ends and it is deleted. Nobody is signed out. The window is long so that a 365-day API token outlives a rotation.

Check it (owner role):

curl -H "Authorization: Bearer $TOKEN" https://api.dpndncy.example.com/api/admin/auth-secrets

The answer shows the active version, its age, the next rotation and any configuration warning. Rotate now:

curl -X POST -H "Authorization: Bearer $TOKEN" https://api.dpndncy.example.com/api/admin/auth-secrets/rotate

If a key may have leaked, rotate and end every older session and API token at once:

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"verifyWindowDays":0,"revokeOlder":true}' \
  https://api.dpndncy.example.com/api/admin/auth-secrets/rotate

Everyone is signed out and every API token stops working, so pipelines need new tokens. Each rotation is written to the audit log.

#APP_SECRET

APP_SECRET encrypts credentials at rest. It does not sign anything, so replacing it signs nobody out, but changing the value without re-encrypting would make the stored credentials unreadable. Use the tool:

  1. Stop dpndncY. The database must not be written while the tool runs.

  2. Dry run, to see what it would change:

    docker compose run --rm --no-deps --entrypoint node dpndncY \
      scripts/rotate-app-secret.js --db /app/data/dpndncY.db --old env:APP_SECRET --new generate

    The service already has the data volume and the current APP_SECRET, so the command needs nothing else. On Kubernetes, run the same command in a Job that mounts the data volume and the Secret.

  3. Apply. The tool writes a backup next to the database, re-encrypts, and then reads every value back with the new secret. It stops without changing anything if a value cannot be read with the old secret.

    docker compose run --rm --no-deps --entrypoint node dpndncY \
      scripts/rotate-app-secret.js --db /app/data/dpndncY.db --old env:APP_SECRET --new generate --apply

    With --new generate the new secret is printed once at the end; store it before you do anything else.

  4. Set APP_SECRET to the new value (.env, Secret, or secrets manager) and start dpndncY.

  5. If anything looks wrong, restore the backup the tool printed and put the old APP_SECRET back.

An installation that has no stored git tokens, integration configurations or Maven passwords has nothing to re-encrypt, and the tool says so.

#Licence

Settings, License. A new installation runs a 30-day trial. A licence is applied by pasting a key, entering an activation code, activating online, or importing a file (for air-gapped sites). After a licence expires there is a 7-day grace period, then the installation is read-only until it is renewed.

#Sizing and capacity

One container uses the memory the scans need; 4 GB is a floor, 8 GB comfortable. Scan time grows with repository size and the number of ecosystems. Concurrent scans queue rather than fail. If System health shows a growing queue or lag, give the machine more CPU and memory. There is no horizontal scaling: the database is a single SQLite file, so size up the one machine instead.