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.
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 |
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 /eventslegt ein neues Ereignis anGET /eventsgibt 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
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{})
}
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
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
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"]
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.