Skip to main content

Command Palette

Search for a command to run...

Running Immich with S3 Storage: A Complete Developer Guide

Published
7 min read
Running Immich with S3 Storage: A Complete Developer Guide

I was thinking for many days about hosting my own Google Photos alternative. I found many options, but Immich is very close to what Google Photos offers. I'm using a $5 Hetzner Cloud machine, but there's a problem.

What is Immich?

Immich is an open-source, self-hosted photo and video management solution that you can think of as your own personal Google Photos.

Key features:

  • Upload photos from mobile and web
  • Automatic backup from your phone
  • Face recognition and search
  • Album creation and sharing
  • Timeline view of your memories
  • Machine learning for photo tagging

The best part? You own your data completely. No monthly subscriptions, no privacy concerns, and no storage limits imposed by big companies.

What is S3FS?

S3FS-FUSE is a clever tool that makes cloud storage (like Amazon S3) appear as a regular folder on your server.

How it works:

  • Your server sees a normal folder: /opt/immich/library/upload
  • But this folder is actually connected to your S3 bucket in the cloud
  • When Immich writes a photo to this "local" folder, it actually gets stored in S3
  • When Immich reads a photo, s3fs fetches it from S3 transparently

Think of it as a bridge between your server and cloud storage. Your applications don't know the difference - they just see a regular folder, but everything is actually stored in the cloud.

The Storage Problem

Why do we need S3 storage? First, let me explain the problem.

On a $5 machine or any fixed-price server, you get limited disk space - maybe 20GB, 40GB, or 80GB. For my use case, to attach more space, I have to pay more dollars, which becomes very costly.

So I thought: why not use S3 storage?

There are some disadvantages of using S3 as storage - it makes things a little bit slow. But I mostly use it as backup and for writes rather than very frequent reads, so that was okay for me.

Why Use S3 with Immich?

Benefits of S3 Storage:

  • Unlimited Capacity: No more worrying about disk space
  • Cost Effective: Pay only for what you store
  • Portability: Deploy Immich anywhere while keeping the same storage

Trade-offs to Consider:

  • Latency: Slight delay when accessing photos (typically 100-500ms)

Architecture Overview

Our setup uses s3fs-fuse (you can read more here) to mount an S3 bucket as a local filesystem. This allows Immich to read/write files as if they were stored locally, while actually storing everything in S3.

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Immich    │◄──►│   s3fs      │◄──►│  S3 Bucket  │
│ Application │    │   Mount     │    │   Storage   │
└─────────────┘    └─────────────┘    └─────────────┘

Prerequisites

  • AWS Account with S3 access
  • Docker and Docker Compose installed
  • Basic Linux command line knowledge
  • Server with internet connectivity

Step 1: Create S3 Bucket

First, create an S3 bucket for your photos:

# Configure AWS CLI
aws configure

# Create your bucket (replace with your preferred name and region)
aws s3 mb s3://my-immich-photos --region us-east-1

Optional: Enable Transfer Acceleration for faster uploads

aws s3api put-bucket-accelerate-configuration \
    --bucket my-immich-photos \
    --accelerate-configuration Status=Enabled

Step 2: Install and Configure s3fs

Install s3fs-fuse to mount your S3 bucket as a local filesystem:

# Ubuntu/Debian
sudo apt update
sudo apt install -y s3fs

# CentOS/RHEL
sudo yum install -y s3fs-fuse

Create credentials file for s3fs:

# Create credentials file
echo "$(aws configure get aws_access_key_id):$(aws configure get aws_secret_access_key)" \
  | sudo tee /etc/passwd-s3fs > /dev/null
sudo chmod 600 /etc/passwd-s3fs

Step 3: Set Up Immich Directory Structure

Create the directory structure for Immich:

# Create Immich directory
sudo mkdir -p /opt/immich
cd /opt/immich

# Create mount point for S3 storage
sudo mkdir -p /opt/immich/library/upload
sudo chown -R 1000:1000 /opt/immich/library

Step 4: Mount S3 Bucket

Mount your S3 bucket to the Immich upload directory:

# Mount S3 bucket
sudo s3fs my-immich-photos /opt/immich/library/upload \
  -o allow_other \
  -o nonempty \
  -o use_cache=/tmp \
  -o passwd_file=/etc/passwd-s3fs \
  -o endpoint=us-east-1 \
  -o url=https://s3.us-east-1.amazonaws.com

Verify the mount:

mount | grep s3fs
ls -la /opt/immich/library/upload  # Should show your S3 bucket contents

Step 5: Create Required Subdirectories

Immich requires specific subdirectories with marker files:

BASE="/opt/immich/library/upload"

# Create required subdirectories
for DIR in upload thumbs library encoded-video profile backups; do
  FULL="$BASE/$DIR"
  sudo mkdir -p "$FULL"
  sudo touch "$FULL/.immich"
  sudo chmod 777 "$FULL/.immich"
done

Step 6: Configure Immich with Docker Compose

You can refer to the official docs for more details.

Create the Docker Compose configuration:

docker-compose.yml:

name: immich

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 2283:3001
    depends_on:
      - redis
      - database
    restart: always

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1
      interval: 5m
      start_interval: 30s
      start_period: 5m
    command: ["postgres", "-c", "shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
    restart: always

volumes:
  model-cache:

Create .env file:

cat > .env << 'EOF'
# Point to your S3 mount directory
UPLOAD_LOCATION=/opt/immich/library/upload

# Immich version
IMMICH_VERSION=release

# Database configuration
DB_PASSWORD=your-secure-password
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
DB_DATA_LOCATION=/opt/immich/postgres

# Redis configuration
REDIS_HOSTNAME=immich_redis
EOF

Step 7: Start Immich

Start your Immich deployment:

# Start all services
docker compose up -d

# Check status
docker compose ps
docker compose logs immich_server

Step 8: Configure Auto-Mount on Boot

Create a systemd service to automatically mount S3 on server reboot:

sudo tee /etc/systemd/system/s3fs-immich.service > /dev/null << 'EOF'
[Unit]
Description=S3FS Mount for Immich Photos
After=network.target

[Service]
Type=forking
User=root
ExecStart=/usr/bin/s3fs my-immich-photos /opt/immich/library/upload -o passwd_file=/etc/passwd-s3fs,allow_other,use_cache=/tmp,endpoint=us-east-1,url=https://s3.us-east-1.amazonaws.com
ExecStop=/bin/umount /opt/immich/library/upload
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

# Enable and start the service
sudo systemctl enable s3fs-immich.service
sudo systemctl start s3fs-immich.service

Step 9: Configure S3 Bucket Policy (Optional)

If you want direct access to photos via HTTPS URLs, configure a bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPublicReadPhotos",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-immich-photos/photos/*"
    }
  ]
}

Testing Your Setup

  1. Access Immich: Navigate to http://your-server-ip:2283
  2. Create Account: Set up your admin account
  3. Upload Photos: Try uploading photos via web or mobile app
  4. Verify S3: Check your S3 bucket to confirm photos are being stored

Performance Optimization Tips

s3fs Mount Options:

# For better performance, use these mount options:
sudo s3fs my-immich-photos /opt/immich/library/upload \
  -o allow_other,nonempty \
  -o use_cache=/var/cache/s3fs \
  -o max_stat_cache_size=100000 \
  -o stat_cache_expire=60 \
  -o multireq_max=5 \
  -o parallel_count=30

AWS CLI Configuration:

# Optimize AWS CLI for better S3 performance
aws configure set default.s3.max_concurrent_requests 20
aws configure set default.s3.multipart_threshold 64MB
aws configure set default.s3.multipart_chunksize 16MB

Cost Comparison: Why This Setup Makes Sense

Let me break down the costs to show why this approach is better:

Hetzner Cloud Storage Upgrade Costs:

  • Base $5/month: 20GB storage
  • $10/month: 40GB storage (+$5 for 20GB = $0.25/GB/month)
  • $20/month: 80GB storage (+$15 for 60GB = $0.25/GB/month)

AWS EC2 + EBS Storage:

  • t3.micro: $8.5/month + $10/month for 100GB EBS = $18.5/month
  • t3.small: $17/month + $10/month for 100GB EBS = $27/month

AWS Lightsail:

  • $5/month: 20GB storage
  • $10/month: 40GB storage
  • $20/month: 80GB storage

S3 Storage (eu-north-1):

  • S3 Standard: $0.023/GB/month
  • 100GB: ~$2.30/month
  • 500GB: ~$11.50/month
  • 1TB: ~$23/month

The Winner: Hetzner + S3

Recommended Setup:

  • Hetzner Cloud $5/month: Cheapest compute option
  • S3 Storage: Pay only for what you use
  • Total for 100GB photos: $5 + $2.30 = $7.30/month

Conclusion

So yeah, I think by this process, I have achieved what I wanted. I'm currently using Immich app with S3.

The other advantage we have here is: if we want to change or migrate the machine, S3 will be a single source of truth for us. We can easily migrate, which will help in my case when we want to change the machine.

And obviously, we will pay less for this setup.

So yeah, that's it. Thank you.

Key Takeaways

  • Problem Solved: Unlimited photo storage without expensive server upgrades
  • Cost Effective: Hetzner ($5) + S3 storage cheaper than alternatives
  • Portable: S3 as single source of truth makes migration easy
  • Reliable: Enterprise-grade storage with automatic backups
  • Scalable: Pay only for what you use, grow as needed

Your photos are now safely stored in the cloud while maintaining the familiar Immich experience, all on a budget!