<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Immich – Serverküche</title><link>https://serverkueche.de/en/tags/immich/</link><description>Immich – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Mon, 20 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/immich/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-hosting Immich: your private photo backup behind Traefik</title><link>https://serverkueche.de/en/tutorials/self-host-immich-photos/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/self-host-immich-photos/</guid><description>Set up Immich with Docker behind Traefik: the self-hosted alternative to Google Photos – with automatic phone backup, face recognition and HTTPS.</description><content:encoded><![CDATA[<p>Thousands of photos and videos from your phone – and all of them at Google or Apple.
<strong>Immich</strong> brings them back to your server: a self-hosted photo service that comes
astonishingly close to Google Photos, including automatic phone upload, timeline, search
and face recognition. In this recipe we set up Immich cleanly behind Traefik.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Immich v3</strong> runs behind your Traefik proxy, reachable at
<code>https://photos.YOUR_DOMAIN</code> with valid HTTPS. The official <strong>Immich phone app</strong> then backs
up your new photos and videos automatically to your server – like the cloud backup of Google
Photos, except the images live exclusively with you. Via the web interface you browse your
timeline, search by full text (&ldquo;beach&rdquo;, &ldquo;dog&rdquo;) and let Immich group faces.</p>
<p>Immich consists of <strong>four containers</strong>: the server (web + API), a machine-learning service
(for search and face recognition), a PostgreSQL database with a vector extension, and a cache
(Valkey). Sounds like a lot – but the official template takes most of it off your hands, and
we only hang the server onto Traefik.</p>
<p>The appeal over Google Photos: the images don&rsquo;t leave your server, there are no storage
subscriptions and no automatic analysis of your shots by a corporation. In return, you bear
responsibility for operation and – very importantly – for <strong>backups</strong>: if the server dies and
you have no backup, the photos are gone. That&rsquo;s exactly why the backup below is not optional
but mandatory.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Immich moves fast – pin the version
  </p>
  <div class="prose-kitchen text-sm">Immich develops rapidly. Database migrations run automatically on update and are <strong>not
backward-compatible</strong> – a downgrade is no longer cleanly possible afterwards. So <strong>always pin
a fixed version</strong> (here <code>v3.0.3</code>) instead of <code>release</code> or <code>latest</code>, and make a <strong>backup
before every update</strong>. That way you decide when to update.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A running <strong>Traefik reverse proxy</strong> with the <code>proxy</code> network and the Let&rsquo;s Encrypt resolver
<code>le</code> – see <a href="/en/tutorials/traefik-reverse-proxy/">reverse proxy with Traefik</a>.</li>
<li>A subdomain <code>photos.YOUR_DOMAIN</code> with a DNS record to your server IP – see
<a href="/en/tutorials/connect-domain-to-server/">connecting a domain to your server</a>.</li>
<li>A <strong>backup</strong>. Your photo collection is irreplaceable – first set up
<a href="/en/tutorials/restic-backups/">backups with Restic</a>.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>How big does the server need to be?
  </p>
  <div class="prose-kitchen text-sm">Immich is the most resource-hungry service in the Serverküche. Officially, <strong>at least 6 GB
RAM (8 recommended)</strong> are required – the machine-learning service for face recognition and
search in particular needs memory. The tested <strong>VPS 1000 with 8 GB RAM</strong> just meets that but
has little reserve. For a large library or several users, a server with <strong>16 GB RAM</strong> (e.g.
VPS 2000) is more relaxed. On a 4 GB server, Immich only runs with <strong>ML turned off</strong> (see
&ldquo;When things go wrong&rdquo;). And: photos need space – plan for enough disk.</div>
</div>
<p>How much RAM your photo library including AI search really needs is calculated by the
<a href="/en/server-calculator/">server calculator</a>.</p>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 2000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCores · 16 GB RAM · 512 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €19.24/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">For a large photo library with face recognition, 16 GB RAM is comfortable.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-2000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-create-the-dns-record">Step 1: Create the DNS record</h3>
<p>Create an entry <code>photos.YOUR_DOMAIN</code> that points to your server IP, and check it:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short photos.YOUR_DOMAIN</span></span></code></pre></div>
</div>
<p>Your server IP must come back – otherwise Traefik won&rsquo;t fetch a certificate later.</p>
<h3 id="step-2-prepare-the-folder-and-env">Step 2: Prepare the folder and .env</h3>
<p>Immich is configured via a <code>.env</code> file. Create the project:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/immich <span class="o">&amp;&amp;</span> <span class="nb">cd</span> ~/immich</span></span></code></pre></div>
</div>
<p>Create a <code>.env</code> with the core values (set a strong DB password – <strong>letters and digits
only</strong>, no special characters, Immich&rsquo;s DB init doesn&rsquo;t like those):</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">UPLOAD_LOCATION</span><span class="o">=</span><span class="s">./library</span>
</span></span><span class="line"><span class="cl"><span class="na">DB_DATA_LOCATION</span><span class="o">=</span><span class="s">./postgres</span>
</span></span><span class="line"><span class="cl"><span class="na">DB_PASSWORD</span><span class="o">=</span><span class="s">YOUR_DB_PASSWORD</span>
</span></span><span class="line"><span class="cl"><span class="na">DB_USERNAME</span><span class="o">=</span><span class="s">postgres</span>
</span></span><span class="line"><span class="cl"><span class="na">DB_DATABASE_NAME</span><span class="o">=</span><span class="s">immich</span>
</span></span><span class="line"><span class="cl"><span class="na">IMMICH_VERSION</span><span class="o">=</span><span class="s">v3.0.3</span></span></span></code></pre></div>
</div>
<ul>
<li><strong><code>UPLOAD_LOCATION</code></strong> – this is where your <strong>photos and videos</strong> land. It&rsquo;s the most
important directory for the backup.</li>
<li><strong><code>DB_DATA_LOCATION</code></strong> – the PostgreSQL data. Must be on a <strong>local disk</strong> (no
network drive/NFS – the DB resents that).</li>
<li><strong><code>IMMICH_VERSION</code></strong> – the pinned version.</li>
</ul>
<h3 id="step-3-create-the-composeyaml">Step 3: Create the compose.yaml</h3>
<p>We adopt the official Immich template and only add the Traefik labels on the <code>immich-server</code>.
Create <code>compose.yaml</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">immich</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">immich-server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/immich-app/immich-server:${IMMICH_VERSION}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${UPLOAD_LOCATION}:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DB_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">REDIS_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_started</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_healthy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.immich.rule=Host(`photos.YOUR_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.immich.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.immich.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.immich.loadbalancer.server.port=2283&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default, proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">immich-machine-learning</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">model-cache:/cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/valkey/valkey:9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">${DB_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">${DB_USERNAME}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_DB</span><span class="p">:</span><span class="w"> </span><span class="l">${DB_DATABASE_NAME}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${DB_DATA_LOCATION}:/var/lib/postgresql/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD-SHELL&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME}&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">default]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">model-cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>Important to understand:</p>
<ul>
<li><strong>Only <code>immich-server</code> is on the <code>proxy</code> network</strong> and carries Traefik labels. The ML
service, Valkey and the database stay exclusively on the internal <code>default</code> network – not
reachable from outside.</li>
<li><strong><code>loadbalancer.server.port=2283</code></strong> – Immich listens on port 2283 in the container.</li>
<li>The <strong>database</strong> is deliberately the prebuilt Immich Postgres image with the vector
extension <em>VectorChord</em> (for image search). Don&rsquo;t just use a standard <code>postgres</code> – the
extension is then missing.</li>
<li><strong>Valkey</strong> is the Redis successor; the service is still called <code>redis</code> in the Immich
template for compatibility reasons.</li>
<li>The <strong>healthcheck</strong> on the database reports when Postgres is really ready. Via
<code>depends_on: … condition: service_healthy</code>, <code>immich-server</code> only starts then – this
prevents migration errors and the &ldquo;database not reachable&rdquo; start on the first boot.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Applying changes to the .env
  </p>
  <div class="prose-kitchen text-sm">If you later change something in the <code>.env</code>, a <code>docker compose restart</code> is <strong>not</strong> enough –
Docker only reads the environment variables when <strong>recreating</strong> the containers. Use
<code>docker compose up -d</code> then; Compose detects the change and rebuilds the affected containers.</div>
</div>
<h3 id="step-4-allow-large-uploads-traefik-timeout">Step 4: Allow large uploads (Traefik timeout)</h3>
<p>This is <strong>the</strong> stumbling block with Immich behind Traefik: by default Traefik aborts
connections after <strong>60 seconds</strong> (<code>readTimeout</code>). When uploading large videos from the phone
this leads to aborted uploads (error 502/499). Increase the timeout on the <code>websecure</code>
entrypoint in your <strong>Traefik configuration</strong> (the <code>traefik</code> service from the
<a href="/en/tutorials/traefik-reverse-proxy/">Traefik tutorial</a>):</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># ... your existing lines ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;--entrypoints.websecure.transport.respondingTimeouts.readTimeout=600s&#34;</span></span></span></code></pre></div>
</div>
<p>Restart Traefik afterwards (<code>docker compose up -d</code> in the Traefik folder). Unlike nginx,
Traefik has no fixed size limit for uploads – only this timeout has to be high.</p>
<h3 id="step-5-start-and-run-through-the-initial-setup">Step 5: Start and run through the initial setup</h3>
<p>Pull the images (several GB – this takes a while) and start:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs -f immich-server</span></span></code></pre></div>
</div>
<p>The images are several gigabytes in size – the first <code>pull</code> takes a few minutes depending on
your connection. Then check that all four containers are running:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<p>You should see <code>immich-server</code>, <code>immich-machine-learning</code>, <code>redis</code> and <code>database</code> with status
<code>running</code> (the database <code>healthy</code>). Wait until
<code>Immich Server is listening on http://[::1]:2283 [v3.0.3]</code> appears in the log. Then open
<code>https://photos.YOUR_DOMAIN</code>. Traefik fetches the Let&rsquo;s Encrypt certificate on the first
access (be patient for a moment). On the very first start, Immich greets you with <strong>&ldquo;Welcome
to Immich&rdquo;</strong> and the choice of whether you <strong>start fresh</strong> or restore a backup:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-immich-photos/immich-willkommen_hu_df42e8458020275e.webp" srcset="/en/tutorials/self-host-immich-photos/immich-willkommen_hu_a2524608a6612199.webp 480w, /en/tutorials/self-host-immich-photos/immich-willkommen_hu_df42e8458020275e.webp 768w, /en/tutorials/self-host-immich-photos/immich-willkommen_hu_22b944244b61376c.webp 1200w, /en/tutorials/self-host-immich-photos/immich-willkommen_hu_6355c1dbc067f168.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-immich-photos/immich-willkommen_hu_157a6edfcb1fc735.webp"
    alt="The welcome screen of Immich with the options &ldquo;Getting Started&rdquo; and &ldquo;Restore from database&rdquo;" title="First launch: choose \&#34;Getting Started\&#34; for a fresh installation"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">First launch: choose \&#34;Getting Started\&#34; for a fresh installation</figcaption></figure></p>
<p>Click <strong>Getting Started</strong> (you only need Restore if you&rsquo;re importing a backup). Then you
create the <strong>administrator account</strong> – as the first user you automatically become admin:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-immich-photos/immich-registrierung_hu_5e17e8d959b4be91.webp" srcset="/en/tutorials/self-host-immich-photos/immich-registrierung_hu_5da2b7656e747eea.webp 480w, /en/tutorials/self-host-immich-photos/immich-registrierung_hu_5e17e8d959b4be91.webp 768w, /en/tutorials/self-host-immich-photos/immich-registrierung_hu_dc0630b55968f943.webp 1200w, /en/tutorials/self-host-immich-photos/immich-registrierung_hu_94e19f1b46ee602b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-immich-photos/immich-registrierung_hu_7d1f7c90347fc007.webp"
    alt="The Immich form &ldquo;Admin Registration&rdquo; with fields for email, password and name" title="Admin registration – the first user automatically becomes administrator"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Admin registration – the first user automatically becomes administrator</figcaption></figure></p>
<p>After the first login, a short <strong>setup wizard</strong> guides you through theme, language and basic
privacy settings:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-immich-photos/immich-onboarding_hu_605f1bfc221f0d38.webp" srcset="/en/tutorials/self-host-immich-photos/immich-onboarding_hu_3985d8f71d5bbe6b.webp 480w, /en/tutorials/self-host-immich-photos/immich-onboarding_hu_605f1bfc221f0d38.webp 768w, /en/tutorials/self-host-immich-photos/immich-onboarding_hu_7a4acec9c89174a5.webp 1200w, /en/tutorials/self-host-immich-photos/immich-onboarding_hu_afbeaf6cfb205ba2.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-immich-photos/immich-onboarding_hu_8ded2f93b7bae082.webp"
    alt="The Immich setup wizard greets the new administrator" title="The onboarding wizard after the first login"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The onboarding wizard after the first login</figcaption></figure></p>
<p>After that you land on your (still empty) <strong>timeline</strong> – the heart of Immich:</p>
<p><figure class="my-6"><img src="/en/tutorials/self-host-immich-photos/immich-timeline_hu_7736fed89cfe0c0f.webp" srcset="/en/tutorials/self-host-immich-photos/immich-timeline_hu_29dd4a91051bc5d2.webp 480w, /en/tutorials/self-host-immich-photos/immich-timeline_hu_7736fed89cfe0c0f.webp 768w, /en/tutorials/self-host-immich-photos/immich-timeline_hu_5f77e6240ffda01c.webp 1200w, /en/tutorials/self-host-immich-photos/immich-timeline_hu_3e1ec3671920a427.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/self-host-immich-photos/immich-timeline_hu_d8deafe9761a334f.webp"
    alt="The empty photo timeline of Immich with the prompt to upload the first photo" title="The photo timeline – all images later flow in here (server online, v3.0.3)"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">The photo timeline – all images later flow in here (server online, v3.0.3)</figcaption></figure></p>
<h3 id="step-6-connect-the-phone-app">Step 6: Connect the phone app</h3>
<p>The real benefit comes from the app. Install <strong>Immich</strong> from the App Store or Play Store (or
F-Droid). At launch it asks for the <strong>server address</strong> – enter <code>https://photos.YOUR_DOMAIN</code>
and log in with your account. Then, in the app settings, enable the <strong>backup</strong> and choose the
albums to be uploaded.</p>
<p>From now on your phone backs up new photos automatically to your server – in the background
and over Wi-Fi. The first pass of a large library takes a while; after that only new shots are
added.</p>
<p>After the upload, Immich keeps working in the background: it generates thumbnails, reads the
capture metadata (date, location) and lets the ML service <strong>recognize faces</strong> and index the
images for <strong>smart search</strong>. These jobs run for some time after the first big import – so
faces and search hits only appear gradually. You see the progress under <strong>Administration →
Job queues</strong>. In the sidebar you then find the typical photo features: <strong>Explore</strong> (by people
and places), the <strong>map</strong> with geolocation, <strong>albums</strong> for sharing and the <strong>memories</strong> (&ldquo;a
year ago&rdquo;).</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Videos &amp; transcoding on the VPS
  </p>
  <div class="prose-kitchen text-sm">Immich converts videos for playback in the browser (transcoding). On a normal VPS <strong>without a
graphics card</strong> this happens via the CPU – with many or long videos this is noticeably slower
and maxes out cores. For a private phone backup this is usually no problem; whoever has a lot
of video should choose the server correspondingly larger.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>More users
  </p>
  <div class="prose-kitchen text-sm">For the family, create further accounts under <strong>Administration → Users</strong> – each gets its own
separate library. Registration from outside is off by default; new users only arise via the
admin account.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Your photos are on the internet
  </p>
  <div class="prose-kitchen text-sm">As soon as Immich is reachable via <code>photos.YOUR_DOMAIN</code>, the login page is open on the net. So
set a <strong>long, unique password</strong> for the admin and all user accounts and enable <strong>two-factor
authentication</strong> in the account settings. Whoever wants to be extra safe makes Immich reachable
only via a VPN – but for automatic phone upload, direct HTTPS reachability is usually the more
practical way.</div>
</div>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>Uploads of large videos abort after about a minute (error 502 or 499).</strong> Traefik&rsquo;s
<code>readTimeout</code> (default 60 s) kicks in. Set it to <code>600s</code> (or higher) as in step 4 and restart
Traefik. This is by far the most common Immich-behind-proxy error.</p>
<p><strong>The <code>immich-machine-learning</code> container crashes or exits with &ldquo;exit 137&rdquo;, search and face
recognition don&rsquo;t work.</strong> Too little RAM – the container was killed by the system (out of
memory). Give the server more memory, or <strong>disable ML</strong> by removing the service
<code>immich-machine-learning</code> from the <code>compose.yaml</code>. Immich then runs without face recognition
and smart search, but upload and timeline work normally.</p>
<p><strong>After an update the containers no longer start or report migration errors.</strong> Server, machine
learning and database must run on the <strong>same</strong> version. Set the new <code>IMMICH_VERSION</code> in the
<code>.env</code> and update <strong>all</strong> containers together (<code>docker compose pull &amp;&amp; docker compose up -d</code>).
A downgrade after a migration isn&rsquo;t possible – only a reset from the backup.</p>
<p><strong>Immich shows &ldquo;maintenance mode&rdquo; / &ldquo;temporarily unavailable&rdquo;.</strong> Immich v3 starts into a
maintenance mode under certain database states. The log (<code>docker compose logs immich-server</code>)
then contains a URL with a <strong>one-time token</strong> (<code>…/maintenance?token=…</code>) – through it you log
into the maintenance mode and choose &ldquo;restart&rdquo; or end it. After that the normal interface is
back.</p>
<p><strong>Container won&rsquo;t start, &ldquo;permission denied&rdquo; on the database or upload directory.</strong>
<code>UPLOAD_LOCATION</code> or <code>DB_DATA_LOCATION</code> have the wrong access rights or are on a network
drive. Put both on a local disk and make sure Docker may write into them.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li>
<p><strong>Two things belong in the backup – consistently together.</strong> Your <strong>photos</strong>
(<code>UPLOAD_LOCATION</code>, i.e. <code>~/immich/library</code>) and the <strong>database</strong>. The DB dump contains only
the metadata; without the matching files it&rsquo;s worthless, and vice versa. Cleanest is to stop
Immich briefly and back up both together:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose stop immich-server
</span></span><span class="line"><span class="cl">docker compose <span class="nb">exec</span> -T database pg_dump --clean --if-exists <span class="se">\
</span></span></span><span class="line"><span class="cl">  --dbname<span class="o">=</span>immich --username<span class="o">=</span>postgres <span class="p">|</span> gzip &gt; immich-db.sql.gz
</span></span><span class="line"><span class="cl">docker compose start immich-server</span></span></code></pre></div>
</div>
<p>Back up the dump together with the <code>library</code> folder <strong>encrypted and off-site</strong> with
<a href="/en/tutorials/restic-backups/">Restic</a>. Alternatively, Immich can generate automatic DB
dumps under <strong>Administration → Job queues</strong> – you still have to back up the photos
separately.</p>
</li>
<li>
<p><strong>Rehearse the worst case once.</strong> A backup you&rsquo;ve never restored is just a hope. To restore,
you import the DB dump into a fresh Immich instance of the same version and place the
<code>library</code> folder in the same spot – Immich offers the option <strong>&ldquo;Restore from database&rdquo;</strong> on
the first start for this (the second button from step 5). What matters is the <strong>same
version</strong>: the database state and the program must match.</p>
</li>
<li>
<p><strong>Backup before every update.</strong> Because migrations aren&rsquo;t reversible, the backup is your only
way back. Back up first, then raise <code>IMMICH_VERSION</code>, then
<code>docker compose pull &amp;&amp; docker compose up -d</code>.</p>
</li>
<li>
<p><strong>Honest about the effort:</strong> Immich often brings several releases per month. You don&rsquo;t have
to follow every one – but before a jump across several versions, read the release notes, and
stick to the pinned version until you deliberately update.</p>
</li>
<li>
<p><strong>Keep an eye on disk space.</strong> Photos and videos grow steadily; Immich additionally creates
thumbnails and converted videos. Monitor the disk usage (e.g. with
<a href="/en/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a>) so the server doesn&rsquo;t fill up.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>