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

StoreContentsCriticality
/opt/netends/.envThe encryption key, the database, JWT and event-store secrets, and the image coordinatesCritical — escrow it
/var/lib/netends/install-state/install-identityA file on the host, outside Docker, holding the installation’s identity and evaluation clock. Your node-locked license is bound to that identityCritical — back it up with the host
postgres_data volumeThe system of record: inventory, users and roles, encrypted device credentials, config backups, policies, licenses and metric historyCritical
clickhouse_data volumeTelemetry: syslog, SNMP traps and flowsImportant — losing it leaves a gap in history; the platform still works
nos_images volumeOS upgrade images you uploadedRe-uploadable
/opt/netends/certs/The HTTPS certificate and keyBack up your own certificate. The generated self-signed one can be re-created, with a new fingerprint
redis_data, batfish_data, app_logs volumesBroker and cache, what-if model snapshots, diagnostic logs — rebuilt or transientSkip

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:

  1. Copy the ENCRYPTION_KEY= line from /opt/netends/.env into your secrets manager or offline escrow. Escrowing the whole .env file is better still — it also covers the database, JWT and other secrets.
  2. 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.

  1. Install NetEnds on the new host at the same or a newer release, then stop it: cd /opt/netends && docker compose down.

  2. Restore .env — overwrite /opt/netends/.env with 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’s NETENDS_REGISTRY and NETENDS_VERSION lines. Then chmod 600 .env.

  3. 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"
    done

    From 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();"'
  4. 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.