Immich Multiple Storage Backends and Storage Service Configuration

Immich handles multiple storage backends by abstracting them behind a single filesystem mount configured via IMMICH_MEDIA_LOCATION, while src/server/src/services/storage.service.ts manages bootstrap validation, mount checks, and folder initialization through methods like detectMediaLocation() and onBootstrap().

The immich-app/immich repository implements a backend-agnostic storage architecture that allows you to use any filesystem-compatible storage—from local disks to cloud object stores mounted via FUSE. At the heart of this system is storage.service.ts, which orchestrates the detection and validation of storage paths during application startup. Understanding how Immich handles multiple storage backends and the configuration for storage.service.ts enables you to deploy scalable, resilient media backups using your preferred storage technology.

Storage Backend Architecture Overview

Immich stores all uploaded media—photos, videos, thumbnails, and encoded files—on a single physical storage backend represented by a local filesystem path. While the architecture supports any storage technology, Immich does not ship with native S3, Azure, or Google Cloud adapters. Instead, you must mount your preferred storage as a regular filesystem using tools like s3fs, rclone, or Docker volumes, then point Immich to that mount via environment variables.

The abstraction layer ensures that all internal services interact with path helpers (StorageCore, StorageFolder, PathType) rather than concrete storage implementations. This design means the rest of the codebase remains unaware whether the underlying storage is a local SSD, NFS share, or S3-backed FUSE mount.

Storage Backend Selection and Configuration

Immich determines the active storage backend through environment variables evaluated at startup. The StorageService class in src/server/src/services/storage.service.ts encapsulates this logic, providing automatic fallbacks and validation.

Environment Variable Configuration

Two primary environment variables control storage backend behavior:

  • IMMICH_MEDIA_LOCATION: Defines the root path where Immich creates subdirectories (upload, library, thumbs, encoded-video, backups). If unset, Immich falls back to /data or /usr/src/app/upload.
  • IMMICH_IGNORE_MOUNT_CHECK_ERRORS: When set to true, Immich skips strict read/write verification during startup, allowing the server to start even if the storage mount is temporarily unavailable.

Automatic Directory Detection

The detectMediaLocation() method in storage.service.ts implements the fallback logic:

const env = this.configRepository.getEnv();
// Returns env.storage.mediaLocation, or checks /data, /usr/src/app/upload
const mediaRoot = this.detectMediaLocation();

This method queries ConfigRepository for parsed environment data and uses StorageRepository.existsSync to verify directory existence before returning the validated path.

Deep Dive: storage.service.ts Configuration

The StorageService class handles the critical bootstrap sequence that prepares the storage backend for use. It operates with high priority during the AppBootstrap event to ensure storage is ready before other services initialize.

Bootstrap Process with onBootstrap()

Decorated with @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.StorageService }), the onBootstrap() method executes three critical steps:

  1. Sets the global media location via StorageCore.setMediaLocation(this.detectMediaLocation())
  2. Acquires a database lock (DatabaseLock.SystemFileMounts) to prevent concurrent mount operations during verification
  3. Validates each storage folder by creating .immich marker files and testing read/write permissions
@OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.StorageService })
async onBootstrap() {
  // 1. Resolve root folder
  StorageCore.setMediaLocation(this.detectMediaLocation());

  // 2. Acquire lock and verify each StorageFolder
  await this.databaseRepository.withLock(DatabaseLock.SystemFileMounts, async () => {
    for (const folder of Object.values(StorageFolder)) {
      await this.createMountFile(folder);
      await this.verifyReadAccess(folder);
      await this.verifyWriteAccess(folder);
    }
  });
}

Mount Verification and Marker Files

The service creates a .immich marker file in each StorageFolder subdirectory to confirm write access. The createMountFile() method handles the EEXIST case for existing installations, while verifyReadAccess() and verifyWriteAccess() test filesystem operations. If checks fail and IMMICH_IGNORE_MOUNT_CHECK_ERRORS is false, the service throws ImmichStartupError with a documentation link.

File Deletion Operations

The handleDeleteFiles() method, decorated with @OnJob({ name: JobName.FileDelete, queue: QueueName.BackgroundTask }), processes background deletion jobs using StorageRepository.unlink(). Errors are logged without aborting the job queue, ensuring failed deletions don't block other background tasks.

Configuring External Storage Backends

To use cloud object storage or network-attached storage, mount the remote filesystem to your host and expose it to the Immich container.

Docker Compose Configuration Example

Mount an S3 bucket via s3fs or rclone to /mnt/immich-media on the host, then configure the container:

services:
  immich-server:
    volumes:
      - /mnt/immich-media:/media
    environment:
      - IMMICH_MEDIA_LOCATION=/media
      - IMMICH_IGNORE_MOUNT_CHECK_ERRORS=false

On startup, StorageService detects /media as the media location, performs mount checks, and directs all file operations—including uploads in StorageFolder.upload and thumbnails in StorageFolder.thumbs—to the mounted backend.

Key Source Files and Dependencies

File Role Key Components
src/server/src/services/storage.service.ts Bootstrap logic and mount validation detectMediaLocation(), onBootstrap(), verifyReadAccess()
src/server/src/cores/storage.core.ts Static path resolution helpers setMediaLocation(), getBaseFolder(), getNestedPath()
src/server/src/repositories/config.repository.ts Environment variable parsing getEnv(), EnvData.storage interface
src/server/src/enum.ts Storage folder definitions StorageFolder enum (upload, library, thumbs, etc.)
src/server/src/services/storage-template.service.ts Asset organization and moving Uses StorageCore for path calculations

Summary

  • Immich uses a single storage backend abstraction configured via IMMICH_MEDIA_LOCATION, supporting any filesystem-mounted storage including S3, NFS, or local disks.
  • The storage.service.ts file in src/server/src/services/ manages storage initialization through detectMediaLocation() and the onBootstrap() lifecycle hook.
  • Mount verification occurs via .immich marker files and read/write tests, controllable via IMMICH_IGNORE_MOUNT_CHECK_ERRORS.
  • All internal services consume storage paths through StorageCore helpers, ensuring the codebase remains backend-agnostic.

Frequently Asked Questions

Can Immich use multiple storage backends simultaneously?

No, Immich is designed to use a single physical storage backend at a time. While you can change backends by modifying IMMICH_MEDIA_LOCATION and migrating data, the architecture does not support striping assets across multiple distinct backends concurrently. To use different storage technologies, mount them as a unified filesystem (e.g., with mergerfs) and point Immich to the merged mount point.

What is the purpose of the .immich marker file?

The .immich file serves as a write-access verification marker created by createMountFile() during bootstrap in each StorageFolder directory (upload, thumbs, library, etc.). verifyWriteAccess() and verifyReadAccess() use this file to confirm Immich has proper filesystem permissions before accepting uploads. If the marker cannot be written, the service throws ImmichStartupError unless mount check errors are ignored.

How do I migrate to a new storage location?

To migrate, update the IMMICH_MEDIA_LOCATION environment variable to point to your new mount and move the contents of your existing StorageFolder directories (upload, library, thumbs, encoded-video) to the corresponding locations in the new path. The onBootstrap() method in storage.service.ts detects the change and updates the system metadata, though you must manually move existing assets before restarting to prevent "file not found" errors.

Does Immich support native S3 storage without mounting?

No, Immich does not include native S3, Azure Blob, or Google Cloud Storage adapters. To use object storage, you must mount the bucket as a local filesystem using s3fs, rclone mount, or similar FUSE-based tools, then configure IMMICH_MEDIA_LOCATION to point to the mount directory. This approach allows the StorageRepository to use standard filesystem operations (readFile, overwriteFile, unlink) against the remote storage.

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 →