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:

  1. Validation: Verifies the backup filename matches the expected pattern using isValidDatabaseBackupName
  2. Restore Point Creation: Generates a fresh dump prefixed restore-point- to capture the current state before modification
  3. Stream Processing: Decompresses the backup if needed (.gz extension) and pipes it through a custom SQL pre-processor
  4. SQL Pre-processing: Injects "drop connections" and "reset schema" statements before the actual dump stream via the sql generator function
  5. Execution: Streams the processed SQL into psql using buildPostgresLaunchArguments
  6. 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 backups
  • deleteBackup: 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.ts as a NestJS service with cron scheduling capabilities
  • Automated scheduling uses configurable cron expressions to queue DatabaseBackup jobs when the backup.database setting is enabled
  • Backup generation spawns pg_dump processes, compresses output with gzip, and stores versioned filenames in the Backups storage folder
  • Retention policies automatically enforce keepLastAmount limits 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →