Zum Inhalt springen

Betrieb mit Docker

Das Form Gateway bringt zwei Dockerfiles und eine docker-compose.yml als Beispiel mit:

DateiAnsatzWann verwenden
Dockerfile (Standard)Kopiert das fertig auf dem Host gebaute Linux-Binary in ein minimales Alpine-Runtime-ImageNormalfall: Image-Build in Sekunden, keine Toolchain im Build-Kontext, exakt dasselbe Binary wie bei allen anderen Deployment-Wegen
Dockerfile.fullBaut Frontend + Backend komplett im Container (Multi-Stage: Node LTS → Go 1.x → Alpine)Umgebungen ohne Go/Node auf dem Host; braucht Zugriff auf das private ORM++-Modul (Build-Arg/Token)

Beide erzeugen dasselbe gehärtete Runtime-Image (Alpine, non-root, Healthcheck).

Terminal-Fenster
make docker-build # Linux-Binary bauen (inkl. Audits) + Image erzeugen
docker compose up -d
docker compose logs -f # Erststart: hier steht das generierte Admin-Passwort!

make docker-build erledigt beides in einem Schritt: erst der normale, sicherheitsgeprüfte Build (npm audit → Vite → govulncheck → Cross-Compile für Linux, Architektur automatisch erkannt, überschreibbar mit DOCKER_ARCH=arm64), dann der Sekunden-schnelle Image-Build.

Danach läuft die App auf http://localhost:8080. Beim ersten Start entstehen im Host-Ordner ./data:

data/
├── config.json Konfiguration (inkl. JWT-Secret und encryption_key — sichern!)
└── gateway.db SQLite-Datenbank (+ -wal/-shm im Betrieb)

Beide Dateien liegen durch den Bind-Mount auf dem Host — einsehen, sichern, anpassen (nach Änderungen: docker compose restart).

Stoppen/Updaten:

Terminal-Fenster
docker compose down # stoppen (Daten bleiben in ./data)
make docker-build # neue Version bauen
docker compose up -d # starten

Schema-Änderungen einer neuen Version wendet ORM++ beim Start selbst an (additiv automatisch, Umbauten über die versionierten Migrationsschritte — siehe database.md).

Terminal-Fenster
make build-linux # bzw. make build-linux-arm64 auf ARM-Hosts
docker build -t form-gateway .

Oder komplett im Container: docker build -f Dockerfile.full -t form-gateway .

Container starten — mit denselben Härtungs-Optionen wie im Compose-Beispiel:

Terminal-Fenster
docker run -d \
--name form-gateway \
-p 8080:8080 \
-v "$(pwd)/data:/data" \
--read-only \
--security-opt no-new-privileges \
--cap-drop ALL \
--restart unless-stopped \
form-gateway

Admin-Passwort des Erststarts anzeigen, stoppen, entfernen:

Terminal-Fenster
docker logs form-gateway | grep -A3 Seeding
docker stop form-gateway
docker rm form-gateway # ./data bleibt erhalten

Standard-Dockerfile: ein minimales alpine:3, in das nur das auf dem Host gebaute Linux-Binary kopiert wird (bin/form-gateway-linux-<arch>; ARG TARGETARCH wählt automatisch). Sämtliche Sicherheits-Gates und die Versions-Injektion laufen im Makefile-Build — im Image landen nur CA-Zertifikate und das Binary. Zeitzonendaten sind ins Go-Binary eingebettet (time/tzdata).

Dockerfile.full: Stage 1 baut das Frontend (node:lts-alpine), Stage 2 das Go-Binary (golang:1-alpine, govulncheck, CGO_ENABLED=0), Stage 3 ist dasselbe minimale Runtime. Build-Werkzeuge und Quellcode sind im fertigen Image nicht enthalten.

Im Image (beide Dockerfiles):

MaßnahmeWirkung
apk upgrade --no-cacheaktuelle Alpine-Security-Patches zum Build-Zeitpunkt
Nur ca-certificates installiertminimale Angriffsfläche (auch nötig für TLS zu SMTP-Servern und die Reputations-Downloads)
USER app (UID 1000, nologin)Prozess läuft nie als root
CGO_ENABLED=0statisches Binary, keine libc-Schwachstellen
npm audit + govulncheck im Buildverwundbare Abhängigkeiten brechen den Build ab
HEALTHCHECK auf /api/v1/healthOrchestrierung erkennt hängende Container

Zur Laufzeit (Compose/docker run-Flags):

MaßnahmeWirkung
read_only: trueRoot-Dateisystem unveränderlich; beschreibbar nur /data
no-new-privilegeskeine Privilegien-Eskalation
cap_drop: ALLsämtliche Linux-Capabilities entfernt
Port-Mapping statt --network hosteigener Netzwerk-Namespace

TLS: Der Container spricht HTTP. Für öffentliche Deployments einen Reverse-Proxy (Caddy, Traefik, nginx) davorschalten — die Public API (/v1) und das Admin-UI laufen über denselben Port. Ausgehend braucht der Container Zugriff auf die SMTP-Server der Routen (Port 587/465) und — für den Reputations-Refresh — auf iptoasn.com und check.torproject.org.

Der Container schreibt als UID 1000. Auf den meisten Hosts funktioniert der Bind-Mount ./data:/data direkt; bei abweichender Host-UID:

Terminal-Fenster
mkdir -p data && sudo chown 1000 data

Alternativ ein Named Volume:

volumes:
- formgw-data:/data
# ...
volumes:
formgw-data:

Die normale Konfiguration passiert über data/config.json (entsteht beim Erststart). Umgebungsvariablen erlauben Container-typische Overrides:

VariableBedeutungDefault im Image
TZZeitzone (Logs, Zeitstempel), z.B. Europe/BerlinEtc/UTC
FORMGW_HOSTListen-Adresse0.0.0.0 (nötig fürs Port-Mapping)
FORMGW_PORTListen-PortWert aus config.json (8080)
FORMGW_DATADatenverzeichnis/data

Beispiel (Compose):

environment:
TZ: Europe/Berlin
FORMGW_PORT: "9000"
ports:
- "9000:9000"

PostgreSQL statt SQLite: in der data/config.json database_dsn setzen (z. B. postgres://formgw:…@db:5432/formgw) — dank ORM++ ohne weitere Änderung; die Postgres-Instanz als weiteren Compose-Service betreiben.

Das -debug-Flag funktioniert auch im Container: docker run … form-gateway -debug (Argumente nach dem Image-Namen gehen an das Binary, dank ENTRYPOINT).