Documentation
Backup & restore
Documents the current release. We keep improving the platform, so some details may change. Ask support if anything looks off.
This page covers what holds state, how to back it up, and the exact order to restore it. Read Escrow the encryption key first before anything else — the key is the one thing a backup cannot recover if it is lost.
What holds state
| Store | Contents | Criticality |
|---|---|---|
/opt/netends/.env | The encryption key, the database, JWT and event-store secrets, and the image coordinates | Critical — escrow it |
/var/lib/netends/install-state/install-identity | A file on the host, outside Docker, holding the installation’s identity and evaluation clock. Your node-locked license is bound to that identity | Critical — back it up with the host |
postgres_data volume | The system of record: inventory, users and roles, encrypted device credentials, config backups, policies, licenses and metric history | Critical |
clickhouse_data volume | Telemetry: syslog, SNMP traps and flows | Important — losing it leaves a gap in history; the platform still works |
nos_images volume | OS upgrade images you uploaded | Re-uploadable |
/opt/netends/certs/ | The HTTPS certificate and key | Back up your own certificate. The generated self-signed one can be re-created, with a new fingerprint |
redis_data, batfish_data, app_logs volumes | Broker and cache, what-if model snapshots, diagnostic logs — rebuilt or transient | Skip |
Volume names are prefixed with your install directory’s name — run docker volume ls | grep netends to see the real names, for example netends_postgres_data when installed at /opt/netends.
A host rebuilt without the install-identity file and without a database restore gets a new Installation ID, and a license bound to the old one will not activate until NetEnds issues a replacement.
Escrow the encryption key first
Your device credentials (SSH, SNMP, API) are encrypted at rest with the encryption key in /opt/netends/.env. A database restore without the original key loses every stored credential. The platform still runs, but every device action asks for credentials again until they are re-entered.
At install time:
- Copy the
ENCRYPTION_KEY=line from/opt/netends/.envinto your secrets manager or offline escrow. Escrowing the whole.envfile is better still — it also covers the database, JWT and other secrets. - Record where it is escrowed in your runbook.
Verify escrow on every backup drill: the key in escrow must equal the key in the running .env.
Back up
Database (system of record)
An online dump, with no downtime:
cd /opt/netends
docker compose exec -T postgres \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
> netends-pg-$(date +%Y%m%d-%H%M).dump
The single quotes matter: $POSTGRES_USER and $POSTGRES_DB are read inside the database container, where they hold the names your installation uses.
Schedule it, and ship the dumps off the host together with a copy of .env and the install-identity file.
Telemetry (event store)
The simplest reliable route is the cold volume snapshot below — it captures the event store and everything else in one pass, with no extra tooling. Losing telemetry history is a gap, not an outage, so weigh any extra tooling against that.
Cold volume snapshot (everything, stack stopped)
cd /opt/netends
docker compose down
mkdir -p backup
for v in postgres_data clickhouse_data nos_images; do
docker run --rm \
-v "netends_${v}:/from:ro" -v "$PWD/backup:/to" \
alpine tar czf "/to/${v}.tar.gz" -C /from .
done
cp .env backup/env.snapshot && chmod 600 backup/env.snapshot
docker compose up -d
Adjust the netends_ prefix to what docker volume ls shows.
Restore
Order matters. The backend applies database migrations when it starts, so the database must be restored before the application comes up against it — and .env must be restored before anything starts.
-
Install NetEnds on the new host at the same or a newer release, then stop it:
cd /opt/netends && docker compose down. -
Restore
.env— overwrite/opt/netends/.envwith your escrowed copy. The original encryption key is what makes your stored credentials readable. If the escrowed copy is older than the release you installed, keep the new install’sNETENDS_REGISTRYandNETENDS_VERSIONlines. Thenchmod 600 .env. -
Restore the data — before starting anything.
From a cold snapshot, unpack each volume archive back into its volume:
cd /opt/netends for v in postgres_data clickhouse_data nos_images; do docker run --rm \ -v "netends_${v}:/to" -v "$PWD/backup:/from:ro" \ alpine sh -c "rm -rf /to/* && tar xzf /from/${v}.tar.gz -C /to" doneFrom a database dump, start the database only, recreate the database, and run the restore. The time-series extension needs its pre- and post-restore steps:
docker compose up -d postgres docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d postgres \ -c "DROP DATABASE IF EXISTS \"$POSTGRES_DB\";" \ -c "CREATE DATABASE \"$POSTGRES_DB\" OWNER \"$POSTGRES_USER\";"' docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \ -c "CREATE EXTENSION IF NOT EXISTS timescaledb;" \ -c "SELECT timescaledb_pre_restore();"' docker compose exec -T postgres sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" \ --no-owner' < netends-pg-<stamp>.dump docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \ -c "SELECT timescaledb_post_restore();"' -
Start the stack —
docker compose up -d. If the backup came from an older release, the migrations at startup bring the schema forward automatically.
Verify the restore
A health check only proves the process is up. The real test is an authenticated read that exercises the database, sign-in and decryption together. The platform serves HTTPS with a self-signed certificate until you install your own, so these commands, run on the host itself, pass -k:
TOKEN=$(curl -kfsS -X POST https://localhost/api/auth/login \
-d 'username=admin' -d 'password=<admin password>' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
curl -kfsS -H "Authorization: Bearer $TOKEN" \
'https://localhost/api/inventory/devices?page=1&page_size=5'
It worked if:
- Sign-in returns a token — users and password hashes are restored.
- The device list returns your inventory, not an empty fresh-install set.
- A device poll or SSH action works in the product — that proves the encryption key matches your restored credentials.
If every credentialed action fails, the encryption key does not match the data. Stop. Put the correct escrowed key into .env and run docker compose up -d again — do not re-enter credentials on top of a wrong key.