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/dataor/usr/src/app/upload.IMMICH_IGNORE_MOUNT_CHECK_ERRORS: When set totrue, 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:
- Sets the global media location via
StorageCore.setMediaLocation(this.detectMediaLocation()) - Acquires a database lock (
DatabaseLock.SystemFileMounts) to prevent concurrent mount operations during verification - Validates each storage folder by creating
.immichmarker 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.tsfile insrc/server/src/services/manages storage initialization throughdetectMediaLocation()and theonBootstrap()lifecycle hook. - Mount verification occurs via
.immichmarker files and read/write tests, controllable viaIMMICH_IGNORE_MOUNT_CHECK_ERRORS. - All internal services consume storage paths through
StorageCorehelpers, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →