Restore from a backup
Restore an Infrahub instance from a backup file on Docker Compose. Follow these steps to recover from data loss, roll back a change, or move to new infrastructure.
Community edition compatibility​
Restores are supported for Infrahub Community Edition when the backup was created from the same edition. Enterprise backups include components that are not available in Community Edition and cannot be restored.
Restoring an Enterprise backup to a Community Edition deployment is not supported. Always use backups that were captured from the same edition you plan to restore.
Prerequisites​
Before restoring from backup:
- Have a valid backup file created by infrahub-backup
- Ensure sufficient disk space for extraction (3x backup size)
- Stop all write operations to the current instance
- Verify you have necessary permissions
- Create a current backup before overwriting existing data
Restoration replaces all existing data. Always verify you have a current backup before proceeding with a restore operation.
Docker high availability deployments​
If your Docker Compose deployment runs multiple task manager replicas (HA setup), you must stop the task-manager and task-manager-background-svc containers before running a restore. This prevents those services from accessing the database while data is being replaced.
# Stop task manager services before restore
docker compose stop task-manager task-manager-background-svc
After the restore completes, restart the stopped services:
# Restart task manager services after restore
docker compose start task-manager task-manager-background-svc
Step 1: Prepare for restoration​
Create safety backup​
Before overwriting current data, create a safety backup:
# Backup current state
infrahub-backup create --backup-dir=/tmp/safety-backup
# Note the filename for potential recovery
SAFETY_BACKUP=$(ls -t /tmp/safety-backup/*.tar.gz | head -1)
echo "Safety backup: $SAFETY_BACKUP"
Step 2: Execute the restore​
Basic restore​
Restore from a backup file:
infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz
The restore process will:
- Validate backup compatibility
- Stop all Infrahub containers
- Clear existing data
- Restore Neo4j database
- Restore PostgreSQL task manager
- Restart all services
Restore from S3​
Restore directly from S3 or S3-compatible storage using an s3:// URI:
- AWS S3
- MinIO
- Environment Variables
# Restore from S3
infrahub-backup restore s3://my-backups/infrahub/prod/infrahub_backup_20250929_143022.tar.gz
# Restore from MinIO or S3-compatible storage
infrahub-backup restore \
--s3-endpoint http://minio.local:9000 \
s3://my-backups/infrahub_backup_20250929_143022.tar.gz
# Configure S3 via environment variables
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
infrahub-backup restore s3://my-backups/infrahub/prod/infrahub_backup_20250929_143022.tar.gz
The object is downloaded to a temporary file inside the local backup directory before restoration, and removed again afterwards. Because the download never uses the archive's own name, a local archive of the same name — what create --s3-upload --s3-keep-local leaves behind — is never overwritten and never deleted.
Restore the most recent backup​
If you do not want to name an archive — for example from a scheduled job, which cannot know a filename in advance — pass --latest instead of a file:
infrahub-backup restore --latest
--latest lists the configured backup directory, ranks the archives it finds, and restores the newest one. To rank archives in the configured S3 bucket and prefix instead, add --s3:
infrahub-backup restore --latest --s3 --s3-bucket my-backups --s3-prefix infrahub/prod
Exactly one location is consulted per run. With --s3 the local backup directory is never read; without it the bucket is never read. The two are never merged, and there is no fallback from one to the other.
Everything else about the restore is unchanged: --latest only decides which archive is used. Metadata validation, checksum verification, and the container stop and start sequence all behave exactly as they do when you name an archive, and every other restore flag composes with --latest.
How the newest archive is chosen​
--latest ranks archives exactly as the retention policy ranks them:
- Only names matching
infrahub_backup_<YYYYMMDD_HHMMSS>.tar.gz, optionally with an.encsuffix, take part. Anything else in the directory or under the prefix — unrelated files, partial uploads, foreign names — is ignored. - Archives are ordered by the timestamp embedded in the name, newest first, with ties broken by name in descending order.
Because the ordering is shared with retention, the archive --latest restores is always one that retention's "the most recent backup at each location always survives" rule protects. See Manage backup retention.
Before the restore begins, the run reports what it selected and where it came from:
INFO[0000] Restoring latest backup infrahub_backup_20250929_143022.tar.gz from local:/opt/infrahub_backups
The location reads local:<directory> for the backup directory and s3://<bucket>/<prefix> for a bucket. That single line is what makes an unattended restore auditable from captured output alone, without access to the machine that ran it.
When --latest refuses to run​
--latest fails rather than guessing, and it never falls back to an older archive:
| Situation | Result |
|---|---|
| No matching archives in the selected location | Non-zero exit naming the location that was listed. This is the expected state before the first backup runs. |
The newest archive is encrypted and no --decrypt-key was passed | Non-zero exit naming the archive. The check runs before the --sleep wait, before any download, and before any container is stopped. |
Both --latest and an archive name are passed | Non-zero exit. The two are mutually exclusive. |
--s3 is passed without --latest | Non-zero exit. To restore one exact remote archive, pass its s3:// URI as the argument instead. |
Restoring whichever archive happens to be readable would leave a deployment holding stale data while reporting success. On a schedule nobody watches, that is worse than a job that visibly fails.
Because the selection happens before the --sleep wait, an archive transferred into place during the sleep is not considered by --latest. The wait exists so that a named file can be transferred in; with --latest the choice has already been made.
With --latest --s3 the selected object is downloaded to a temporary file inside the backup directory and removed afterwards, exactly as an s3:// URI restore downloads. A local archive sharing the selected object's name — what create --s3-upload --s3-keep-local leaves behind — is never overwritten and never deleted.
Refresh staging from production on a schedule​
To keep a staging deployment on the newest production backup, run restore --latest --s3 against the bucket production uploads to. Because no filename is involved, the job is stable across every backup production takes:
# /etc/cron.d/infrahub-staging-sync
# Refresh staging from the newest production backup, nightly at 03:00.
INFRAHUB_S3_BUCKET=my-backups
INFRAHUB_S3_PREFIX=infrahub/prod
0 3 * * * root /usr/local/bin/infrahub-backup restore --latest --s3 --project infrahub-staging --reset-deployment-id >> /var/log/infrahub-staging-sync.log 2>&1
Add --reset-deployment-id as shown, so the refreshed staging instance does not report the production deployment ID. Capture the job's output: the selection line above is the record of which production backup staging is currently running.
A scheduled restore replaces all data in the deployment it targets. Confirm that --project — or the environment the job runs in — points at the deployment you intend to overwrite before enabling the schedule, and keep the production deployment out of reach of the job entirely.
On Kubernetes, the infrahub-backup Helm chart renders the same refresh as a CronJob. See Refresh an environment on a schedule.
Restore to specific project​
If you have multiple Infrahub projects:
# Restore to a specific Docker Compose project
infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz --project=infrahub-staging
Reset the deployment ID​
Every Infrahub instance carries a unique deployment ID stored on the Root node in the Neo4j database. When you restore a production backup into a non-production environment — for example, cloning prod into staging or spinning up a disaster-recovery replica — the restored instance inherits the source deployment ID, and both environments report the same identity.
If you need the restored instance to appear as a distinct deployment, add the --reset-deployment-id flag:
infrahub-backup restore infrahub_backups/infrahub_backup_20250929_143022.tar.gz --reset-deployment-id
The flag generates a new UUID and writes it to the Root node after the database is restored and before the Infrahub application containers start, so the application never observes the source deployment ID.
Omit this flag when you are restoring into the same environment the backup came from (for example, recovering from data loss on the original instance). Resetting the deployment ID is only required when the restored instance is logically separate from the source.
Step 3: Monitor restoration progress​
Watch the detailed restoration output:
INFO[0000] Starting restore process...
INFO[0000] Reading backup metadata...
INFO[0001] Backup created: 2025-09-29T14:30:22Z
INFO[0001] Backup version: 1.0.0
INFO[0001] Validating backup compatibility...
INFO[0002] Validation successful
INFO[0002] Stopping all Infrahub containers...
INFO[0008] All containers stopped
INFO[0008] Extracting backup archive...
INFO[0012] Archive extracted successfully
INFO[0012] Restoring Neo4j database...
INFO[0025] Neo4j database restored
INFO[0025] Restoring PostgreSQL database...
INFO[0030] PostgreSQL database restored
INFO[0032] Starting Infrahub containers...
INFO[0040] All containers started
INFO[0040] Restore completed successfully
Step 4: verify restoration​
Check service health​
Verify all services are running:
# Check container status
docker compose ps
# All containers should show "Up" status
# Example output:
# NAME IMAGE STATUS
# infrahub-server opsmill/infrahub:stable Up 2 minutes
# database neo4j:5.13 Up 2 minutes
# task-manager-db postgres:15 Up 2 minutes
Test application access​
Confirm Infrahub is accessible:
# Test HTTP endpoint
curl -I http://localhost:8000
# Check GraphQL endpoint
curl -X POST http://localhost:8000/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ InfrahubInfo { version } }"}'
Validate data integrity​
Verify your data was restored correctly:
- Log into the Infrahub web interface
- Check that your schemas are present
- Verify critical data objects exist
- Test a few key queries or operations
- Review recent task history
Step 5: post-restoration tasks​
Verify integrations​
Check external integrations are working:
- Git repositories sync correctly
- External authentication (LDAP/SSO) functions
- Webhook endpoints are accessible
- API integrations reconnect properly
Validation​
Confirm restoration was successful:
- ✓ All services are running
- ✓ Web interface is accessible
- ✓ Data appears complete and correct
- ✓ Authentication works
- ✓ Background tasks are processing
- ✓ Integrations are functional