Die zwölf Faktoren, auf einem Server, den du besitzt
Die Twelve-Factor-App-Methodik kam aus Herokus frühem Engineering-Team und beschreibt die Einschränkungen, die eine Web-App erfüllen muss, um auf jeder modernen Plattform gut zu laufen. Sie überlebte die Plattform, die sie hervorbrachte, weil es bei den zwölf Faktoren nie wirklich um Heroku ging – es geht darum, was Software portabel, skalierbar und betreibbar macht, unabhängig davon, wem die Maschine darunter gehört. Hier ist, wo jeder einzelne konkret auftaucht, auf einer Plattform, die um Server gebaut ist, die du besitzt statt mietest.
I. Codebase. Ein Repo, ein Deploy – git push, oder dieselbe Git-URL ins Dashboard eingefügt, baut und deployt bei jedem Push neu. Nichts daran ist spezifisch für den Eigentümer des Servers; es ist derselbe Workflow, den Heroku populär gemacht hat, nur gegen Infrastruktur laufend, zu der du die Schlüssel hältst.
II. Dependencies. Explizit deklariert, nie vom Host angenommen: requirements.txt, Gemfile, package.json, go.mod, composer.json, mix.exs werden direkt gelesen, und nichts an der Build- oder Laufzeitumgebung wird als systemweit vorinstalliert angenommen.
III. Config. Liegt unter Variables, oder wird gesetzt mit railyard env:set my-app KEY=VALUE – nie ins Build eingebrannt. Dasselbe Artefakt läuft unverändert von einem Preview-Branch bis zur Produktion; ein Bug im Staging ist entweder real oder nicht, nie ein Artefakt davon, dass Staging ein anderes Bundle läuft als Prod.
IV. Backing services. Eine Datenbank, Redis, oder eine andere App auf dem Canvas wird gleich referenziert, ob lokal oder entfernt: ${{postgres.RAILYARD_PRIVATE_URL}}, pro Umgebung aufgelöst. Tausche die Backing-Ressource aus, und der Code der App muss es nie wissen.
V. Build, Release, Run. Drei getrennte Stufen, nicht ein verwischter Schritt: Eine Build-Methode (Dockerfile zuerst, falls vorhanden, dann Railpack, Nixpacks oder Buildpacks) erzeugt ein Artefakt; ein Release-Command (php artisan migrate --force, python manage.py migrate --no-input, mix ecto.migrate) läuft einmal gegen den neuen Release, bevor Traffic umzieht; dann startet die Run-Stufe ihn tatsächlich. Jede Stufe kann unabhängig scheitern, und tut es auch, statt dass ein Skript alle drei macht und verdeckt, welcher Teil kaputt ist.
VI. Processes. Stateless, und mehr als ein Typ, der sich ein einziges Build teilt: web, worker, scheduler, beat – jeder mit einem anderen Command gestartet, jeder unabhängig skaliert (railyard scale my-app worker=2). Eine Codebase, mehrere Rollen, nie mehrere Codebases, die man synchron halten muss.
VII. Port binding. Die App bindet $PORT selbst und exportiert HTTP als Service – sie wird nicht irgendwie magisch in einen Laufzeitcontainer injiziert. Ein Go-Binary, ein Rails-Prozess, ein kompilierter Elixir-Release: alle binden denselben $PORT-Vertrag, und genau deshalb funktioniert dieselbe Build-Methode über völlig unterschiedliche Sprachen hinweg.
VIII. Concurrency. Skalieren über das Prozessmodell, nicht indem man einen Prozess mehr machen lässt: web=3 worker=2 sind zwei unabhängig skalierte Dimensionen desselben Builds, keine Thread-Count-Tuning-Übung innerhalb eines einzelnen Prozesses.
IX. Disposability. Schneller Start, geordnetes Herunterfahren, und ein neuer Release übernimmt Traffic erst, sobald sein Container tatsächlich antwortet – der alte bedient weiter, bis der neue beweist, dass er bereit ist, sodass ein schlechter Deploy nie ein Fenster bekommt, in dem nichts läuft.
X. Dev/Prod Parity. Die eine Regel, die wortwörtlich in der eigenen Erkennungslogik jedes Frameworks auf dieser Plattform auftaucht: Derselbe Build läuft unverändert vom ersten Push eines Entwicklers bis zu Staging und Produktion. Kein separates „Produktions-Dockerfile“, kein umgebungsspezifisches Neubauen – Parität ist kein Ziel, das man anstrebt, es ist der einzige Weg, der existiert.
XI. Logs als Event-Streams. Die App gibt einfach auf stdout/stderr aus; sie verwaltet nie eigene Logdateien. Was der Container ausgibt, ist das, was live angezeigt wird, was später durchsucht wird und was in einen Drain geht – der Job der App endet beim Ausgeben, genau wie es immer sein sollte.
XII. Admin-Prozesse. Einmalige Tasks laufen in genau derselben Umgebung wie die laufende App, nicht in einer separat gepflegten Konsole: railyard run my-app -- bin/rails db:migrate startet einen frischen Container aus dem aktuellen Release – die CLI selbst borgt sich Herokus eigenen Namen für dieselbe Idee (heroku run), weil die Idee schon richtig war.
Der eigentliche Punkt, das jetzt noch mal durchzugehen
Keine dieser zwölf Regeln verlangt, Infrastruktur von demjenigen zu mieten, der sie geschrieben hat. Sie beschreiben, wie man Software baut, der es egal ist, wem die Maschine gehört, auf der sie läuft – genau die Eigenschaft, die eine App überhaupt erst von jeder Plattform weg portabel macht, einschließlich dieser hier.
Vollständige Referenz: Build-Methoden, Variablen und Secrets, CLI-Dokumentation.