Operations
Restore
Verify a backup set and restore it, in a drill or in a real recovery.
A restore drill is the only thing that turns “we have backups” into “we can recover”. Run one before go-live and again after every key rotation.
Verifying a set
Run php artisan vaultigen:backup:verify /path/to/completed-set with the original recovery inputs
and matching database engine. It authenticates the manifest, validates the format and paths, and
checks every file and checksum without applying the restore. Format 2 also checks original APP_KEY,
audit/passkey secrets, and required KEK values. Format 1 sets remain supported, but cannot validate
all secret values in advance; preserve the exact originals and complete a drill.
Two failure signals mean “do not restore this set, use another one”:
- a manifest MAC mismatch: the set was altered after it was written;
- a checksum mismatch: one of the files is corrupt.
Do not try to repair a set.
The restore drill
-
Prepare a clean host or a disposable installation with the same Vaultigen version. Never practise on production.
-
Run
php artisan downand stop web, queue workers, and scheduler. Keep all services offline throughout restoration; only one host may access this installation. -
Provide the matching recovery inputs: the same
installation.json(installation ID, audit key, passkey secret), the samekek.json, the samebackup.jsonand the sameAPP_KEY. Do not run initialization, key generation, seeding, or new-version migrations on the target. Prepare an empty database and private file store with the same engine and original identity; the snapshot supplies the schema. -
Copy the backup set to the host and run (for Docker Compose, see below):
php artisan vaultigen:restore /path/to/backup-set --services-stopped--services-stoppedconfirms that all other application processes have stopped; Laravel must also be down. Without--forcethe command prints a summary, including the key identifiers the set needs, and asks for confirmation. Read it. -
After successful restoration, run
php artisan up, restart services, and test: sign in, open a vault, reveal a login, download an attachment, restore an older revision, and look for therestore.completedevent in Activity. -
Write down how long it took and anything that surprised you.
Staging and failures
Restore privately stages encrypted files and decrypts/validates the snapshot before replacing the
database. It checks target permissions and space, rejects unsafe paths/links and incompatible
engines, restores attachments and retained import sources to their original private disk paths,
and checks the database inventory against the manifest. Database rows also restore images/icons.
On PostgreSQL, MySQL, and MariaDB it first removes every table in the target database, so tables
from a newer schema cannot survive a rollback to an older set; point it only at this installation’s
own database.
Stale jobs, sessions, caches, and operation state are reset only after files succeed;
restore.completed is recorded afterwards.
Database/files replacement is not atomic. If application fails, keep services offline. Plaintext
staging is always removed; storage/framework/restore-in-progress.json marks the partial restore.
Fix the cause and retry the same full set with original keys, or restart on a clean target. Do not
run artisan up on a partial restore. After success, verify MFA, revisions, images, import sources, and the audit chain in Activity as part of the drill.
Restoring for real
The procedure is the same. Additionally:
- Restore the newest set that you trust. After a suspected compromise, pick a set taken before the compromise.
- Make sure only one writer touches the database during the restore.
- After the restore, check Installation status and take a fresh backup.
Docker Compose
Restore onto a new server
This is both the restore drill and the real recovery after losing a server. You need:
- a completed backup set (the folder
backups/<id>), - your key copy (
vaultigen-keys.tar.gzwith thekeys/folder), - the original
.env, andcompose.proxy.yamlif you used it.
Use a fresh server with Docker and the compose.yaml of the release the backup was taken with (see Download). Keep VAULTIGEN_IMAGE_TAG and DB_PASSWORD from the original .env. For a drill, change only VAULTIGEN_DOMAIN to a test hostname; never run a drill against production.
umask 077
mkdir -p ~/vaultigen/data ~/vaultigen/backups && cd ~/vaultigen
curl -fLO https://raw.githubusercontent.com/vaultigen/vaultigen/v1.0.0/scripts/docker/compose.yaml
# copy the original .env here, then:
tar -xzf ~/vaultigen-keys.tar.gz -C ~/vaultigen/data
# copy the backup folder to ~/vaultigen/backups/BACKUP_ID, then:
docker compose up -d --wait database
docker compose run --rm --no-deps setup restore /backups/BACKUP_ID
docker compose up -d --wait
The restore container hands the folders to the application user, verifies the set against your keys, shows a summary and asks for confirmation, then restores the database and files with the application offline. It never generates keys, creates an administrator or runs migrations first. Run the last command only after the restore reports success.
Then sign in with an existing account and its authenticator, reveal a known entry, download its attachment, and look for restore.completed in Activity. Move DNS to the new server only after the old one is offline.
If the restore fails or you cancel the confirmation, the installation stays in maintenance mode. Fix the reported problem and run the same restore command again with the same set and the original keys. Do not start the application services in between, and never run setup without restore on a server that should receive an existing installation.
Copy the whole installation
Because the installation is one directory, you can also preserve or move it exactly as it is. This needs a short downtime; a copy of a running database is not valid:
cd ~/vaultigen
docker compose stop
sudo tar -czpf ~/vaultigen-full.tar.gz -C ~ vaultigen
docker compose up -d --wait
On the new server, unpack it with sudo tar -xzpf vaultigen-full.tar.gz -C ~ and run docker compose up -d --wait in ~/vaultigen. Do not run setup. The -p option keeps the owners and permissions the containers need.
This archive contains your keys and your data, unencrypted: anyone who has it can read your vaults. Encrypt it before it leaves the server (for example gpg --symmetric vaultigen-full.tar.gz) and store the passphrase elsewhere. For daily protection, rely on the encrypted backup sets.
Move a manual installation to Docker Compose
The Compose stack uses PostgreSQL. A manual installation on PostgreSQL moves with a normal restore and keeps its installation identity, keys, accounts and data. Installations on SQLite, MySQL or MariaDB stay on the manual setup, because a backup can only be restored into the same database engine.
- Update the manual installation to the release you will run with Docker, run
php artisan vaultigen:backup, then stop its web server, queue worker and scheduler and keep it offline. - On the Docker server, follow steps 1 to 3 of the installation with the same version, but do not run setup. Add the manual installation’s
APP_KEY=…line to the new.env, plus any settings you still need such as mail or license values. Keep the generatedDB_PASSWORD. - Copy
installation.json,kek.jsonandbackup.jsonfrom the manual installation’sstorage/app/private/into~/vaultigen/data/keys/, and the backup folder into~/vaultigen/backups/. - Start the database and run the restore command from Restore onto a new server.
Keep the APP_KEY line in .env from then on and include .env in your key copy.
Restores and key rotation
- After rotating the installation KEK, old backup sets still need the old KEK keys. Keep them in the keyring for as long as you retain those sets.
- After rotating a vault key, take a new backup; keep the pre-rotation backup until you are sure you no longer need it.
- After rotating the backup key with
--retire-old-keys, off-host copies that were not re-encrypted can no longer be read.
See Key management.
Found a mistake? Tell us at [email protected].