DEV Community

John
John

Posted on

Immich Sizing Guide: What a 20,000 Photo Google Takeout Import Really Costs in Disk, RAM and Hours

Budget roughly 1.35 to 1.6 times your raw photo size on disk, 6 GB of RAM for the full stack with machine learning enabled, 4 CPU cores, and a full weekend of background processing before a 20,000 item library is fully searchable. The import itself is the fast part. Thumbnail generation, video transcoding and machine learning jobs are what actually keep the server busy, and three settings decided in the first hour, the storage template, the machine learning model, and the transcoding policy, are the ones that force a full re-run of every job if you change your mind in month two.

TL;DR by reader profile:

  • The phone-only archivist, one iPhone and 20,000 photos with almost no video: 2 CPU cores, 4 GB RAM and 1.4x your library size is enough, because thumbnails dominate and transcoding barely runs.
  • The family with a decade of camcorder video, 20,000 items where 3,000 are clips: go to 4 cores and 8 GB RAM before you import, because video transcoding is the single job that will run for days on weak hardware.
  • The RAW shooter with a Lightroom folder tree, 20,000 files averaging 25 MB: use an external library in read-only mode rather than uploading, so Immich never becomes the only copy of your originals.
  • The privacy-first switcher leaving Google Photos this week: set the storage template and the machine learning model before the first upload, then never touch them again, because both settings rewrite work already done.
  • The low-power host, a Raspberry Pi 5 or an N100 mini PC: disable the smart search model on day one and run facial recognition only, or accept that machine learning jobs will still be queued a week later.
  • The person who just wants it working tonight: import with default settings, leave transcoding on the optimal policy, and treat the first 72 hours as unattended processing time rather than a broken install.

The central tradeoff is that every setting which makes Immich feel fast and searchable later, larger machine learning models, generated previews, transcoded video, costs you disk and hours of CPU during the first week, and changing your mind afterwards means paying that cost a second time.


Table of contents


How much disk space does Immich actually need for 20,000 photos?

Start from the size of your Google Takeout, not the photo count. A 20,000 item library from a modern phone is usually somewhere between 60 GB and 120 GB of originals, because a 12 megapixel HEIC frame lands near 2 MB while a single 4K clip can pass 400 MB. Immich stores your originals untouched, then adds derived files on top.

The multiplier you should plan for is 1.35x to 1.6x the original size, split across four directories under UPLOAD_LOCATION:

  • upload/ and library/, the originals: 100 percent of your Takeout size, byte for byte, because Immich never recompresses the file you gave it.
  • thumbs/, the previews: typically 15 to 25 percent on a photo heavy library, since every asset gets a small thumbnail plus a larger preview image used by the timeline and the detail view.
  • encoded-video/, the transcodes: zero if you have no video, but easily 50 to 100 percent of your video bytes again when the default policy re-encodes clips your browser cannot play natively.
  • The Postgres volume, metadata and machine learning vectors: small in absolute terms, usually a few hundred megabytes at this scale, but it grows with embeddings and faces rather than with file size.

Practical floor: if your Takeout unzips to 90 GB, provision 200 GB and do not let the filesystem cross 80 percent during import. Running du -sh on each subdirectory after the first 1,000 assets gives you a real multiplier for your own library, which beats any generic estimate.


Why is the Immich database and thumbnail folder bigger than you expected?

Because Immich does not generate one thumbnail per photo. It generates a small WebP tile for the timeline grid and a much larger preview used whenever you open an asset, and that second file is the one that surprises people. A 2 MB HEIC original can produce a preview of several hundred kilobytes, so the ratio between a phone photo and its derivatives is far worse than it is for a 25 MB RAW file, where the same preview is a rounding error.

Four things inflate these directories beyond a naive estimate:

  • Preview resolution is a setting, not a constant: the preview size configured in Administration, Settings, Image Settings applies to every asset, so raising it after import means every existing preview is regenerated and the old ones replaced.
  • Motion photos count twice: an iPhone Live Photo or a Samsung motion shot arrives as a still plus a short video, and Immich stores and processes both, which quietly turns 20,000 selected items into more than 20,000 stored assets.
  • Postgres carries vectors, not just rows: smart search stores an embedding per asset and facial recognition stores one per detected face, so a library with many group photos grows the database faster than a library of landscapes.
  • Deleted assets linger for 30 days: the trash retention default keeps originals and derivatives on disk until the period expires, so disk usage during a messy first week reflects your mistakes as well as your library.

Check the real split with du -sh thumbs encoded-video inside UPLOAD_LOCATION before you conclude anything about total size.


How much RAM does Immich need, and what happens when it runs out?

Plan for 6 GB of system RAM for the full stack with machine learning enabled, and 4 GB if you disable smart search. The four containers are not equal: the server and Postgres are steady and modest, Redis is tiny, and immich-machine-learning is the one that spikes, because it loads a model into memory the first time a job needs it and holds it there.

What failure looks like is the part nobody warns you about. The kernel OOM killer does not stop Immich politely. It kills the machine learning container mid job, the job returns to the queue, the container restarts, reloads the model, and fails again on the same asset. You get a silent loop that burns CPU for hours and never advances the counter.

Three signals tell you this is happening:

  • docker compose ps shows a restart count climbing: the machine learning container sits at 5 or 10 restarts while the server container sits at 0.
  • The job queue stalls at the same number: smart search stays frozen while thumbnail generation keeps moving, because only one container is dying.
  • dmesg | grep -i oom prints kill lines: this is the definitive check, and it names the process that was terminated.

The fix is either more RAM, a smaller model, or fewer concurrent machine learning jobs, set to 1 in the job settings.

Hosting choice matters here because you cannot add RAM to a box that has none free. A NAS with 4 GB shared with other services, a self-managed VPS, a mini PC and a managed option are all viable starting points. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user.


How many CPU cores does the first import really use?

All of them, unless you tell Immich otherwise. Immich runs background work as named job queues, and each queue has its own concurrency setting in Administration, Settings, Job Settings. The defaults are tuned for a machine that is doing nothing else, so on a 4 core box the first import will pin every core for hours and make the web interface feel sluggish while you are still uploading.

The queues do not weigh the same:

  • Thumbnail Generation: the heaviest sustained consumer during a bulk import, because it decodes every original and writes two derivatives, and it scales almost linearly with the concurrency value you set.
  • Video Transcoding: the one queue you should keep at concurrency 1, since FFmpeg already uses multiple threads internally and running two transcodes at once mostly makes both slower.
  • Smart Search and Face Detection: CPU bound unless you have a supported GPU, and these are the queues that decide whether your library is searchable on day three or day seven.
  • Metadata Extraction: cheap per asset, fast to complete, and rarely the bottleneck at 20,000 items.

Four cores is a workable floor. Two cores works but roughly doubles your wall clock time, and a shared vCPU with a burst credit balance will collapse to baseline speed partway through and stay there.

Watch it live with docker stats, which shows per container CPU percentage. If immich-server sits near your core count times 100 percent for hours, that is normal during first import, not a fault. Lower Thumbnail Generation concurrency to 2 if you need the machine responsive for anything else.


How long does a 20,000 item Google Takeout import take from start to searchable?

Think in phases, not in one number. The upload finishes long before the library is usable, and the gap between those two moments is where people assume something is broken.

Phase What is happening Typical shape on 4 cores
Upload via immich-go or the CLI Files transferred, metadata read, duplicates skipped Minutes to a few hours, limited by disk or LAN speed
Metadata extraction Dates, GPS, camera fields written to Postgres Completes soon after upload, rarely the bottleneck
Thumbnail generation Two derivatives written per asset Several hours, and the timeline stays gappy until it ends
Video transcoding Non compatible clips re-encoded by FFmpeg Hours to days, entirely driven by how much video you have
Smart search and face detection Embeddings and faces computed per asset The long tail, often the last queue still running

A photo heavy library with under 500 short clips is usually fully processed inside 24 hours on 4 cores. Add 3,000 camcorder clips and the same library can take a long weekend, because transcoding runs at concurrency 1 by design.

Two practical points. First, upload speed and processing speed are independent: immich-go can finish at 2 a.m. while the job queues still have 18,000 items pending. Second, the Jobs page in the admin panel shows an active and a waiting count per queue, and the waiting count falling is the only honest progress bar you have.

Do not judge the install until every queue reads zero. Search results, people grouping and the map view are all incomplete before that point, and re-running them costs the same hours again.


Should you upload into Immich or point it at an external library?

Upload if Immich is becoming your primary photo home. Use an external library if you already have a folder tree you edit with other tools and want to keep owning.

The two paths differ in who controls the files on disk:

  • Uploaded assets live under UPLOAD_LOCATION and obey the storage template: Immich decides the folder layout and filename, moves files when you change the template, and treats deletion in the app as deletion on disk after the trash period.
  • External libraries are read only by design: you register a path such as /mnt/photos in the container, Immich indexes what it finds, generates thumbnails into its own directories, and never writes to or renames your originals.
  • External libraries need a rescan to notice changes: new files added by Lightroom, Syncthing or a NAS share appear after a scan job runs, either on the configured interval or when you trigger it manually, which is not the instant behaviour uploads give you.
  • Mobile backup always uploads: the phone app has no concept of an external path, so a mixed setup is normal, historic archive as an external library and new phone photos as uploads.
  • Duplicate risk is real if you do both with the same files: importing a Takeout with immich-go and also mounting that same folder externally gives you every asset twice, counted twice in storage reporting.

For a 20,000 file RAW archive averaging 25 MB, the external route is the safer first week choice: your originals stay where your backup script already finds them, and a mistake in Immich cannot rename or move 500 GB of files.


What does the Google Takeout format break, and how do you fix it before importing?

Takeout does not hand you a photo library. It hands you archives of files whose metadata has been moved out of the images and into JSON sidecars, and if you import them naively your entire timeline collapses onto the import date.

  • Dates and GPS live in sidecar JSON, not in the file: each media file gets a companion .json holding photoTakenTime and location, and Immich's plain upload path does not merge them, which is why so many first imports show 20,000 photos all dated today.
  • Sidecar filenames do not reliably match their media: Google truncates long names and appends counters like IMG_1234(1).jpg, so naive pairing by filename fails on a meaningful slice of any large export.
  • Edited copies arrive alongside originals: a file plus its -edited variant are two assets, which inflates your item count above what Google Photos showed you.
  • Albums are folders, not metadata: album membership is expressed by directory layout and an album JSON, so a plain folder upload gives you every photo and zero albums.
  • Live Photos are split: the still and the paired video land as separate files that need rejoining, not as one asset.

The fix is tooling, not manual cleanup. immich-go was written for exactly this: it reads the sidecars, rebuilds dates, recreates albums and pairs motion photos, and it can consume the Takeout zip files directly without you unzipping 100 GB first.

Import a single archive as a test run of a few hundred assets, confirm the timeline dates look correct, then delete those assets and run the full set. Discovering a date problem after 20,000 items means redoing all of it.


Which storage template should you set before the first upload?

Decide this before a single asset lands, because the template controls the on disk path of every uploaded file, and changing it later triggers a Storage Migration job that physically moves all 20,000 files.

Templates are built from variables such as {{y}}, {{MM}}, {{filename}} and {{ext}}, set in Administration, Settings, Storage Template. Four sane choices:

Template Resulting path shape Best for
Disabled, the default Random directory and asset id under the user folder People who will never touch the files outside Immich
{{y}}/{{MM}}/{{filename}} 2019/07/IMG_1234.jpg Anyone who wants a browsable archive that survives Immich
{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}} 2019/2019-07-14/IMG_1234.jpg Event heavy libraries where one day equals one shoot
{{album}}/{{filename}} Corsica 2019/IMG_1234.jpg Album driven workflows, with the caveat that assets in no album fall back

The default is genuinely defensible. Random paths never collide and never break when you rename an album. The argument against it is portability: if Immich is your only index, a corrupted database leaves you with a directory of meaningless filenames.

Two hazards. Filename collisions inside the same folder get a numeric suffix, so {{y}}/{{filename}} on a phone that resets its counter will produce IMG_0001_1.jpg. And the template applies to uploaded assets only, never to external libraries, which keep their original paths untouched.

Pick a year and month layout unless you have a specific reason not to. It is readable, it sorts, and it means a plain file browser can still make sense of your archive years from now.


Which machine learning model should you choose on day one?

This is the setting with the harshest change penalty. Smart search stores one embedding per asset, and embeddings from different models are not interchangeable, so switching models invalidates all 20,000 of them and forces a full re-run of the Smart Search queue.

The choice lives in Administration, Settings, Machine Learning Settings, and the models are pulled from the Immich Hugging Face collection on first use.

  • The default CLIP model: shipped because it fits modest hardware, and on a 4 core box with no GPU it is the only option that finishes a 20,000 item library in hours rather than days.
  • Larger visual models: better at abstract queries like "birthday cake" or "snow on a mountain", at the cost of more RAM in the machine learning container and a longer queue, which is exactly the combination that triggers the OOM loop on a 4 GB host.
  • Multilingual models: the only way to search in a language other than English, noticeably heavier than their English only equivalents, and worth choosing on day one if your household does not think in English.
  • Facial recognition is separate: it runs its own detection and recognition models with their own concurrency, so you can keep faces enabled while leaving smart search off entirely.
  • Off is a valid answer: on a Raspberry Pi 5 or an N100, disabling smart search turns a week of queued jobs into a library that is fully browsable by date and album tonight.

Test with 200 assets before committing. Search for three things you would realistically look for, judge the results, then import the rest. Making that judgement after the full import costs you the entire queue again.


What should the video transcoding policy be, and what does it cost you?

Leave it on the default optimal policy unless you have a specific reason not to, then understand exactly what that policy is doing to your disk and your weekend.

Transcode policy What it re-encodes What it costs you
Don't transcode Nothing Zero extra disk, but clips your browser cannot decode simply will not play
Videos not in an accepted format Only unsupported codecs and containers The cheapest useful option, ideal for phone footage that is already H.264 in MP4
Videos higher than target resolution or not in an accepted format The above, plus anything above the target resolution, 720p by default The default, and the reason a 4K library balloons in processing time
All videos Every clip regardless of source Doubles your video storage and can run for days on 3,000 items

Three things decide the real cost. The target resolution, because raising it to 1080p produces larger transcodes but keeps quality your phone actually shot. The constant rate factor, where the default of 23 trades size against fidelity. And hardware acceleration, which is the difference between hours and days: Immich supports NVENC, QSV, VAAPI and RKMPP, enabled by adding hwaccel.transcoding.yml to your compose command and selecting the matching API in the settings.

The trap is that the original is always kept. Transcodes are additional files in encoded-video/, never replacements, so an aggressive policy costs storage on top of your originals rather than saving any.

If your video is mostly modern phone footage, switching to "videos not in an accepted format" before the first import can remove most of the transcoding queue entirely.


Where should you actually run Immich: NAS, mini PC, home server or hosted?

The hardware question is really a question about video and about who fixes it at 11 p.m. when the phone app stops backing up.

  • A consumer NAS, Synology or QNAP class: attractive because the disks are already there, but many models ship 2 GB or 4 GB of RAM shared with the NAS operating system, and their Celeron and ARM CPUs turn the transcoding queue into a multi day job. Check for a container station and free RAM before assuming it will work.
  • A mini PC, N100 or similar: the sweet spot for a first Immich server. Four cores, upgradable RAM, low idle draw, and Intel Quick Sync on board, which is the single change that collapses video processing time.
  • An old desktop or a home server: the most CPU per euro you will ever get, and the right answer if you already own one. The costs are electricity, noise and the fact that your library now depends on a machine with no redundancy.
  • A VPS or a managed personal server: removes the hardware problem and the port forwarding problem, and moves the storage question into a monthly bill, since 200 GB of block storage is a different line item than a 4 TB disk you buy once. Yundera is a managed Personal Cloud Server, built on CasaOS, where apps are installed from an app store in one click rather than assembled from compose files by hand, and each app is reachable on a public HTTPS subdomain via NSL.SH mesh routing.

Whatever you pick, the deciding number is free RAM, not disk. Disk you can add later. A 4 GB ceiling shared with three other containers is what actually stops a 20,000 item import.


Which day-one settings force a full re-run if you change them later?

Some Immich settings are free to change at any time. Five are not, and the difference is whether the change invalidates work already written to disk. On a 20,000 item library, each of these costs you the same hours you spent during the first import.

Setting What changing it later triggers Safe default for week one
Storage template A Storage Migration job that moves every uploaded file on disk Set it before the first upload, then leave it
Smart search model All existing embeddings discarded, full Smart Search queue re-run Pick the model you can afford to run, test on 200 assets
Preview and thumbnail size Thumbnail Generation re-runs for every asset, old derivatives replaced Accept the default unless you view photos on a 4K display
Transcode policy or target resolution Every clip matching the new rule is re-encoded from the original Decide by looking at what codecs your clips actually use
Facial recognition model Face detection and recognition re-run, and people you named can need reassigning Enable it once and do not switch models casually

Two settings that look scary and are not: UPLOAD_LOCATION can be moved if you move the directory and keep the structure intact, and job concurrency can be raised or lowered mid import with no penalty at all.

The practical rule is simple. Anything that changes how a derived file is generated forces regeneration of every derived file. Anything that changes scheduling or naming of the running system does not.

Write your five choices down before you import. Reviewing them takes 15 minutes. Discovering one was wrong in month two costs you another weekend of queued jobs.

Top comments (0)