immich-app/immich · critical · ImmichStartupError

Failed to write

Error message

Failed to write "${externalPath} - ${docsMessage}"

What it means

Immich verifies at startup that each configured storage folder (library, upload, thumbs, profile, encoded-video, backups) is writable by writing a probe file (.immich) to the internal mount path. If the test write fails, it throws ImmichStartupError with the external (user-facing) path so the operator can fix the mount. This is a fail-fast guard against a misconfigured or read-only volume.

Solutions

  1. Mount the folder read/write in the container (remove :ro from the docker-compose volume)
  2. Ensure the directory exists and is owned by the immich user (chown/chmod)
  3. Verify the volume is actually mounted and available (df / ls inside the container)
  4. Check disk space and filesystem errors (dmesg, df -h)

Example fix

// before
docker-compose.yml:
  volumes:
    - /mnt/photos:/usr/src/app/upload:ro
// after
docker-compose.yml:
  volumes:
    - /mnt/photos:/usr/src/app/upload
Defensive patterns

Strategy: try-catch

Validate before calling

const probe = join(mountPath, '.immich');
try { await fs.access(mountPath, fs.constants.W_OK); } catch { throw new Error(`Mount ${mountPath} is not writable`); }

Type guard

null

Try / catch

try {
  await startServer();
} catch (e) {
  if (e instanceof ImmichStartupError && e.message.includes('Failed to write')) {
    // check mount permissions/read-only flag and remount rw
  }
}

Prevention

When it happens

Trigger: Server bootstrap calls onBootstrap -> verifyWriteAccess for each StorageFolder; the storageRepository.overwriteFile probe write to '<folder>/.immich' throws (read-only filesystem, nonexistent mount, wrong permissions, volume not mounted).

Common situations: Docker volume mounted read-only, host path not mounted into the container, ownership/permissions changed after migration, NFS mount offline, disk full.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/a686a4f852cd4413. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/storage.service.ts:186

      this.storageRepository.mkdirSync(folderPath);
      await this.storageRepository.createFile(internalPath, Buffer.from(Date.now().toString()));
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
        this.logger.warn('Found existing mount file, skipping creation');
        return;
      }
      this.logger.error(`Failed to create ${internalPath}: ${error}`);
      throw new ImmichStartupError(`Failed to create "${externalPath} - ${docsMessage}"`);
    }
  }

  private async verifyWriteAccess(folder: StorageFolder) {
    const { internalPath, externalPath } = this.getMountFilePaths(folder);
    try {
      await this.storageRepository.overwriteFile(internalPath, Buffer.from(Date.now().toString()));
    } catch (error) {
      this.logger.error(`Failed to write ${internalPath}: ${error}`);
      throw new ImmichStartupError(`Failed to write "${externalPath} - ${docsMessage}"`);
    }
  }

  private getMountFilePaths(folder: StorageFolder) {
    const folderPath = StorageCore.getBaseFolder(folder);
    const internalPath = join(folderPath, '.immich');
    const externalPath = `<UPLOAD_LOCATION>/${folder}/.immich`;

    return { folderPath, internalPath, externalPath };
  }
}

View on GitHub (pinned to e55ac299a4)