# Immich Database Backup System: Implementation and Recovery Options

> Learn how Immich implements its database backup system in database-backup.service.ts, explore recovery options, and ensure safe restores with automatic rollback.

- Repository: [Immich/immich](https://github.com/immich-app/immich)
- Tags: internals
- Published: 2026-02-27

---

**The Immich database backup system automates PostgreSQL dumps via [`database-backup.service.ts`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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:

```typescript
// 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.

```typescript
// 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.

```typescript
// 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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/server/src/services/database-backup.service.ts) | Core backup/restore implementation, scheduling, and cleanup |
| [`server/src/utils/database-backups.ts`](https://github.com/immich-app/immich/blob/main/server/src/utils/database-backups.ts) | Validation utilities (`isValidDatabaseBackupName`, `isValidDatabaseRoutineBackupName`, `isFailedDatabaseBackupName`) and version extraction |
| [`server/src/cores/storage.core.ts`](https://github.com/immich-app/immich/blob/main/server/src/cores/storage.core.ts) | Storage abstraction for backup file I/O |
| [`server/src/repositories/config.repository.ts`](https://github.com/immich-app/immich/blob/main/server/src/repositories/config.repository.ts) | Configuration source for backup settings |
| [`server/src/repositories/database.repository.ts`](https://github.com/immich-app/immich/blob/main/server/src/repositories/database.repository.ts) | PostgreSQL version detection and migration execution |
| [`server/src/repositories/cron.repository.ts`](https://github.com/immich-app/immich/blob/main/server/src/repositories/cron.repository.ts) | Cron job registration and management |
| [`server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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.