Pryv.io core migration
This guide describes how to migrate a Pryv.io core to a new machine — that is, moving the running core binary, its configuration and its persistent data to a fresh host while keeping the same public URL.
Since v2 (2026) Pryv.io runs as a single binary (
bin/master.js) in a single Docker image (pryvio/open-pryv.io). Moving a core is therefore a single-service migration: copy the config, copy the data directories (plus your database dump), start the core on the new host, and either point DNS or proxy traffic from the old host while DNS propagates. There is no longer a separateregister,hfs,previewormfacontainer to coordinate.
The strategy:
- Install the core on the dest machine
- Copy config and data from source to dest
- Bring the core up on dest
- Either update DNS to point to dest, or keep source alive as an NGINX proxy to dest during DNS propagation
- Stop source once traffic has drained
(Optional) Create a verification user on source
Section titled “(Optional) Create a verification user on source”Create a test user with a few streams and events on the source core before the migration. After the cutover, log in as that user on dest and check that the data is intact — a quick naked-eye check complements the automated verification steps later.
Set up the dest machine
Section titled “Set up the dest machine”On dest, install the same core version as source:
- Either pull the Docker image (same tag as on source:
docker pull pryvio/open-pryv.io:<tag>) - Or clone the same commit and run
just setup-dev-env && just install(native install)
Also install the database you use (PostgreSQL or MongoDB), unless you are running the database on a separate host that will remain unchanged.
Set up an SSH key pair for rsync:
ssh-keygen -t rsa -b 4096 -C "migration@remote"# private key stays on source at ${PATH_TO_PRIVATE_KEY}# public key goes into ~/.ssh/authorized_keys on destTransfer data
Section titled “Transfer data”Transfer configuration
Section titled “Transfer configuration”Copy the override YAML(s) used on source. Their path depends on how you run the core. Typical locations:
- Docker deploy: the mounted config volume (e.g.
/etc/pryv/override.yml) - Native deploy: the file passed via
--configtobin/master.js - Dokku deploy: the app config stored under
/home/dokku/<app>/
# on sourcersync --verbose --copy-links --archive --compress \ -e "ssh -i ${PATH_TO_PRIVATE_KEY}" \ /etc/pryv/ \ ${USERNAME}@${DEST_MACHINE}:/etc/pryv/If the machine’s IP, private network or database host changes as part of the migration, edit the override YAML on dest accordingly (for example storages.engines.postgresql.host or core.ip).
Dump and transfer the database
Section titled “Dump and transfer the database”Stop the core on source before dumping, so the dump is consistent and no writes come in during the transfer:
# Docker: docker stop pryvio_open_pryv_io# Systemd: systemctl stop pryv-corePostgreSQL (recommended):
# on source (or on your PG host)pg_dump -U postgres -Fc pryv_db > /tmp/pryv_db.dumprsync -e "ssh -i ${PATH_TO_PRIVATE_KEY}" \ /tmp/pryv_db.dump \ ${USERNAME}@${DEST_MACHINE}:/tmp/pryv_db.dump
# on dest (restore into an empty database)createdb -U postgres pryv_dbpg_restore -U postgres -d pryv_db /tmp/pryv_db.dumpMongoDB:
# on sourcemongodump --db=pryv-node --out=/tmp/mongodumprsync --archive --compress \ -e "ssh -i ${PATH_TO_PRIVATE_KEY}" \ /tmp/mongodump/ \ ${USERNAME}@${DEST_MACHINE}:/tmp/mongodump/
# on destmongorestore /tmp/mongodump/InfluxDB (optional — only if you use InfluxDB for high-frequency series; PostgreSQL is fine too and moves with the PG dump above):
# on sourceinfluxd backup -portable /tmp/influx-backup
# on dest (after rsync)influxd restore -portable /tmp/influx-backupTransfer file-system data
Section titled “Transfer file-system data”Per-user SQLite DBs, attachments, previews and the rqlite platform DB all live under the core’s data/ directory (see INSTALL — Data directories). Transfer the whole tree:
# on source — adjust paths to match your filesystem and sqlite.path configrsync --verbose --copy-links --archive --compress \ -e "ssh -i ${PATH_TO_PRIVATE_KEY}" \ /var/lib/pryv/data/ \ ${USERNAME}@${DEST_MACHINE}:/var/lib/pryv/data/Ensure file ownership on dest matches the UID the core runs as (root for the stock Docker image; the pryv user for native installs).
Launch the core on dest
Section titled “Launch the core on dest”Start the core:
# Dockerdocker start pryvio_open_pryv_io
# Native / systemdsystemctl start pryv-coreWatch the logs for a clean startup — master.js should report its workers ready, rqlited up, and HTTP listeners bound:
docker logs -f pryvio_open_pryv_io# orjournalctl -u pryv-core -fDo not start the core on dest while source is still serving traffic with the same database — both cores writing to the same base storage will corrupt data.
Cut over
Section titled “Cut over”Option A — update DNS and stop source
Section titled “Option A — update DNS and stop source”Simplest path when you can schedule a short outage:
- Keep source stopped after the dump.
- Start the core on dest.
- Update the DNS A record for the public domain to the new IP.
- Wait for propagation. Clients that still hit the old IP while DNS is stale will fail; this is the outage window.
Option B — proxy from source to dest during DNS propagation
Section titled “Option B — proxy from source to dest during DNS propagation”Zero-downtime variant: keep source’s reverse proxy alive and point it at dest until DNS propagates, then retire source.
In v2 the core exposes just two HTTP upstreams — API on port 3000 and HFS on port 4000 (see INSTALL — Running behind nginx). Replace the api_backend / hfs_backend upstreams in source’s NGINX:
# on source — was pointing at localhostupstream api_backend { server 127.0.0.1:3000; }upstream hfs_backend { server 127.0.0.1:4000; }
# becomes (TLS-terminated by dest, plain https upstream)upstream api_backend { server ${DEST_CORE_IP}:443; }upstream hfs_backend { server ${DEST_CORE_IP}:443; }and change every proxy_pass http://api_backend / http://hfs_backend to https://api_backend / https://hfs_backend.
Reload NGINX on source. All traffic now flows client → source NGINX → dest. When DNS has propagated (monitor source’s access log until it goes idle), stop NGINX on source and decommission the host.
Verify
Section titled “Verify”Once traffic is landing on dest:
- Log in as the verification user from the pre-migration step and spot-check recent events.
- Watch the core logs to confirm that API calls arrive on dest and not on source.
- Hit the basic endpoints described in the healthchecks guide.
Multi-core deployments — update the PlatformDB entry
Section titled “Multi-core deployments — update the PlatformDB entry”In v2 multi-core mode each core self-registers into the rqlite PlatformDB on startup with its core.id, core.ip, core.available and (for DNSless multi-core) core.url. Restarting the moved core on dest automatically re-advertises the new IP/URL — there is no register machine or admin panel to edit.
Two things to check after the move:
-
core.idis unchanged — the ID identifies this core’s user partition in the platform. Keep it the same on dest so existing user→core mappings stay valid. -
Other cores can reach the new host — the Raft port (default 4002) must be open between all cores. From another core:
Terminal window curl -s https://core-x.mc.example.com/system/admin/cores -H 'Authorization: <admin-key>'The moved core should appear in the list with its new IP.
If you run v1 and still need to update the old register machine’s HOSTINGS_AND_CORES entry, see the v1 register migration reference for the legacy procedure.

