# Immich Multiple Storage Backends and Storage Service Configuration

> Learn how Immich manages multiple storage backends with IMMICH_MEDIA_LOCATION and configures storage.service.ts for mount checks and folder initialization.

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

---

**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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/storage.service.ts) implements the fallback logic:

```typescript
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`](https://github.com/immich-app/immich/blob/main/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

```typescript
@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:

```yaml
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`](https://github.com/immich-app/immich/blob/main/src/server/src/services/storage.service.ts) | Bootstrap logic and mount validation | `detectMediaLocation()`, `onBootstrap()`, `verifyReadAccess()` |
| [`src/server/src/cores/storage.core.ts`](https://github.com/immich-app/immich/blob/main/src/server/src/cores/storage.core.ts) | Static path resolution helpers | `setMediaLocation()`, `getBaseFolder()`, `getNestedPath()` |
| [`src/server/src/repositories/config.repository.ts`](https://github.com/immich-app/immich/blob/main/src/server/src/repositories/config.repository.ts) | Environment variable parsing | `getEnv()`, `EnvData.storage` interface |
| [`src/server/src/enum.ts`](https://github.com/immich-app/immich/blob/main/src/server/src/enum.ts) | Storage folder definitions | `StorageFolder` enum (`upload`, `library`, `thumbs`, etc.) |
| [`src/server/src/services/storage-template.service.ts`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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`](https://github.com/immich-app/immich/blob/main/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.