
How I Replaced Google Photos with Immich on a Steam Deck
How I replaced Google Photos with a self-hosted Immich instance
running on a Steam Deck — and made it accessible from anywhere.
Why a Steam Deck?
I had a Steam Deck collecting dust. Instead of selling it, I decided
to turn it into a home server. It’s small, silent, energy-efficient, and
has a built-in battery (free UPS). Why not?
The goal: self-host Immich as a
Google Photos replacement, accessible from outside my home via
Cloudflare Tunnel.
The Problem with SteamOS
SteamOS has a read-only filesystem. This means:
pacmandoesn’t work properly — PGP keys expire and
can’t be refreshed- You can’t install Docker or any system packages permanently
- Every SteamOS update can wipe your changes
I wasted hours trying to work around this before accepting the truth:
SteamOS is for gaming, not for servers.
Enter Bazzite
Bazzite is a Fedora-based Linux
distro designed specifically for Steam Deck (and other gaming
handhelds). It gives you:
- Full read-write filesystem —
dnf
works, packages persist - Gaming Mode — same Steam + Proton experience as
SteamOS - Desktop Mode — full KDE Plasma desktop
- Designed for the Steam Deck hardware — controllers, display,
sleep/wake all work
Installing Bazzite
- Download Bazzite from bazzite.gg
(pick the Steam Deck image) - Flash to USB with Balena
Etcher ordd - Boot from USB (hold Volume Down + Power)
- Install — I chose no encryption, local account, root enabled
The whole process takes about 15 minutes.
Docker on Bazzite
Here’s where it gets interesting. Bazzite comes with Podman, but I
ran into DNS resolution issues — containers couldn’t find each other by
hostname. Docker handles container networking better, so I switched.
Installing Docker via
Homebrew
Bazzite comes with Homebrew pre-installed, which makes adding
packages easy:
brew install docker docker-compose
The vfs Storage Driver
Fix
After installing Docker, every container failed with a cryptic
error:
runc create failed: invalid rootfs: not an absolute path, or a symlink
Even docker run hello-world crashed. The fix: change the
storage driver to vfs:
mkdir -p ~/.config/docker
echo '{"storage-driver": "vfs"}' > ~/.config/docker/daemon.json
systemctl --user restart docker
vfs is slower than overlayfs but it works
reliably on Bazzite’s filesystem. Why? Bazzite uses an ostree/composefs
filesystem that doesn’t support fuse-overlayfs (the default
for rootless Docker) properly. The runc process can’t
resolve the layered rootfs path, so it fails on every container — even
hello-world. vfs bypasses this entirely by
just copying files instead of layering them. Slower for building images,
but no noticeable difference when running containers.
Setting Up Immich
Immich is an open-source,
self-hosted Google Photos alternative. It has face recognition, search,
mobile backup, and a beautiful UI.
Storage Setup
I added a microSD card for photo storage:
# Find the SD card
lsblk
# Format as ext4 (use KDE Partition Manager for GUI)
sudo mkfs.ext4 /dev/mmcblk0p1
# Create mount point and add to fstab
sudo mkdir /mnt/sdcard
echo "UUID=$(blkid -s UUID -o value /dev/mmcblk0p1) /mnt/sdcard ext4 defaults 0 2" | sudo tee -a /etc/fstab
sudo mount -a
Docker Compose
mkdir ~/immich && cd ~/immich
# Download official compose file
curl -o docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
curl -o .env https://github.com/immich-app/immich/releases/latest/download/example.env
Edit .env:
UPLOAD_LOCATION=/mnt/sdcard/immich
DB_DATA_LOCATION=/home/eugene/immich/postgres # use absolute path!
TZ=Europe/Warsaw
DB_PASSWORD=your_secure_password
Important: Use an absolute path for
DB_DATA_LOCATION, not a relative one like
./postgres. Relative paths caused issues with Docker on
Bazzite.
docker compose up -d
Immich is now running at http://localhost:2283.
Importing Photos from Google Photos
Google Takeout gives you a zip file with all your photos, but the
metadata (dates, locations) is stored in separate JSON sidecar files.
Immich can’t import these directly.
immich-go solves
this — it reads Google Takeout exports and uploads photos to Immich with
correct metadata:
# Download immich-go
curl -LO https://github.com/simulot/immich-go/releases/latest/download/immich-go_Linux_x86_64.tar.gz
tar xzf immich-go_Linux_x86_64.tar.gz
# Import (point to your extracted Takeout folder)
./immich-go -server http://localhost:2283 -key YOUR_API_KEY upload google-photos /path/to/takeout/
This took several hours for 29,000+ photos, but every photo kept its
original date, GPS data, and album structure.
Cloudflare Tunnel —
Access From Anywhere
The Steam Deck is behind a home router with no static IP (and
possibly double NAT). Cloudflare
Tunnel solves this — it creates an outbound connection from your
server to Cloudflare, no port forwarding needed.
Setup
brew install cloudflared
# Authenticate
cloudflared tunnel login
# Create tunnel
cloudflared tunnel create immich
# Configure
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: YOUR_TUNNEL_ID
credentials-file: /home/eugene/.cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: photos.yourdomain.com
service: http://localhost:2283
- service: http_status:404
EOF
# Add DNS record
cloudflared tunnel route dns immich photos.yourdomain.com
Autostart with systemd
cat > ~/.config/systemd/user/cloudflared.service << 'EOF'
[Unit]
Description=Cloudflare Tunnel
After=network.target
[Service]
Type=simple
ExecStart=/home/linuxbrew/.linuxbrew/opt/cloudflared/bin/cloudflared tunnel run immich
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
EOF
systemctl --user enable cloudflared
systemctl --user start cloudflared
Now photos.yourdomain.com serves your Immich instance
from the Steam Deck.
Preventing Sleep
A server shouldn’t sleep. Disable it:
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
Tips and Gotchas
- Use Bazzite, not SteamOS — save yourself hours of
fighting read-only filesystems - Docker via Homebrew, not rpm-ostree — cleaner
install, easier updates vfsstorage driver — ugly but works on
Bazzite- Absolute paths in
.env— relative
paths break with Docker rootless - Cloudflare Tunnel > port forwarding — works with
any ISP, any NAT setup - immich-go for Google Takeout — the only tool that
preserves all metadata correctly - Disable sleep — both systemd targets and GNOME
settings
Have questions? Reach me at me@eugenelab.org or on LinkedIn.