Die 12-Factor App in Go – Moderne Cloud-Entwicklung einfach erklärt

Die 12-Factor App in Go – Moderne Cloud-Entwicklung einfach erklärt
By Matthias Petermann / on 02.11.2025

Einführung

Die 12-Factor App beschreibt eine Reihe bewährter Prinzipien für die Entwicklung Cloud-nativer Anwendungen. Sie wurde 2011 von Adam Wiggins, einem der Gründer von Heroku, formuliert – als Antwort auf die Frage, wie Software gestaltet werden kann, die in jeder Umgebung zuverlässig funktioniert.

Das Ziel besteht darin, Anwendungen zu entwickeln, die sich einfach deployen, skalieren und betreiben lassen – unabhängig davon, ob sie lokal, in Docker, Kubernetes oder auf einer Cloud-Plattform ausgeführt werden.

ℹ️ Kurz gesagt
12-Factor bedeutet: Eine Anwendung funktioniert in jeder Umgebung gleich – lokal, in CI/CD-Pipelines und in der Cloud. Sie bleibt portabel, stabil und leicht wartbar.

Warum 12-Factor für jede Laufzeitumgebung von Vorteil ist

Unabhängig davon, ob der Code in Go, Node.js, Python oder einer anderen Sprache entwickelt wird – oder auf welcher Laufzeitumgebung er läuft (z. B. Linux, Windows, Docker, Podman, Kubernetes oder Serverless) – die zwölf Prinzipien sorgen für Ordnung, Wiederholbarkeit und Stabilität.

  • Für Entwickler: Klare Abhängigkeiten und reproduzierbare Builds in jeder Umgebung.
  • Für Betriebsteams: Einheitliche Bereitstellung und einfache Skalierung über Container oder Orchestrierung.
  • Für Teams insgesamt: Konsistentes Verhalten zwischen Entwicklung, Test und Produktion.

Im Kern steht die Trennung von Code, Konfiguration und Umgebung – die Basis moderner Cloud-nativer Softwarearchitektur.


Die 12 Faktoren im Überblick

# Prinzip Bedeutung
I Codebase Eine Codequelle pro Anwendung – keine Duplikate
II Dependencies Alle Abhängigkeiten werden explizit definiert (z. B. in go.mod)
III Config Konfiguration über Umgebungsvariablen, keine Werte im Code
IV Backing Services Datenbanken und Queues sind austauschbare Dienste
V Build, Release, Run Klare Trennung zwischen Build-, Release- und Laufzeitphase
VI Processes Zustandslose Prozesse – kein In-Memory-State
VII Port Binding Die Anwendung bringt ihren eigenen Webserver mit
VIII Concurrency Skalierung über mehrere Prozesse oder Instanzen
IX Disposability Schnelles Starten und sauberes Beenden
X Dev/Prod Parity Entwicklungs- und Produktionsumgebung sind möglichst ähnlich
XI Logs Ausgabe über stdout – keine Logdateien
XII Admin Processes Einmalige Aufgaben laufen getrennt vom Hauptprozess
📝 Hinweis
Die Prinzipien sind als Richtlinien, nicht als Dogmen zu verstehen. Je konsequenter sie befolgt werden, desto wartbarer, portabler und skalierbarer wird eine Anwendung.

Von der Theorie zur Praxis: Eine kleine Go-API

Zur praktischen Umsetzung der 12-Factor-Prinzipien wird im Folgenden eine kompakte REST-API in Go vorgestellt, die Events speichert:

  • POST /events legt ein neues Ereignis an
  • GET /events gibt alle gespeicherten Ereignisse zurück

Die API nutzt reines Go (ab Version 1.23) ohne Frameworks und setzt GORM mit SQLite als leichtgewichtige Datenbank ein.


Projektstruktur

go-event-api/
├─ main.go
├─ models/
│  └─ event.go
├─ go.mod
├─ .env.example
└─ Containerfile

Die Struktur trennt Code, Modelle, Konfiguration und Build-Dateien klar voneinander – im Sinne der Prinzipien Codebase und Config.


Setup

Abhängigkeiten installieren

go mod init d2ux.net/go-event-api
go get gorm.io/gorm
go get gorm.io/driver/sqlite
go get github.com/joho/godotenv
go get modernc.org/sqlite

.env.example

PORT=8080
DATABASE_URL=events.db
ℹ️ Warum ENV-Dateien?
In Cloud-Umgebungen werden Einstellungen – etwa Ports oder Datenbankverbindungen – über Umgebungsvariablen verwaltet, nicht im Code. Das macht den Code unabhängig von der jeweiligen Laufzeitumgebung.

Der Datenbank-Layer (models/event.go)

package models

import "gorm.io/gorm"

// Event beschreibt ein Ereignis in der Datenbank.
type Event struct {
    ID        uint   `json:"id" gorm:"primaryKey"`
    Kind      string `json:"kind"`
    Message   string `json:"message"`
    CreatedAt int64  `json:"created_at" autoCreateTime"`
}

// AutoMigrate erstellt die Tabelle automatisch, falls sie noch nicht existiert.
func AutoMigrate(db *gorm.DB) error {
    return db.AutoMigrate(&Event{})
}
🏗️ Hinweis zu AutoMigrate
Für kleinere Projekte genügt die automatische Migration durch GORM. In größeren Systemen bieten spezialisierte Tools wie golang-migrate eine präzisere Kontrolle.

Der Server (main.go)

Der folgende Code enthält Konfiguration, Routing, Datenbankanbindung und Serverstart – minimalistisch, aber konform zu den 12-Factor-Prinzipien.

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"

    "github.com/joho/godotenv"
    "gorm.io/driver/sqlite"
    _ "modernc.org/sqlite"
    "gorm.io/gorm"

    "d2ux.net/go-event-api/models"
)

func main() {
    // 1. Konfiguration laden
    _ = godotenv.Load()
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    dbPath := os.Getenv("DATABASE_URL")
    if dbPath == "" {
        dbPath = "events.db"
    }

    // 2. Datenbank verbinden
    db, err := gorm.Open(sqlite.Dialector{DriverName: "sqlite", DSN: dbPath}, &gorm.Config{})
    if err != nil {
        log.Fatalf("DB-Verbindung fehlgeschlagen: %v", err)
    }
    if err := models.AutoMigrate(db); err != nil {
        log.Fatalf("Migration fehlgeschlagen: %v", err)
    }

    // 3. Routing definieren (ab Go 1.23)
    mux := http.NewServeMux()

    mux.HandleFunc("POST /events", func(w http.ResponseWriter, r *http.Request) {
        var ev models.Event
        if err := json.NewDecoder(r.Body).Decode(&ev); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        if err := db.Create(&ev).Error; err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /events", func(w http.ResponseWriter, r *http.Request) {
        var events []models.Event
        db.Order("created_at desc").Find(&events)
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(events)
    })

    // 4. Server starten
    log.Printf("Server läuft auf Port %s", port)
    if err := http.ListenAndServe(":"+port, mux); err != nil {
        log.Fatal(err)
    }
}

Lokaler Test

Vor der Containerisierung lässt sich die Anwendung lokal prüfen.

Start des Servers:

go run main.go
ℹ️ Hinweis zu Fallback-Werten
Die Anwendung enthält Fallback-Werte für Umgebungsvariablen: Wenn PORT oder DATABASE_URL nicht gesetzt sind, werden automatisch Standardwerte verwendet (PORT=8080, DATABASE_URL=events.db). So läuft die Anwendung auch ohne .env-Datei oder explizite Konfiguration zuverlässig lokal.

Hinzufügen eines Events:

curl -X POST http://localhost:8080/events \
  -H "Content-Type: application/json" \
  -d '{"kind":"info","message":"Server gestartet"}'

Abruf aller gespeicherten Events:

curl http://localhost:8080/events
✅ Läuft lokal
Wenn die API eine JSON-Liste zurückgibt, ist die Anwendung bereits 12-Factor-konform – konfigurierbar, zustandslos und portabel.

Container bauen und testen

Um die Portabilität zu gewährleisten, wird die Anwendung in einen Container verpackt – ein praktisches Beispiel für den Faktor Build/Release/Run.

Containerfile

FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o event-api .

FROM alpine:3.20
WORKDIR /home/app
COPY --from=builder /app/event-api .
EXPOSE 8080
CMD ["./event-api"]
⚠️ OCI statt Docker
Das Image ist OCI-kompatibel und kann mit Docker, Podman oder Buildah erstellt werden. Unabhängig vom genutzten Tool bleibt das Verhalten identisch.

Container bauen

podman build -t event-api:latest -f Containerfile
podman image ls | grep event-api

Nach dem Build befindet sich das Image im lokalen Container-Cache und kann direkt gestartet werden.

Container starten

mkdir -p ./data
podman run \
  --name event-api \
  -p 3000:3000 \
  -e PORT=3000 \
  -e DATABASE_URL=/data/events.db \
  -v ./data:/data \
  localhost/event-api:latest

Die API läuft anschließend isoliert im Container – konfiguriert über Umgebungsvariablen, ohne lokale Abhängigkeiten und ohne persistente Zustände innerhalb des Containers.


Warum die Anwendung 12-Factor-konform ist

Prinzip Umsetzung
Codebase Ein Repository, ein Deployment
Dependencies Gesteuert über go.mod
Config ENV-Variablen statt Hardcoding
Backing Services SQLite steht exemplarisch für austauschbare Datenbankdienste.
Build/Release/Run Klare Trennung der Phasen
Processes Zustandslos, kein globaler Speicher
Port Binding HTTP-Server direkt in Go integriert
Concurrency Skalierung über mehrere Instanzen
Disposability Schneller Start, sauberes Beenden
Dev/Prod Parity Identische Binärdatei in allen Umgebungen
Logs Ausgabe über stdout
Admin Processes Separate Ausführung von Migrationen

Fazit

Die 12-Factor App ist weit mehr als ein theoretisches Konzept – sie bietet eine praxisnahe Grundlage für nachhaltige Softwareentwicklung. Mit Go lassen sich ihre Prinzipien besonders elegant umsetzen:

  • Klare ENV-Konfiguration
  • Eigenständiges Binary ohne Laufzeit-Frameworks
  • Integrierter HTTP-Server
  • Leicht containerisierbar
  • Lieferkette unter Kontrolle

So entsteht mit wenig Code eine robuste, Cloud-native Anwendung, die sich in jeder Umgebung identisch verhält – von der lokalen Entwicklung bis zur produktiven Cloud-Plattform.


Bildnachweis: Thumbnail-Illustration inspiriert vom Go Gopher, entworfen von Renée French. Verwendet unter der CC BY 3.0-Lizenz.