
Self-host Immich on Netcup — your own Google Photos on a real VPS
TL;DR: Running Immich on Netcup in 5 minutes
Self-host Immich on a Netcup VPS to keep a decade of family photos out of Google's training set and off Apple's quarterly bill. Immich is a self-hosted Google Photos alternative with face recognition, geocoding, semantic search, a fast mobile auto-backup app, and an active release cadence. It needs real RAM and real disk — this is not a Raspberry Pi project.
- What it is: a Postgres-backed photo and video library with a Svelte web UI, Flutter mobile apps, and a Python ML service.
- Competes with: Google Photos, iCloud Photos, Amazon Photos, Synology Photos, PhotoPrism.
- Hosting profile: 6 GB RAM minimum, 8 GB recommended. PostgreSQL with pgvector, Valkey/Redis, an ML container.
- Storage: grows with your library plus 10–20 percent for thumbnails and transcoded video.
- License: AGPL-3.0. The code can never quietly turn into a SaaS lock-in.
- Latest stable: v2.x line, maintained by FUTO with a release roughly every two weeks.
Install on a fresh Debian 12 box:
mkdir -p /opt/immich && cd /opt/immich
wget https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
docker compose up -d
Primary pick: VPS 2000 G12 — 8 vCore, 16 GB DDR5 ECC, 512 GB NVMe, around €19/month. The 16 GB headroom keeps the ML container honest, and 512 GB NVMe absorbs years of casual shooting before you have to think about external storage.
- First month free:
5800nc17718015230 - Second code:
5800nc17804382710 - Third code:
5800nc17802654090
Introduction
Most people end up paying Google or Apple for photo storage because the alternative — running a server — sounds like a weekend they will not get back. Immich changes the math. The mobile app does background uploads that work as well as Google Photos. The web UI loads a 100,000-asset library without choking. Faces, places, and full-text search are all there. The only remaining cost is a box to run it on.
That is where a Netcup VPS earns its keep. Hardware lives in EU data centres (Nuremberg, Vienna, Karlsruhe), every tier ships with NVMe storage and ECC DDR5, and the prices are set by a company that owns its hardware rather than reselling AWS at a markup. Immich is storage-hungry and memory-hungry — the two things Netcup happens to be cheap on.
Reasonable expectation by the end of this article: a working Immich install behind HTTPS, automatic mobile backup configured, and a clear sense of which Netcup tier matches the size of library you are protecting. No GitHub sleuthing, no docker-compose hand-editing, no copy-pasting the same install command from four different blog posts.
A note on scope. This is the long-term-private path. If your library fits in Google's free 15 GB tier and you are happy with the trade-off, do not bother. If your library is north of 50 GB, growing, and you would prefer the bytes live somewhere a single corporate policy change cannot lock you out of, read on.
What is Immich?
Immich is a self-hosted photo and video management system designed as a drop-in replacement for cloud photo services. Browse it through a web UI, back up automatically from native iOS and Android apps, search by face, by location, or by free-text query ("dog on a beach at sunset" — the ML service vectorises both your photos and the query into the same embedding space).
Architecture
Four containers, four jobs:
immich-server— the Node.js API and Svelte-rendered web UI, listening on port 2283. It also runs background jobs for thumbnails, metadata extraction, and transcoding via ffmpeg.immich-machine-learning— a Python service that loads CLIP and face-recognition models, vectorises images, and answers similarity queries. Optional, but the search and "people" features rely on it.database— PostgreSQL 14 with the pgvector extension (and VectorChord for ANN indexing). All metadata, embeddings, users, albums, and sharing state live here.redis— actually Valkey 9 in the official compose, used by BullMQ for the background-job queue.
The original library (your photos, in their original bytes) lives on disk in UPLOAD_LOCATION. Postgres lives separately in DB_DATA_LOCATION. The ML models cache themselves on first run into a small named volume — count on roughly 2 GB once everything is loaded.
Who maintains it
Immich is led by Alex Tran and is now part of FUTO, a non-profit that funds open-source developer salaries directly. The project has 99k+ GitHub stars, 290+ releases, and ships an actual mobile app on the App Store and Play Store — not a weekend hobby.
How big does the data get
The raw library is whatever your phone produces. A modern 12 MP photo is roughly 3 MB; 4K HEVC video lands around 50 MB per minute. Thumbnails and transcoded preview video add 10–20 percent overhead. The Postgres database itself stays modest — typically 1–3 GB, even for libraries pushing 100k assets — because the heavy bytes never enter the database.
A useful planning rule: estimate raw inflow per active uploader at roughly 5 GB per month for casual phone shooters, 25 GB for families, more for video-heavy workflows. Multiply by 12 to get year-one growth, then by 1.2 for Immich's overhead. That number drives the disk-tier decision in the comparison section.
The license is AGPL-3.0, which matters: a hosted clone of Immich would have to ship its modifications back. That is the legal safety net behind the project's "this stays open" posture.
How to use Immich
Day-to-day, Immich is the photo app you forget you are running. The interesting choices happen the first week.
Core concepts
- Asset: a single photo or video. Has a hash, a place on disk, EXIF metadata, and a row in Postgres.
- Album: a curated collection. Albums are user-owned and shareable with other users on the instance or via public links.
- Library: an external directory Immich reads but does not own — useful when you already have a tree of photos organised on disk and do not want Immich to renumber everything. Distinct from
UPLOAD_LOCATION(which Immich does own). - Job: a background task — thumbnail generation, smart-search embedding, face detection, video transcode. Jobs run in the server container; queueing is BullMQ on Valkey.
- People: the face-recognition cluster output. The ML service produces embeddings, Postgres clusters them, you confirm or merge identities in the UI.
Day-to-day workflow
Install the mobile app, point it at your VPS URL, log in, pick which albums on the phone get auto-uploaded. From then on the phone uploads on Wi-Fi (configurable to allow cellular). The web UI is the editing surface — building shared albums, fixing wrongly-clustered faces, deleting duplicates, exporting to Takeout-compatible bundles.
Search is the killer feature once the ML service has run a full pass. "Birthday cake" finds birthday cakes. "Snowy mountain road" finds the trip from 2018. The semantic-search index has to warm up — on a fresh import of 50k assets, expect the smart-search job to chew CPU for an hour or two on a VPS 2000 G12.
Integrations and extensibility
Immich exposes a documented REST API and an OAuth2 endpoint, which means it slots into existing identity stacks (Authentik, Authelia, Keycloak — bring your own). The API is what immich-go and other CLI tools use for batch ingestion of Google Takeout archives. There is no plugin system, but the AGPL license and clean container split make forks practical.
Mobile backup uses the same API as everything else. If the phone app does not fit your workflow, you can build your own uploader against the API and it will not break on the next release.
Backup and operational reality
Three things matter for backups:
- The upload directory (your original photos and videos). Treat it like any large bag of bytes —
resticorrcloneto off-site cold storage on a schedule. - The Postgres database. The Immich docs are emphatic: use
pg_dumpallor the project'simmich-db-backup, not a filesystem snapshot of the running database directory. - The
.envfile anddocker-compose.yml. Tiny, but losing them means recovering from backup is harder than it has to be.
The thumbnails and ML embeddings are reproducible from the originals, so they do not strictly need backing up — but expect the smart-search job to take a few hours to rebuild after a restore.
Quick Start Guide
Five steps from a fresh Netcup box to a working Immich instance behind HTTPS, with auto-restart and mobile backup ready to configure.
1. Provision the box
Sign up for the VPS 2000 G12 at netcup.com using coupon 5800nc17802654091 for one free month. Pick Debian 12 as the image, paste in your SSH public key, and skip the password option entirely. As soon as the VPS boots, SSH in and harden it:
# /etc/ssh/sshd_config.d/00-hardening.conf
PasswordAuthentication no
PermitRootLogin prohibit-password
KbdInteractiveAuthentication no
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
Reboot, reconnect, confirm sshd came back cleanly before closing the existing session.
2. Install Docker
Immich is shipped exclusively as Docker Compose. There is no native binary path, and there should not be — coordinating four containers by hand is not a winning trade.
# get docker-ce + the compose plugin from the upstream repo
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
docker compose version
docker compose version should print something like Docker Compose version v2.30. Anything in the v2.20+ range works; older versions miss the depends_on.condition: service_healthy syntax Immich relies on.
3. Install Immich
Pull the latest release's compose file and example env, edit the env, start the stack:
mkdir -p /opt/immich && cd /opt/immich
wget https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
Edit .env:
# /opt/immich/.env
UPLOAD_LOCATION=/srv/immich/library
DB_DATA_LOCATION=/srv/immich/postgres
DB_PASSWORD=<generate with: openssl rand -hex 24>
TZ=Europe/Berlin
IMMICH_VERSION=release
Note DB_PASSWORD is alphanumeric only. The Postgres image will refuse special characters and the failure mode is opaque container restart loops — easier to just generate a clean hex string.
mkdir -p /srv/immich/library /srv/immich/postgres
docker compose up -d
docker compose ps
After a minute, all four containers (immich_server, immich_machine_learning, database, redis) should be healthy. The web UI is now answering on port 2283 over plain HTTP — do not expose this directly.
4. Put Caddy in front
Caddy fetches a Let's Encrypt cert the first time it sees an HTTPS request — no certbot, no cron, no expiry surprises.
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -fsSL 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable.gpg
echo "deb [signed-by=/usr/share/keyrings/caddy-stable.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main" > /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy
Replace /etc/caddy/Caddyfile:
photos.example.com {
reverse_proxy localhost:2283
request_body {
max_size 50GB
}
}
The max_size line matters — without it, Caddy's default body limit will reject large video uploads. Reload Caddy: systemctl reload caddy. Within thirty seconds the cert is provisioned and https://photos.example.com answers.
5. First run and mobile setup
Open the URL, register the first user (this becomes the admin), then log in. Go to Administration → Settings → Machine Learning and confirm the smart-search and facial-recognition models are enabled. Trigger Jobs → Smart Search → All if you imported assets via immich-go; the ML container will start chewing through embeddings.
Install the mobile app on iOS or Android, point it at https://photos.example.com, log in, and select the camera roll albums to back up. The first sync will saturate your home upload bandwidth — let it run overnight.
For unattended operation, the compose file already sets restart: always on every service, so a host reboot brings the stack back automatically. Add a daily cron for the database dump:
# /etc/cron.daily/immich-db-backup
#!/bin/sh
set -e
docker exec -t immich_postgres pg_dumpall -U postgres \
| gzip > /srv/immich/backups/db-$(date +\%F).sql.gz
find /srv/immich/backups -name 'db-*.sql.gz' -mtime +14 -delete
chmod +x and you have a 14-day rolling local backup. Add an off-site restic job for the upload directory and you are done.
Choosing the Right Netcup Server for cheap Immich hosting
Immich's resource curve is dominated by two things: how much disk your library will eat over the next few years, and whether you want the ML service running. The decision is roughly: how big is your library now, and how aggressively will it grow?
VPS 1000 G12 — the entry point if you disable ML
Specs: 4 vCore, 8 GB DDR5 ECC, 256 GB NVMe, traffic included. Around €10/month (verify on netcup.com).
8 GB RAM is below Immich's recommended floor but matches the documented "minimum if ML is disabled" path. You lose semantic search and the People view; you keep auto-backup, albums, sharing, and EXIF-driven map and timeline browsing. 256 GB NVMe holds roughly 200 GB of photos after Immich's overhead — a couple of years for a casual user, less for a family.
Good fit if: you mostly use Immich as a backup target, you do not care about face recognition, and your library is under 150 GB today.
Coupons (each pops a different code at build time):
5799nc178000613825799nc177180152615799nc17800061381
VPS 2000 G12 — recommended for most Immich deployments
Specs: 8 vCore, 16 GB DDR5 ECC, 512 GB NVMe, traffic included. Around €19/month (verify on netcup.com).
This is the tier the article was written around. 16 GB RAM gives the ML container the headroom it needs to load CLIP plus a face model without thrashing. 512 GB NVMe holds 400+ GB of library after overhead — typically four to six years of growth for a single household. The 8 vCores chew through the initial smart-search backfill in an evening rather than a weekend.
Coupons:
5800nc177180152325800nc177180152335800nc17718015230
VPS 4000 G12 — when the library is already huge
Specs: 12 vCore, 32 GB DDR5 ECC, 1024 GB NVMe, traffic included. Around €32/month (verify on netcup.com).
A terabyte of NVMe is enough for a multi-decade family library or a videographer's working archive. 32 GB RAM is comfortable for running additional services on the same box — a Vaultwarden instance, a Forgejo, a Mealie — without any of them stepping on Immich. If you are migrating off Google Photos with a 600 GB Takeout, this is the tier that fits the import without a same-day disk-resize.
Coupons:
5801nc177790926005801nc178000613905801nc17797900830
RS 2000 G12 — when ML latency matters
Specs: 8 dedicated AMD EPYC 9645 cores, 16 GB DDR5 ECC, 512 GB NVMe. Around €21/month (verify on netcup.com).
Same RAM and disk as the VPS 2000 G12, but the cores are dedicated rather than oversubscribed. The practical difference: smart-search embedding throughput on a fresh import is roughly 2x faster, and the People view's clustering job finishes in one pass instead of three. For a single-user instance this is overkill; for a family of five all running auto-backup over the same weekend, the dedicated cores stop the queues from backing up.
Coupons (Root Server codes give two free months):
5160nc177180154115998nc177930822605160nc17718015412
Comparison
| Offer | vCPU | RAM | NVMe | Approx. price |
|---|---|---|---|---|
| VPS 1000 G12 | 4 | 8 GB | 256 GB | €10/mo |
| VPS 2000 G12 | 8 | 16 GB | 512 GB | €19/mo |
| VPS 4000 G12 | 12 | 32 GB | 1024 GB | €32/mo |
| RS 2000 G12 | 8 dedicated | 16 GB | 512 GB | €21/mo |
If you are trying Immich for the first time on a small library and willing to skip ML for now, take VPS 1000 G12. If you are putting it in front of a family and want the full Google-Photos-replacement experience, take VPS 2000 G12 — it is the answer for most readers. If you are migrating a 500 GB+ library off iCloud or Google Takeout in one go, jump straight to VPS 4000 G12 and skip the disk-resize dance. If you are running it for a household where everyone hits the import button on the same Saturday, the dedicated cores of RS 2000 G12 keep the queues honest.
Web Hosting is not a fit here — Immich needs persistent daemons, a Postgres process, and a Python ML service. Shared hosting cannot run any of them.
Conclusion
A working Immich install on a Netcup VPS 2000 G12 lands at roughly €19/month after the first free month from 5800nc17804382710, with 512 GB of NVMe headroom and the full ML feature set running. That is half what a 2 TB iCloud+ family plan costs, with the photos sitting on hardware in the EU rather than a US-jurisdiction object store.
If you only need the smaller tier or want a few euros off the first invoice on any product, drop the €5-off-any-order coupon 36nc17718015542 in at checkout — it stacks with the VPS-specific code on a separate order if you bring up a second box.
Things to keep an eye on once it is running: the Postgres dump's daily file size (sudden growth usually means the smart-search index is still rebuilding), free disk on /srv/immich/library (Immich does not auto-prune anything), and the v2.x release notes — Immich ships breaking schema migrations occasionally, and the upgrade path is "stop, pull, up" rather than "in-place hot reload". Read the changelog before each compose pull and the boring stuff stays boring.
The library is yours, the bytes are yours, and the bill is one fixed line item per month. That is the whole point.