Nix statt Docker: Entwicklungsumgebungen neu gedacht
Die zwei Gesichter von Docker
Docker hat die Art, wie wir Software deployen, grundlegend verändert. In unserem Beitrag zu Jib und Container Images haben wir beschrieben, wie wir Container-Images für unsere Spring-Boot-Services bauen, und unsere Produktionsworkloads laufen selbstverständlich als Container auf Kubernetes. Daran ändern wir nichts.
Aber Docker hat ein zweites Gesicht, das weniger mit Deployment zu tun hat: Docker Compose als lokale Entwicklungsumgebung. Datenbanken, Message Broker, Caches -- all das per docker compose up hochfahren, damit man lokal entwickeln kann. Und auch die Entwicklungswerkzeuge selbst -- Java, Gradle, Node.js -- werden oft in Devcontainern oder Docker-basierten Setups bereitgestellt, damit "bei jedem die gleiche Version läuft".
Wir haben das jahrelang so gemacht. Und dann haben wir aufgehört.
Das Problem mit Docker Compose für Development
Docker Compose für externe Services -- eine PostgreSQL-Datenbank, ein Redis, ein RabbitMQ -- funktioniert gut. Da haben wir auch nichts dagegen. Das Problem beginnt, wenn Docker Compose zur universellen Antwort auf die Frage wird: "Wie stelle ich sicher, dass alle im Team die gleiche Entwicklungsumgebung haben?"
Die Praxis sah bei uns so aus: Eine docker-compose.yml mit allen Services, dazu gelegentlich Devcontainer-Konfigurationen oder Dockerfiles für das Build-Tooling. Das funktionierte, hatte aber spürbare Nachteile.
Ressourcenverbrauch. Docker Desktop auf macOS ist nicht leichtgewichtig. Die Linux-VM, die im Hintergrund läuft, verbraucht RAM und CPU -- permanent, nicht nur während man Builds ausführt. Bei Projekten mit mehreren Services summiert sich das schnell.
Startup-Zeit. docker compose up braucht Zeit. Images müssen gepullt werden, Container müssen starten, Health-Checks müssen durchlaufen. Bei komplexeren Setups vergehen leicht 30 bis 60 Sekunden, bis man arbeiten kann.
Daemon-Abhängigkeit. Ohne laufenden Docker Daemon geht nichts. Docker Desktop muss gestartet sein, der Daemon muss laufen. Das klingt trivial, ist aber ein weiterer beweglicher Teil im Setup.
Versionspinning ist umständlich. Welche Java-Version braucht dieses Projekt? Welche Gradle-Version? Welche Node-Version für das Frontend? Das alles in Docker Compose oder Devcontainern abzubilden, erzeugt Konfigurationsaufwand, der nichts mit dem eigentlichen Projekt zu tun hat.
Nix devShells: Eine andere Antwort
Wir nutzen NixOS seit 2022 als Host-Betriebssystem für unsere Server, wie wir in unseren Beiträgen zu NixOS als Server-OS und reproduzierbaren Server-Konfigurationen mit Flakes beschrieben haben. Nix Flakes haben sich dort als Grundlage für reproduzierbare Infrastruktur bewährt. Irgendwann war der nächste Schritt naheliegend: Wenn Flakes Server-Konfigurationen reproduzierbar machen können, warum nicht auch Entwicklungsumgebungen?
Die Antwort heißt devShells. Ein Nix Flake kann neben Server-Konfigurationen auch Entwicklungsumgebungen definieren -- eine Shell mit exakt den Werkzeugen und Versionen, die ein Projekt braucht. Keine Container, kein Daemon, keine VM. Einfach eine Shell, in der die richtigen Tools verfügbar sind.
Für ein typisches Java-Projekt bei uns sieht die flake.nix so aus:
{
description = "my-service - Entwicklungsumgebung";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Java-Toolchain
temurin-bin-21
gradle
# Weitere Werkzeuge
kubectl
k9s
jq
httpie
# Datenbank-Client
postgresql_16
];
shellHook = ''
export JAVA_HOME=${pkgs.temurin-bin-21}/lib/openjdk
echo "Java: $(java --version | head -1)"
echo "Gradle: $(gradle --version | grep '^Gradle')"
'';
};
}
);
}
Ein nix develop in diesem Verzeichnis öffnet eine Shell mit Java 21, Gradle, kubectl und allen weiteren definierten Werkzeugen. Die Versionen sind über die flake.lock auf einen exakten nixpkgs-Commit gepinnt. Jeder im Team, der denselben Commit auscheckt, erhält exakt dieselben Werkzeuge in exakt denselben Versionen. Nicht ähnlich -- identisch.
direnv: Automatische Aktivierung
nix develop jedes Mal manuell ausführen, wenn man in ein Projektverzeichnis wechselt, wäre umständlich. Hier kommt direnv ins Spiel. direnv ist ein Shell-Hook, der beim Betreten eines Verzeichnisses automatisch Umgebungsvariablen setzt -- und mit der Erweiterung nix-direnv aktiviert es automatisch die passende Nix-devShell.
Die Einrichtung ist minimal. Eine .envrc-Datei im Projektstamm genügt:
# .envrc
use flake
Zwei Wörter. Wenn wir mit cd in das Projektverzeichnis wechseln, erkennt direnv die .envrc, aktiviert die devShell aus der flake.nix, und alle Werkzeuge sind verfügbar. Beim Verlassen des Verzeichnisses wird die Umgebung wieder aufgeräumt. Kein manuelles Aktivieren, kein Deaktivieren, kein "habe ich die richtige Java-Version aktiv?".
Der erste Aufruf nach einer Änderung an der flake.nix oder flake.lock braucht etwas Zeit, weil Nix die Abhängigkeiten evaluiert und gegebenenfalls herunterlädt. Danach ist die Aktivierung praktisch sofort -- nix-direnv cached das Ergebnis, und der Wechsel ins Verzeichnis fügt den Tools nur den PATH hinzu.
Vergleich: Nix devShell vs. Docker Compose für Development
| Aspekt | Docker Compose | Nix devShell + direnv |
|---|---|---|
| Daemon erforderlich | Ja (Docker Daemon) | Nein |
| Ressourcenverbrauch | Hoch (Linux-VM auf macOS) | Minimal (native Prozesse) |
| Aktivierungszeit | 30-60 Sekunden | < 1 Sekunde (nach Cache) |
| Versionspinning | Per Image-Tag, manuell | Automatisch via flake.lock |
| Reproduzierbarkeit | Abhängig von Image-Tags | Exakt via nixpkgs-Commit |
| Projekt-Isolation | Per Container | Per devShell / direnv |
| Plattformunabhängig | Ja | Linux, macOS (nativ) |
| Lernkurve | Niedrig | Mittel bis hoch |
Die Tabelle zeigt: Für Entwicklungswerkzeuge -- Compiler, Build-Tools, CLI-Utilities -- ist Nix leichter, schneller und reproduzierbarer. Docker Compose hat seine Stärke weiterhin bei Services, die als Prozess laufen müssen: Datenbanken, Message Broker, Caches. In der Praxis kombinieren wir beides: Nix für die Werkzeuge, Docker Compose für externe Services, die einen laufenden Server-Prozess brauchen.
Mehrere Projekte, verschiedene Versionen
Der größte Alltagsvorteil zeigt sich, wenn man an mehreren Projekten gleichzeitig arbeitet. Projekt A braucht Java 17 und Gradle 7, Projekt B braucht Java 21 und Gradle 8, ein Nebenprojekt braucht Node.js 20. Ohne Nix bedeutet das sdkman, nvm, asdf oder ähnliche Versionmanager -- die funktionieren, aber jeder hat seine eigenen Macken und keiner davon ist wirklich deklarativ.
Mit Nix hat jedes Projekt seine eigene flake.nix, seine eigene .envrc, und seine eigenen gepinnten Versionen. Ein cd genügt, und die richtige Umgebung ist aktiv. Kein sdk use java 17.0.9-tem, kein nvm use 20. direnv macht das automatisch, und zwar zuverlässig.
Was wir damit nicht machen
Ein wichtiger Punkt, den wir explizit machen wollen: Diese Nix-Entwicklungsumgebungen ersetzen nicht unsere Container-basierte Deployment-Pipeline. Unsere Anwendungen laufen in Produktion als Docker-Container auf Kubernetes. Jib baut die Container-Images, Helm deployt sie, K3s führt sie aus. Daran ändert Nix nichts.
Nix ersetzt auch nicht Docker Compose für laufende Services. Wenn wir lokal eine PostgreSQL-Datenbank oder einen Redis brauchen, starten wir die weiterhin per Docker Compose. Nix stellt uns den psql-Client bereit, aber nicht den Server-Prozess.
Die Trennlinie ist klar: Nix verwaltet Entwicklungswerkzeuge -- alles, was in den PATH gehört. Docker verwaltet laufende Services und Produktions-Deployments. Das sind komplementäre Werkzeuge, keine Konkurrenten.
Einstieg ins Team
Die Einführung im Team war einfacher als erwartet. Die Voraussetzungen sind überschaubar: Nix muss installiert sein (ein Einzeiler), Flakes müssen aktiviert sein, und direnv muss in der Shell eingerichtet sein. Danach funktioniert es für jeden gleich -- das ist ja der Sinn der Sache.
Der größte Widerstand kam überraschenderweise nicht von der Nix-Syntax, sondern von der Umstellung des Workflows. Wer jahrelang Java per sdkman verwaltet hat, muss sich umgewöhnen, dass die Java-Version jetzt im Repository steht und nicht mehr auf der eigenen Maschine. Aber genau das ist der Punkt: Die Entwicklungsumgebung ist Teil des Projekts, nicht eine individuelle Konfiguration auf dem Laptop des Entwicklers.
Fazit
Nix Flakes und direnv haben unsere lokale Entwicklungserfahrung deutlich verbessert. Schnellere Aktivierung, geringerer Ressourcenverbrauch, exakte Reproduzierbarkeit der Werkzeugversionen -- das sind keine abstrakten Vorteile, sondern spürbare Verbesserungen im Alltag.
Wer bereits Erfahrung mit Nix hat -- sei es durch NixOS auf dem Server oder durch andere Berührungspunkte -- sollte devShells und direnv ausprobieren. Die Einrichtung dauert eine halbe Stunde, und der Mehrwert zeigt sich beim ersten Projektwechsel. Die Lernkurve für Nix-Neulinge ist steiler, aber die Investition lohnt sich -- gerade in Teams, wo "bei mir funktioniert es" der häufigste Debug-Satz ist.
Docker bleibt ein unverzichtbares Werkzeug in unserem Stack -- für Produktion, für Container-Images, für externe Services. Aber als Grundlage für lokale Entwicklungsumgebungen hat Nix es bei uns abgelöst. Leichter, schneller, reproduzierbarer.
Geschrieben von
Patrick HütterGründer & Software-Architekt
Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.
Das könnte dich auch interessieren
Vom Manifest zur Produktion: Wie ADL, A2A und das Inference Gateway die Agenten-Infrastruktur revolutionieren
05.07.2026 · 12 Min. Lesezeit
Agent-Orchestrierung mit Java: Wie Sie LLM-Agenten produktionsreif in die JVM bringen
04.07.2026 · 6 Min. Lesezeit
Spring AI: Wie Java-Entwickler endlich KI-Features ohne Umwege integrieren
25.03.2026 · 5 Min. Lesezeit