<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gennyi07</title>
    <description>The latest articles on DEV Community by Gennyi07 (@gennyi07).</description>
    <link>https://dev.to/gennyi07</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3852425%2Fbe93f8be-003a-4f58-9ea0-563f20ac8403.png</url>
      <title>DEV Community: Gennyi07</title>
      <link>https://dev.to/gennyi07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gennyi07"/>
    <language>en</language>
    <item>
      <title>Immich on Android/Termux without Docker, root, or glibc (aarch64 port)</title>
      <dc:creator>Gennyi07</dc:creator>
      <pubDate>Mon, 30 Mar 2026 23:11:13 +0000</pubDate>
      <link>https://dev.to/gennyi07/immich-on-androidtermux-without-docker-root-or-glibc-aarch64-port-301i</link>
      <guid>https://dev.to/gennyi07/immich-on-androidtermux-without-docker-root-or-glibc-aarch64-port-301i</guid>
      <description>&lt;p&gt;A few weeks ago I set myself a challenge: run &lt;a href="https://github.com/immich-app/immich" rel="noopener noreferrer"&gt;Immich&lt;/a&gt; — a full self-hosted photo management platform — natively on my Android phone, without Docker, without root, and without any cloud dependency.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://github.com/Gennyi07/immich-native-android" rel="noopener noreferrer"&gt;immich-native-android&lt;/a&gt;, a working port for Android/Termux on aarch64. This is what it took to get there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is not trivial
&lt;/h2&gt;

&lt;p&gt;Immich is designed to run on Linux servers via Docker. It's a multi-service stack: PostgreSQL, Redis, a Node.js server, and a Python ML service for face recognition and smart search.&lt;/p&gt;

&lt;p&gt;Android is not Linux. The differences that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bionic libc&lt;/strong&gt; instead of glibc — most prebuilt native binaries simply crash or silently fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No FUSE&lt;/strong&gt; without root — you can't mount remote filesystems as local paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No systemd&lt;/strong&gt; — no service manager, no socket activation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Process Killer&lt;/strong&gt; — Android 12+ aggressively kills background processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only /tmp&lt;/strong&gt; — many build systems assume they can write there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No FHS layout&lt;/strong&gt; — paths like &lt;code&gt;/usr/bin&lt;/code&gt; don't exist in Termux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every single one of these required a workaround.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proot Debian (container)
└── PostgreSQL 17 + VectorChord extension
        ↓ localhost:5432
Termux (native aarch64)
├── Redis
├── Node.js 20  →  Immich server      (port 2283)
└── Python + ONNX  →  Immich ML       (port 3003)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL runs inside a proot Debian container because it needs glibc and kernel features unavailable in Termux. Everything else runs natively.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problems, one by one
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Node.js version
&lt;/h3&gt;

&lt;p&gt;The latest Node.js caused silent startup failures on Bionic. The fix was pinning to Node 20 LTS and forcing npm to treat the platform as Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;npm_config_platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux
&lt;span class="nv"&gt;npm_config_arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64
&lt;span class="nv"&gt;npm_config_libc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;glibc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tricks Sharp and other native modules into using their &lt;code&gt;linux-arm64&lt;/code&gt; prebuilts instead of trying (and failing) to detect Android.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Native dependencies
&lt;/h3&gt;

&lt;p&gt;Most npm packages ship prebuilt binaries compiled against glibc. On Bionic they either crash immediately or produce silent errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharp&lt;/strong&gt; (image processing) required full recompilation from source against Termux's libvips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SHARP_FORCE_GLOBAL_LIBVIPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;bcrypt&lt;/strong&gt; — same treatment, rebuilt from source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python ML dependencies&lt;/strong&gt; (ONNX Runtime, InsightFace) had no Android/aarch64 wheels at all. Each was compiled inside a Python venv using &lt;code&gt;uv&lt;/code&gt; and Rust-based build tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--python-platform&lt;/span&gt; manylinux_2_28_aarch64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;watchfiles&lt;/code&gt; was removed entirely — it requires &lt;code&gt;maturin&lt;/code&gt; (a Rust build tool) that doesn't work on Android's Bionic.&lt;/p&gt;

&lt;p&gt;Other fixes that were necessary across the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--max-old-space-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6144  &lt;span class="c"&gt;# prevents OOM during build&lt;/span&gt;
&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/tmp                         &lt;span class="c"&gt;# /tmp is read-only on Android&lt;/span&gt;
&lt;span class="nv"&gt;npm_config_script_shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which bash&lt;span class="si"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# install scripts need bash, not sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. PostgreSQL in proot
&lt;/h3&gt;

&lt;p&gt;PostgreSQL cannot run natively in Termux — it needs glibc and kernel features that Android doesn't provide. The solution: run it inside a proot Debian container.&lt;/p&gt;

&lt;p&gt;The catch: PostgreSQL crashes when started as root inside proot. The fix was creating a dedicated &lt;code&gt;postgres&lt;/code&gt; user inside the container and launching the service under that user via a scripted proot session.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. WebDAV external library — patching Immich source
&lt;/h3&gt;

&lt;p&gt;This was the most complex problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt; index photos stored on a remote NAS without copying them to the phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Android has no FUSE support without root, so you can't mount a network share as a local path. The only Android solution (RSAF) exposes files through the Storage Access Framework — completely inaccessible from Termux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; patch Immich's source code before building.&lt;/p&gt;

&lt;p&gt;Immich's &lt;code&gt;LibraryService&lt;/code&gt; validates external library paths with &lt;code&gt;path.isAbsolute()&lt;/code&gt;, which rejects HTTP URLs. I added bypass logic for WebDAV paths before the TypeScript compilation step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;importPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// WebDAV path — skip isAbsolute() check&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NAS phone runs &lt;code&gt;rclone serve webdav&lt;/code&gt; over Tailscale. Immich indexes the photos without ever copying them locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Phantom Process Killer
&lt;/h3&gt;

&lt;p&gt;Android 12+ kills background processes not started by the foreground app. Immich runs as four separate processes (PostgreSQL, Redis, Node, Python). Without intervention, Android kills them within minutes.&lt;/p&gt;

&lt;p&gt;Fix via Shizuku + ADB (run once after each reboot):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell device_config set_sync_disabled_for_tests persistent
adb shell device_config put activity_manager max_phantom_processes 2147483647
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full Immich web UI on &lt;code&gt;localhost:2283&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Photo/video upload and backup&lt;/li&gt;
&lt;li&gt;Album management&lt;/li&gt;
&lt;li&gt;External library indexing via WebDAV (remote NAS, no local copy)&lt;/li&gt;
&lt;li&gt;Thumbnail generation&lt;/li&gt;
&lt;li&gt;Timeline and map view&lt;/li&gt;
&lt;li&gt;Machine learning (face recognition, CLIP smart search) — works on this setup, stability may vary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What doesn't (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No GPU/NPU acceleration — all ML runs on CPU&lt;/li&gt;
&lt;li&gt;Immich updates require re-running the install script&lt;/li&gt;
&lt;li&gt;Shizuku must be re-enabled after each reboot&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tested on
&lt;/h2&gt;

&lt;p&gt;Samsung Galaxy S25 — Snapdragon 8 Elite, aarch64, Android 15&lt;br&gt;
Immich v2.5.6&lt;/p&gt;




&lt;h2&gt;
  
  
  The repo
&lt;/h2&gt;

&lt;p&gt;Everything is documented and scripted:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/Gennyi07/immich-native-android" rel="noopener noreferrer"&gt;github.com/Gennyi07/immich-native-android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on &lt;a href="https://github.com/arter97/immich-native" rel="noopener noreferrer"&gt;arter97/immich-native&lt;/a&gt;, heavily reworked for Android/Bionic.&lt;/p&gt;

&lt;p&gt;If you find it useful, a ⭐ on GitHub helps the project get discovered.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The Android/Bionic compatibility layer is genuinely hostile to software not designed for it. Every layer of this stack required a workaround. But it works — and it runs on hardware most people already own.&lt;/p&gt;

&lt;p&gt;If you've been looking for a way to run Immich without a dedicated server, this might be it.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>android</category>
      <category>opensource</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
