Ich entwickle in letzter Zeit immer öfter mit Claude Code. Und dabei ist mir eines schnell klar geworden. Bevor der erste Prompt läuft, braucht es eine saubere Entwicklungsumgebung. Sonst wird aus dem KI-Assistenten schnell ein Chaos-Assistent bzw. ein Assistent der viele Tokens verbraucht. Soll heißen den Anfang macht das Setup und die Konfiguration meines Ubuntu Applikations-Server und ein sauberes Claude Code setup mit Skills und Agenten. In diesem Beitrag zeige ich euch, wie ich einen frischen Ubuntu-Server als Dev-Umgebung einrichte. Das Ziel ist eine Basis, auf der ich mit Claude Code und GitHub sauber und reproduzierbar arbeiten kann. Ganz egal, welches Software-Projekt ihr darauf später baut.

Warum überhaupt eine eigene Dev-Umgebung?

Ich könnte einfach auf meinem produktiven Server drauflos entwickeln. Aber das ist keine gute Idee. Claude Code führt Befehle eigenständig aus. Es installiert Pakete, legt Dateien an und startet Dienste. Das ist genau der Grund, warum ich eine klare Trennung will. Ein Fehler soll nur meine Testumgebung treffen und niemals meine produktiven Daten.

Mein Ansatz ist deshalb einfach. Ein eigener Benutzer nur für die Entwicklung. Ein eigenes Projektverzeichnis. Eine eigene Datenbank für die Tests. So bleibt alles schön getrennt. Und ich kann Claude Code arbeiten lassen, ohne dabei nervös auf meine Produktivdaten zu schielen.

Schritt 1: System aktualisieren und Grundwerkzeuge installieren

Ich starte mit einem frischen Ubuntu und bringe zuerst das System auf den neuesten Stand. Danach installiere ich die Werkzeuge, die ich später auf jeden Fall brauche.

Befehl: sudo apt update && sudo apt upgrade -y

Befehl: sudo apt install -y build-essential curl wget git ca-certificates gnupg lsb-release software-properties-common ufw

Das Paket build-essential ist wichtig. Einige Bibliotheken kompilieren beim Installieren nativen Code. Ohne die passenden Compiler brechen sie ab. Mit diesem einen Paket erspare ich mir später viel Ärger.

Schritt 2: Einen eigenen Dev-Benutzer anlegen

Jetzt kommt die wichtigste Regel für mich. Ich entwickle nie als root. Ich lege einen eigenen Benutzer an, der nur für die Entwicklung da ist. Der erste Befehl erstellt diesen Benutzer. Ubuntu fragt dabei nach einem Passwort und ein paar optionalen Angaben, die ich mit Enter überspringen kann.

Befehl: sudo adduser devuser

Mit dem nächsten Befehl gebe ich dem Benutzer das Recht, sudo zu nutzen. Das brauche ich nur für die Systempflege, etwa zum Installieren von Paketen. Das -aG steht für „add to group“. Ich füge den Benutzer also der Gruppe sudo hinzu, ohne seine anderen Gruppen zu verlieren.

Befehl: sudo usermod -aG sudo devuser

Jetzt wechsle ich in diesen neuen Benutzer. Das su steht für „switch user“. Der Bindestrich dahinter ist wichtig. Er sorgt dafür, dass ich eine frische, saubere Login-Umgebung bekomme, so als hätte ich mich direkt als devuser angemeldet.

Befehl: su - devuser

Zum Schluss lege ich mir ein Arbeitsverzeichnis für die Entwicklung an und wechsle direkt hinein. Das -p sorgt dafür, dass es keine Fehlermeldung gibt, falls der Ordner schon existiert.

Befehl: mkdir -p ~/dev && cd ~/dev

Der Gedanke dahinter ist simpel. Alles, was Claude Code tut, soll auf diesen Bereich begrenzt bleiben. Meine produktiven Dienste laufen unter einem anderen Benutzer. Für devuser sind sie nicht schreibbar. So habe ich eine klare Grenze eingezogen.

Schritt 3: Git einrichten und den SSH-Zugang zu GitHub

Ohne Git geht bei mir gar nichts. Es ist mein Sicherheitsnetz. Bei jedem Schritt sehe ich genau, was sich geändert hat. Zuerst sage ich Git, wer ich bin. Name und E-Mail landen später in jedem Commit. So ist immer nachvollziehbar, wer eine Änderung gemacht hat.

Befehl: git config --global user.name "Dein User-Name"

Befehl: git config --global user.email "du@example.com"

Mit der nächsten Zeile lege ich fest, dass mein Hauptzweig immer main heißt. Das ist heute der Standard und passt zu GitHub, das ebenfalls main verwendet.

Befehl: git config --global init.defaultBranch main

Jetzt lege ich mir bei GitHub ein privates Repository an. Die Verbindung stelle ich über einen SSH-Key her. Das ist bequemer und sicherer als ein Passwort. Der nächste Befehl erzeugt ein Schlüsselpaar. Es besteht aus zwei Teilen. Ein privater Schlüssel bleibt geheim auf dem Server. Ein öffentlicher Schlüssel darf raus und kommt gleich zu GitHub. Das -t ed25519 wählt ein modernes, sicheres Verfahren. Das -C hängt nur einen Kommentar an, damit ich den Schlüssel später wiedererkenne.

Befehl: ssh-keygen -t ed25519 -C "du@example.com"

Beim Erstellen fragt ssh-keygen nach einer Passphrase. Die ist optional. Lasse ich das Feld leer und drücke zweimal Enter, funktioniert der Schlüssel ohne Passwortabfrage. Das ist bequem und auf einem privaten Dev-Server, zu dem nur ich Zugang habe, völlig in Ordnung. Wer mehr Sicherheit möchte, vergibt eine Passphrase. Dann liegt der private Schlüssel verschlüsselt auf der Platte. Den ssh-agent lasse ich die Passphrase dann einmal pro Sitzung merken, damit sie nicht bei jedem Git-Befehl abgefragt wird.

Als Nächstes lasse ich mir den öffentlichen Schlüssel anzeigen. Der Befehl cat gibt einfach den Inhalt der Datei aus. Wichtig ist die Endung .pub. Das ist der öffentliche Teil, den ich weitergeben darf. Den privaten Schlüssel ohne .pub zeige ich niemals jemandem.

Befehl: cat ~/.ssh/id_ed25519.pub

Jetzt kommt der entscheidende Schritt, den man leicht übersieht. Ich kopiere die komplette Ausgabe. Sie beginnt mit ssh-ed25519 und endet mit meiner E-Mail. Dann gehe ich in GitHub:

  • oben rechts auf mein Profilbild,
  • dann weiter auf Settings.
  • Links im Menü wähle ich SSH and GPG keys
  • klicke anschließend auf New SSH key
  • Ich gebe dem Schlüssel einen Namen, füge den kopierten Text in das Feld Key ein und speichere mit Add SSH key.

Erst wenn der Schlüssel bei GitHub hinterlegt ist, funktioniert der Test im nächsten Schritt.

Jetzt teste ich die Verbindung. Beim ersten Mal fragt SSH, ob ich GitHub als bekannten Rechner akzeptieren will. Das bestätige ich mit yes.

Befehl: ssh -T git@github.com

Wenn alles passt, begrüßt mich GitHub mit meinem Benutzernamen. Der Zusatz „does not provide shell access“ ist dabei kein Fehler, sondern normal. Sehe ich stattdessen „Permission denied (publickey)“, dann liegt der öffentliche Schlüssel noch nicht bei GitHub. In dem Fall gehe ich einen Schritt zurück und trage ihn wie oben beschrieben ein.

Schritt 4: Ein Projekt auf GitHub anlegen

Jetzt bin ich sowieso schon bei GitHub angemeldet. Deshalb lege ich gleich mein Projekt an. Denn git clone funktioniert nur, wenn auf GitHub bereits ein Repository existiert. Ohne Projekt gibt es nichts zu holen.

Ich gehe auf GitHub oben rechts auf das Plus und wähle New repository. Dann vergebe ich einen Namen für mein Projekt. Ich stelle es auf Private, damit nicht jeder mitlesen kann. Und ich setze den Haken bei Add a README file. Das ist ein kleiner, aber wichtiger Kniff. Dadurch ist das Repository nicht leer und ich kann es im nächsten Schritt sofort klonen. Zum Schluss klicke ich auf Create repository.

Danach hole ich das frische Repository auf den Server. Ich wechsle in meinen Dev-Ordner und klone es. Die passende Adresse finde ich auf GitHub über den grünen Button Code unter dem Reiter SSH. Den Platzhalter in der URL ersetze ich natürlich durch meinen Benutzernamen und meinen Projektnamen.

Befehl: cd ~/dev

Befehl: git clone git@github.com:deinuser/mein-projekt.git

Der Klon erstellt einen neuen Unterordner mit dem Namen meines Projekts. Bevor ich weiterarbeite, wechsle ich zuerst in diesen Ordner. Das ist wichtig, denn alle Git-Befehle funktionieren nur innerhalb des Projektordners. Den Namen passe ich an mein Projekt an.

Befehl: cd mein-projekt

Jetzt lege ich meine Dateien in diesen Ordner. Änderungen sichere ich dann immer mit denselben drei Schritten. Erst git add für die Auswahl, dann git commit für den Schnappschuss mit Kommentar, dann git push zum Hochladen zu GitHub.

Befehl: git add .

Befehl: git commit -m "Projektstart"

Befehl: git push

Es gibt auch den umgekehrten Weg. Wer schon lokal Dateien auf seinem Rechner hat, der legt auf GitHub ein leeres Repository ohne README an. Danach verbindet er seinen lokalen Ordner damit und schiebt alles hoch. Die Befehle dafür sehen so aus.

Befehl: git init

Befehl: git add .

Befehl: git commit -m "Projektstart"

Befehl: git branch -M main

Befehl: git remote add origin git@github.com:deinuser/mein-projekt.git

Befehl: git push -u origin main

Beide Wege führen zum selben Ergebnis. Ich nehme meist den ersten, weil er weniger Fehlerquellen hat und ich direkt auf GitHub so meine Repositories verwalten kann.

Schritt 5: Docker für Datenbanken und Dienste

Fast jedes Projekt braucht eine Datenbank. Ich installiere sie aber nicht direkt auf dem System. Ich nutze dafür Docker. Das hält den Server sauber. Und ich kann eine Datenbank mit einem Befehl starten und wieder wegwerfen. Zuerst lade ich das offizielle Installationsskript herunter. Das -o speichert es unter dem angegebenen Dateinamen.

Befehl: curl -fsSL https://get.docker.com -o get-docker.sh

Dann führe ich das Skript aus. Es erkennt meine Ubuntu-Version von allein und installiert Docker samt allen nötigen Komponenten.

Befehl: sudo sh get-docker.sh

Damit ich Docker ohne sudo nutzen kann, füge ich meinen Benutzer der Gruppe docker hinzu. Das spart mir bei jedem Docker-Befehl das Voranstellen von sudo.

Befehl: sudo usermod -aG docker $USER

Die neue Gruppe wird erst nach einem neuen Login aktiv. Mit newgrp aktiviere ich sie sofort in der aktuellen Sitzung, ohne mich neu anmelden zu müssen.

Befehl: newgrp docker

Zum Schluss ein kleiner Test. Dieser Befehl lädt ein winziges Test-Image und startet es. Das --rm räumt den Container gleich wieder weg.

Befehl: docker run --rm hello-world

Wenn die Begrüßung von Docker erscheint, läuft alles. Docker Compose ist heute schon als Plugin dabei. Ich muss es nicht mehr getrennt installieren.

Schritt 6: Python und uv einrichten

Ich arbeite in meinen Projekten meistens mit Python. Als Paketmanager nutze ich uv. Es ist schnell und es verwaltet die virtuelle Umgebung von ganz allein. Wer eine andere Sprache nutzt, ersetzt diesen Schritt einfach durch das passende Werkzeug.

Zuerst prüfe ich, welche Python-Version auf dem Server liegt. Ich brauche mindestens Python 3.12. Der folgende Befehl zeigt mir die installierte Version an.

Befehl: python3 --version

Zeigt die Ausgabe 3.12 oder höher, bin ich fein raus und springe zum nächsten Absatz. Ist die Version älter, etwa bei einem frischen Ubuntu 22.04, dann installiere ich Python 3.12 nach. Der erste Befehl fügt die passende Paketquelle hinzu, der zweite installiert Python.

Befehl: sudo add-apt-repository ppa:deadsnakes/ppa -y && sudo apt update

Befehl: sudo apt install -y python3.12 python3.12-venv python3.12-dev

Jetzt installiere ich uv. Der Befehl lädt ein Installationsskript und führt es direkt aus.

Befehl: curl -LsSf https://astral.sh/uv/install.sh | sh

Das Installationsskript legt uv ab und ergänzt den Suchpfad. Damit meine aktuelle Sitzung den neuen Pfad kennt, lade ich die Shell-Konfiguration einmal neu. Danach prüfe ich mit der Versionsabfrage, ob die Installation geklappt hat.

Befehl: source ~/.bashrc

Befehl: uv --version

Bei mir wurde die uv-Version wie folgt angezeigt:

devuser@OptiPlex-5040:~/dev$ uv --version
uv 0.11.28 (x86_64-unknown-linux-gnu)

An dieser Stelle kommt eine Frage auf, die ich oft höre. Brauche ich noch eine virtuelle Umgebung? Meine Antwort ist ein klares Ja. Und zwar genau eine pro Projekt. Der Vorteil liegt auf der Hand. Die Abhängigkeiten meines Projekts landen nicht im System-Python. So bleibt das System sauber und Versionskonflikte bleiben mir erspart.

Das Schöne an uv ist, dass ich die Umgebung nicht von Hand anlegen muss. Ich wechsle zuerst in meinen Projektordner. Dann initialisiere ich das Projekt. Der Befehl uv init erstellt die Datei pyproject.toml. Das ist die zentrale Datei, in der später alle Abhängigkeiten stehen.

Befehl: cd ~/dev/mein-projekt

Befehl: uv init

Jetzt lege ich die virtuelle Umgebung an und installiere alles, was in der pyproject.toml steht. Genau das macht uv sync. Beim ersten Aufruf entsteht dabei automatisch ein Ordner .venv im Projekt.

Befehl: uv sync

Ab jetzt führe ich Python-Befehle mit uv run aus. Dann nutzt uv automatisch die virtuelle Umgebung. Ein manuelles Aktivieren ist nicht nötig. Zur Kontrolle lasse ich mir die Python-Version aus der Umgebung anzeigen.

Befehl: uv run python --version

Der Ordner .venv gehört übrigens in die .gitignore. Die Umgebung selbst wird nie eingecheckt. Ich sichere nur die Datei pyproject.toml und die Lock-Datei. Damit kann ich die Umgebung überall exakt gleich wieder aufbauen. Habe ich später ein bestehendes Projekt geklont, in dem die pyproject.toml schon liegt, überspringe ich uv init und rufe direkt uv sync auf.

Mit dem folgenden Befehl kannst Du Dir auch den versteckten Ornder .env anzeigen lassen.

Befehl: ls -a

Schritt 7: Claude Code installieren

Jetzt kommt der eigentliche Star, unser agentischer Claude Code Client. Anthropic empfiehlt inzwischen den nativen Installer. Der Vorteil ist groß. Ich brauche kein Node.js mehr und der Installer hält sich selbst aktuell.

Eine Frage vorweg, die sich schnell stellt. In welchem Verzeichnis führe ich den Installer aus? Die Antwort ist einfach. Das ist egal. Der Installer richtet Claude Code einmal für meinen Benutzer ein und legt es unter ~/.local/bin ab. Es landet also nicht im Projekt. Ich kann den Befehl daher von überall ausführen, zum Beispiel aus meinem Home-Verzeichnis. Erst später starte ich Claude Code bewusst im Projektordner. Der erste Befehl lädt den Installer und führt ihn aus.

Befehl: curl -fsSL https://claude.ai/install.sh | bash

Der Installer legt das Programm unter ~/.local/bin ab und trägt diesen Pfad normalerweise dauerhaft in meine Shell-Konfiguration ein. Damit die aktuelle Sitzung den Pfad sofort kennt, lade ich die Konfiguration einmal neu.

Befehl: source ~/.bashrc

Dann prüfe ich, ob die Installation geklappt hat.

Befehl: claude --version

Bei mir hat alles geklappt und der letzte Befehl hat folgende Version ausgegeben.

devuser@OptiPlex-5040:~$ claude --version
2.1.207 (Claude Code)

Ein kleiner Stolperstein taucht hier manchmal auf. Der Befehl claude wird nicht gefunden. Dann hat der Installer den Pfad nicht eingetragen. In dem Fall hänge ich ihn einmalig selbst an meine Shell-Konfiguration an und lade sie neu. Danach findet die Shell den Befehl dauerhaft.

Befehl: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

Befehl: source ~/.bashrc

Node.js brauche ich nur noch, wenn ich später MCP-Server einbinden möchte. Für den Start lasse ich es weg.

Schritt 8: Claude Code mit dem Konto verbinden

Auf einem Server arbeite ich meistens über SSH. Ein Browser ist da nicht immer zur Hand. Dafür gibt es zwei Wege. Der erste Weg ist die Anmeldung über mein Claude-Abo. Ich starte einfach claude in meinem Projektordner und folge der Anmeldung, die mich durch den Login führt.

Befehl: claude

Nach dem ich mich über den Browser-Link authentifiziert habe war ich im Claude Code Client erfolgreich angemeldet.

<Bild einfügen>

Der zweite Weg ist praktisch für reine SSH-Server. Ich hinterlege einen API-Key als Umgebungsvariable. Dann läuft die Anmeldung ganz ohne Browser.

Befehl: echo 'export ANTHROPIC_API_KEY="sk-ant-..."' >> ~/.bashrc

Befehl: source ~/.bashrc

Befehl: claude doctor

Der Befehl claude doctor ist mein Freund. Er prüft die Installation, die Anmeldung und die Konfiguration. So sehe ich sofort, ob alles passt. Wer lieber im Editor bleibt, nutzt VS Code mit Remote-SSH. Dann editiere ich lokal und alles läuft trotzdem auf dem Server.

Ubuntu & Claude Code

Ubuntu & Claude Code

Schritt 9: Die CLAUDE.md als Spielregel

Ein Tipp, der bei mir den größten Unterschied macht. Ich lege im Projekt eine Datei namens CLAUDE.md an. Diese Datei liest Claude Code bei jeder Sitzung automatisch. Hier stehen meine Regeln. Also welche Werkzeuge ich nutze, wie die Befehle heißen und was auf keinen Fall passieren darf.

Ohne diese Datei rät der Assistent. Mit dieser Datei hält er sich an meine Konventionen. Und zwar von der ersten Nachricht an. Ich halte sie bewusst kurz. Lange Erklärungen gehören in eine separate Doku. Die CLAUDE.md ist nur der Steckbrief für das Projekt.

Schritt 10: Firewall und ein bisschen Härtung

Bevor ich richtig loslege, sichere ich den Server noch ein wenig ab. Der erste Befehl erlaubt den SSH-Zugang. Das ist wichtig, damit ich mich nicht selbst aussperre, sobald die Firewall aktiv ist.

Befehl: sudo ufw allow OpenSSH

Dann schalte ich die Firewall ein. Ab jetzt sind nur noch die Ports offen, die ich ausdrücklich erlaubt habe.

Befehl: sudo ufw enable

Zum Schluss lasse ich mir den Status anzeigen. So sehe ich auf einen Blick, welche Regeln aktiv sind.

Befehl: sudo ufw status

Meine Datenbank läuft nur im Docker-Netz und auf localhost. Den Port öffne ich nicht nach außen. Wenn ich später eine Weboberfläche brauche, gebe ich einzelne Ports gezielt frei. Am liebsten über einen SSH-Tunnel.

Meine Checkliste vor dem ersten Prompt

Bevor ich Claude Code das erste Mal richtig arbeiten lasse, gehe ich eine kurze Liste durch. Wenn alle Punkte grün sind, ist der Server startklar.

  • Python ist vorhanden und aktuell
  • uv läuft
  • Docker läuft ohne sudo
  • Das Repo steht und der erste Commit ist gemacht
  • claude doctor meldet keine Fehler
  • Die CLAUDE.md liegt im Projekt

Fazit

Diese Einrichtung kostet mich am Anfang etwa eine halbe Stunde. Aber sie zahlt sich sofort aus. Ich habe eine saubere Trennung zwischen Entwicklung und Produktion. Ich habe Git als Sicherheitsnetz. Und ich habe mit der CLAUDE.md eine klare Spielregel für meinen KI-Assistenten. Auf dieser Basis kann ich jedes Projekt starten. Der Assistent arbeitet dann nicht wild drauflos, sondern innerhalb meiner Leitplanken.

Im nächsten Schritt geht es bei mir an das eigentliche Projekt. Ich gebe Claude Code die erste Aufgabe und lasse den Code entstehen. Aber das ist ein Thema für einen eigenen Beitrag. Schreibt mir gern in die Kommentare, wie ihr eure Dev-Umgebung aufsetzt. Ich bin gespannt, welche Kniffe ihr nutzt.