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
- Access Immich: Navigate to
http://your-server-ip:2283 - Create Account: Set up your admin account
- Upload Photos: Try uploading photos via web or mobile app
- 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!


