Immich Database Backup System: Implementation and Recovery Options
The Immich database backup system automates PostgreSQL dumps via database-backup.service.ts, storing compressed backups with versioned filenames while providing a restore workflow that includes automatic rollback to restore points if migrations fail.
The immich-app/immich repository includes a robust database backup subsystem located in server/src/services/database-backup.service.ts. This service orchestrates scheduled PostgreSQL dumps, manages retention policies, and implements a comprehensive recovery pipeline with rollback capabilities. This article examines the implementation details, from cron scheduling to the restore point mechanism.
Configuration and Scheduling
Service Initialization
The backup service initializes during the NestJS lifecycle via onConfigInit (lines 55-74). It reads the backup.database configuration section to determine if automated backups are enabled and acquires a DatabaseLock.BackupDatabase lock to prevent concurrent backup operations across service instances.
Cron Job Registration
When enabled, the service registers a cron job using the expression from database.cronExpression. The cron handler queues a JobName.DatabaseBackup job via the job repository:
// From onConfigInit - lines 55-74
const config = await this.configRepository.getConfig();
if (config.backup.database.enabled) {
const lock = await this.databaseRepository.acquireDatabaseLock(DatabaseLock.BackupDatabase);
if (lock) {
await this.cronRepository.create({
name: 'backupDatabase',
expression: config.backup.database.cronExpression,
onTick: () => this.jobRepository.queue({ name: JobName.DatabaseBackup }),
});
}
}
Backup Generation Process
Creating the Database Dump
The createDatabaseBackup method (lines 18-53) handles the actual dump generation. It constructs PostgreSQL CLI arguments via buildPostgresLaunchArguments, which detects the connection method (URL-based or split config) and validates the PostgreSQL version (>=14.0.0 <19.0.0).
Compression and Storage
The service spawns pg_dump (or pg_dumpall for cluster-wide dumps) as a duplex stream, pipes the output through gzip, and writes to a temporary file before atomic renaming. Backups are stored in StorageCore.getBaseFolder(StorageFolder.Backups) with filenames following the pattern:
immich-db-backup-20231103T123456-v1.107.0-pg14.7.sql.gz
The timestamp uses DateTime.now().toFormat("yyyyLLdd'T'HHmmss") and embeds both the Immich version and PostgreSQL version for compatibility tracking.
Retention and Cleanup
The cleanupDatabaseBackups method (lines 12-42) enforces retention policies after each successful dump. It retrieves the keepLastAmount setting from configuration, identifies routine backups using isValidDatabaseRoutineBackupName, and deletes the oldest files exceeding the retention count. It also removes any files matching isFailedDatabaseBackupName to clean up partial or corrupted dumps.
// Cleanup logic enforces keepLastAmount
const backups = await this.listBackups();
const routineBackups = backups.filter(b => isValidDatabaseRoutineBackupName(b.filename));
const toDelete = routineBackups.slice(0, -config.backup.database.keepLastAmount);
Restore and Recovery Options
The Restore Workflow
The restoreDatabaseBackup method (lines 45-147) implements a robust recovery pipeline with multiple safety mechanisms:
- Validation: Verifies the backup filename matches the expected pattern using
isValidDatabaseBackupName - Restore Point Creation: Generates a fresh dump prefixed
restore-point-to capture the current state before modification - Stream Processing: Decompresses the backup if needed (
.gzextension) and pipes it through a custom SQL pre-processor - SQL Pre-processing: Injects "drop connections" and "reset schema" statements before the actual dump stream via the
sqlgenerator function - Execution: Streams the processed SQL into
psqlusingbuildPostgresLaunchArguments - Migration: After successful restore, calls
databaseRepository.runMigrations()to bring the schema to current version
Automatic Rollback on Migration Failure
If databaseRepository.runMigrations() fails after restoring a backup, Immich automatically initiates a rollback sequence. The service replays the restore-point dump that was created before the restore began, using the sqlRollback generator to reset the schema and return the database to its pre-restore state. This prevents leaving the database in a partially-migrated, inconsistent state.
Progress Monitoring
The createSqlProgressStreams utility (lines 93-161) provides real-time progress callbacks during backup, restore, migration, and rollback operations. It creates PassThrough streams that count processed lines and invoke a debounced callback with percentage completion, allowing administrators to track operation status through the API or logs.
// Progress callback signature
await databaseBackupService.restoreDatabaseBackup(
'immich-db-backup-20231103T123456-v1.107.0-pg14.7.sql.gz',
(stage, percent) => {
console.log(`[${stage}] ${Math.round(percent * 100)}%`);
},
);
API Operations
The service exposes several methods used by the backup controller for HTTP endpoints:
uploadBackup: Validates and stores user-provided dump files (lines 55-64)downloadBackup: Streams backup files for download (lines 66-78)listBackups: Returns metadata (filename, size) of available backupsdeleteBackup: Removes specific backup files after validation
All operations validate backup names using isValidDatabaseBackupName from server/src/utils/database-backups.ts to prevent path traversal and ensure file integrity.
Key Files and Architecture
| File | Purpose |
|---|---|
server/src/services/database-backup.service.ts |
Core backup/restore implementation, scheduling, and cleanup |
server/src/utils/database-backups.ts |
Validation utilities (isValidDatabaseBackupName, isValidDatabaseRoutineBackupName, isFailedDatabaseBackupName) and version extraction |
server/src/cores/storage.core.ts |
Storage abstraction for backup file I/O |
server/src/repositories/config.repository.ts |
Configuration source for backup settings |
server/src/repositories/database.repository.ts |
PostgreSQL version detection and migration execution |
server/src/repositories/cron.repository.ts |
Cron job registration and management |
server/src/enum.ts |
Job names, queue names, and database lock enums |
Summary
- Immich’s database backup system is implemented in
server/src/services/database-backup.service.tsas a NestJS service with cron scheduling capabilities - Automated scheduling uses configurable cron expressions to queue
DatabaseBackupjobs when thebackup.databasesetting is enabled - Backup generation spawns
pg_dumpprocesses, compresses output withgzip, and stores versioned filenames in the Backups storage folder - Retention policies automatically enforce
keepLastAmountlimits and clean up failed backup attempts - Recovery options include full database restoration with automatic restore-point creation and rollback capabilities if migrations fail
- Progress monitoring provides real-time feedback during backup, restore, and rollback operations via stream-based line counting
Frequently Asked Questions
How does Immich schedule automatic database backups?
Immich registers a cron job during service initialization (onConfigInit) that reads the database.cronExpression from the backup configuration. When triggered, it queues a JobName.DatabaseBackup job through the job repository, which eventually executes handleBackupDatabase to create the dump. The service acquires a DatabaseLock.BackupDatabase lock to prevent concurrent backup operations.
What happens if a database restore fails during the migration step?
If databaseRepository.runMigrations() fails after restoring a backup, Immich automatically initiates a rollback sequence. The service replays the restore-point dump that was created before the restore began, using the sqlRollback generator to reset the schema and return the database to its pre-restore state. This prevents leaving the database in a partially-migrated, inconsistent state.
How does Immich validate backup files before processing?
The service uses validation utilities from server/src/utils/database-backups.ts, specifically isValidDatabaseBackupName, isValidDatabaseRoutineBackupName, and isFailedDatabaseBackupName. These regex-based checks prevent path traversal attacks and ensure only properly formatted backup files are processed for listing, download, or restore operations.
Can administrators monitor the progress of backup and restore operations?
Yes, the createSqlProgressStreams utility provides real-time progress callbacks during backup, restore, migration, and rollback operations. It creates PassThrough streams that count processed lines and invoke a debounced callback with percentage completion, allowing administrators to track operation status through the API or logs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →