Backup & Restore
This page covers backup strategies and restore procedures for self-hosted Infracast deployments. SaaS customers have automated daily backups managed by Infracast — no action required.
If you're using app.infracast.io, your data is backed up automatically with 35-day retention. Contact support to request a data export.
What to Back Up
| Data | Location | Criticality |
|---|---|---|
| Asset graph, findings, users, tenants | PostgreSQL | 🔴 Critical |
| Evidence artifacts | S3 or local filesystem | 🟡 High |
| Configuration (env vars, secrets) | Your secrets manager / .env | 🔴 Critical |
| Plugin binaries | Container image / filesystem | 🟢 Low (re-downloadable) |
PostgreSQL Backups
Option A: pg_dump (Docker / Self-Hosted)
Run a manual dump:
# Docker Compose
docker compose exec postgres pg_dump \
-U vulcan \
-d vulcan \
-F c \
-f /tmp/vulcan-$(date +%Y%m%d-%H%M%S).dump
# Copy dump from container to host
docker compose cp postgres:/tmp/vulcan-*.dump ./backups/
Automate with cron:
# Daily backup at 02:00 UTC, retain 30 days
0 2 * * * root /usr/local/bin/vulcan-backup.sh
#!/bin/bash
set -e
BACKUP_DIR="/opt/vulcan/backups"
DATE=$(date +%Y%m%d-%H%M%S)
DUMP_FILE="$BACKUP_DIR/vulcan-$DATE.dump"
mkdir -p "$BACKUP_DIR"
docker compose -f /opt/vulcan/docker-compose.yml exec -T postgres \
pg_dump -U vulcan -d vulcan -F c > "$DUMP_FILE"
# Compress
gzip "$DUMP_FILE"
# Remove backups older than 30 days
find "$BACKUP_DIR" -name "*.dump.gz" -mtime +30 -delete
echo "Backup complete: ${DUMP_FILE}.gz"
Upload to S3 for off-site storage:
aws s3 cp "$DUMP_FILE.gz" "s3://your-backup-bucket/vulcan/$(basename $DUMP_FILE.gz)"
Option B: RDS Automated Backups (Terraform / AWS)
If using the Terraform module with RDS, automated backups are enabled by default:
# In terraform.tfvars
db_backup_retention_days = 35 # 35 days for prod, 7 for dev
db_backup_window = "02:00-03:00" # UTC
db_maintenance_window = "sun:03:00-sun:04:00"
RDS automated backups:
- Daily snapshot taken during backup window
- Transaction logs backed up continuously (point-in-time recovery to any second)
- Stored in S3 in the same region
Option C: RDS Manual Snapshots
Before upgrades or major changes:
# Create a manual snapshot
aws rds create-db-snapshot \
--db-instance-identifier vulcan-prod \
--db-snapshot-identifier vulcan-pre-upgrade-$(date +%Y%m%d)
# List snapshots
aws rds describe-db-snapshots \
--db-instance-identifier vulcan-prod \
--query "DBSnapshots[*].{ID:DBSnapshotIdentifier,Time:SnapshotCreateTime,Status:Status}" \
--output table
Create a manual RDS snapshot before every upgrade. Unlike automated backups, manual snapshots are retained indefinitely until you delete them.
Evidence Artifact Backups
Evidence artifacts are stored in S3 (Terraform/SaaS) or locally (Docker).
S3 (Terraform/SaaS)
Enable S3 versioning and cross-region replication on the evidence bucket:
resource "aws_s3_bucket_versioning" "evidence" {
bucket = aws_s3_bucket.evidence.id
versioning_configuration {
status = "Enabled"
}
}
Local (Docker)
# Backup local evidence directory
tar -czf vulcan-evidence-$(date +%Y%m%d).tar.gz /opt/vulcan/data/evidence/
# Upload to S3
aws s3 cp vulcan-evidence-*.tar.gz s3://your-backup-bucket/evidence/
Configuration Backup
Back up environment variables and secrets regularly:
# Export current running env (Docker)
docker compose exec vulcan-api env | grep -v PASSWORD | sort > backup-env-$(date +%Y%m%d).txt
# Store secrets separately in your vault/secrets manager
# Never commit secrets to git
Terraform state should be stored in a remote backend:
terraform {
backend "s3" {
bucket = "your-terraform-state-bucket"
key = "vulcan/prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Restore Procedures
Restore PostgreSQL from pg_dump
# Stop the API to prevent writes during restore
docker compose stop vulcan-api
# Drop and recreate the database
docker compose exec postgres psql -U vulcan -c "DROP DATABASE IF EXISTS vulcan;"
docker compose exec postgres psql -U vulcan -c "CREATE DATABASE vulcan;"
# Restore from dump file
docker compose exec -T postgres pg_restore \
-U vulcan \
-d vulcan \
--no-owner \
--role=vulcan \
< ./backups/vulcan-20260101-020000.dump.gz
# Restart the API (runs migrations automatically)
docker compose start vulcan-api
# Verify
curl http://localhost:8080/healthz
Restore RDS from Automated Backup (Point-in-Time)
# Restore to a specific time
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier vulcan-prod \
--target-db-instance-identifier vulcan-prod-restored \
--restore-time "2026-04-01T02:00:00Z"
# Once restored, update DATABASE_URL to point to new instance
# Then run migrations
Restore RDS from Manual Snapshot
# Restore from snapshot
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier vulcan-prod-restored \
--db-snapshot-identifier vulcan-pre-upgrade-20260401 \
--db-instance-class db.r5.large \
--multi-az
# Update ECS task definition / DATABASE_URL to new endpoint
Data Retention Policies
| Data Type | SaaS Retention | Self-Hosted Default | Notes |
|---|---|---|---|
| Asset graph | Indefinite | Indefinite | Nodes updated in place |
| Finding history | Indefinite | Indefinite | Append-only status changes |
| Audit logs | 2 years | Indefinite | Compliance requirement |
| Evidence artifacts | 3 years | Configurable | FedRAMP recommends 3 years |
| Discovery job logs | 90 days | 90 days | Pruned automatically |
| RDS automated backups | 35 days | Per config | Set in Terraform |
Automatic Data Pruning
Infracast automatically prunes old job logs after 90 days. This runs as a background job nightly at 03:00 UTC (server time). No configuration is required.
For compliance requirements beyond 90 days for job logs, export logs to S3 before pruning:
# Export old job logs before they're pruned (optional)
curl -H "Authorization: Bearer $TOKEN" \
"https://api.infracast.io/api/v1/tenants/$TENANT/jobs?before=2025-01-01&format=jsonl" \
-o job-logs-archive.jsonl
Backup Verification
Test your backups regularly — untested backups are not backups.
#!/bin/bash
# Weekly backup verification test
# Run on a separate test instance
TEST_DB="vulcan_restore_test"
LATEST_BACKUP=$(ls -t /opt/vulcan/backups/*.dump.gz | head -1)
echo "Testing restore of $LATEST_BACKUP..."
# Create test database
psql -U postgres -c "CREATE DATABASE $TEST_DB;"
# Restore
pg_restore -U postgres -d "$TEST_DB" < "$LATEST_BACKUP"
# Verify row counts
TENANT_COUNT=$(psql -U postgres -d "$TEST_DB" -tAc "SELECT COUNT(*) FROM tenants;")
NODE_COUNT=$(psql -U postgres -d "$TEST_DB" -tAc "SELECT COUNT(*) FROM nodes;")
echo "Tenants: $TENANT_COUNT"
echo "Nodes: $NODE_COUNT"
# Cleanup
psql -U postgres -c "DROP DATABASE $TEST_DB;"
echo "Backup verification complete."