Die Kunst des Deployments: ARM, AMD und GLIBC
Die Kunst des Deployments: ARM, AMD und GLIBC
5. Februar 2026 — Heute habe ich gelernt, dass die Distanz zwischen einem funktionierenden lokalen Build und einem laufenden Production-Container nicht in Codezeilen gemessen wird, sondern in kryptischen Fehlermeldungen und Stack-Overflow-Tiefentauchgängen.
Das Setup
- Quelle: M2 MacBook (ARM64 / Apple Silicon)
- Ziel: Hetzner-Cloud-Server (AMD64 / x86_64)
- Stack: Docker Multi-Stage Build → Google Cloud Artifact Registry → Docker Compose
Akt I: Der Plattform-Mismatch
exec /app/bin/spotlight: exec format error
Mein Mac hatte ein ARM64-Image gebaut. Mein x86_64-Server weigerte sich, es auszuführen.
Lösung: --platform linux/amd64 im Docker-Build-Befehl.
Akt II: Der JIT-Verrat
** (ArgumentError) could not call Module.put_attribute/3
because the module Spotlight.MixProject is already compiled
QEMU-Emulation + Erlang-JIT = Chaos. José Valim bestätigte das Problem in einem GitHub-Issue.
Lösung: ENV ERL_AFLAGS="+JMsingle true" im Dockerfile.
Akt III: Die GLIBC-Überraschung
version 'GLIBC_2.34' not found
Builder-Stage nutzte Debian Bookworm (GLIBC 2.36); Runtime nutzte Bullseye (GLIBC 2.31).
Lösung: Beide Stages auf debian:bookworm-slim angleichen.
Akt IV: Das Persistent-Storage-Rätsel
Uploads lieferten 404-Fehler. In einem Phoenix-Release funktionieren relative Pfade nicht. Außerdem werden Dateien im Container bei jedem Deploy gelöscht.
Lösung: UPLOADS_PATH-Umgebungsvariable + Docker-Volume-Mount + Custom-Plug für das Ausliefern der Uploads.
Lektionen
- Cross-Platform-Docker-Builds auf Apple Silicon sind trickreich
- Builder- und Runtime-Base-Images immer angleichen
-
In Releases
Application.app_dir/2verwenden, nie relative Pfade - Persistenten Speicher von Anfang an einplanen
- Caddy ist magisch für automatisches HTTPS
Die App ist live. Die Reise geht weiter.