CI/CD auf dem eigenen VPS
Ein eigener Build-Server lohnt sich, wenn die Cloud-Minuten nicht reichen, die Builds langsam sind oder Sie Zugriff auf interne Ressourcen brauchen. Ein Runner führt jedoch beliebigen Code aus dem Repository aus, und ein Fehler in seiner Konfiguration öffnet den Zugang zum Produktivserver und zu allen Secrets. Hier lesen Sie, wie die Runner von GitLab und GitHub funktionieren, welche Ressourcen sie brauchen, wie Sie per SSH deployen und wo Sie Schlüssel aufbewahren.
CI/CD bedeutet, dass Code nach jeder Änderung automatisch gebaut, geprüft und ausgerollt wird. Die Beschreibung der Pipeline liegt im Repository (.gitlab-ci.yml oder Dateien in .github/workflows/), ausgeführt wird sie von einem Runner: einem Programm, das Jobs von GitLab oder GitHub abholt und startet. Den Runner können Sie vom Dienst nutzen oder auf Ihrem eigenen VPS installieren.
Eigene Runner: GitLab und GitHub
GitLab Runner wird aus dem offiziellen Repository installiert (Installationsanleitung) und mit einem Token aus den CI/CD-Einstellungen in einem Projekt oder einer Gruppe registriert. Die wichtigste Entscheidung bei der Registrierung ist der Executor. shell führt die Befehle direkt im System des Servers aus, docker für jeden Job in einem neuen Container. Laut GitLab-Dokumentation hat der Shell-Executor keine saubere Umgebung pro Build, und die Jobs sehen das gesamte Dateisystem einschließlich des Runner-Tokens und fremder Caches. Für die meisten Projekte ist der Docker-Executor die richtige Wahl.
Einen eigenen Runner für GitHub Actions fügen Sie in den Einstellungen des Repositorys oder der Organisation hinzu (GitHub-Dokumentation). Die Nutzung ist kostenlos, Sie bezahlen nur den Server. Der Runner verbindet sich selbst per HTTPS über Port 443 mit GitHub, eingehende Ports müssen Sie auf dem Server nicht öffnen. Wurde ein Runner 30 Tage lang nicht aktualisiert, erhält er von GitHub keine Jobs mehr (Referenz zu eigenen Runnern).
Eine wichtige Einschränkung für Open-Source-Projekte: Laut GitHub sollten eigene Runner fast nie mit öffentlichen Repositorys verwendet werden, denn jeder kann einen Pull Request öffnen und auf dem Server eigenen Code ausführen. Für öffentlichen Code bleiben Sie bei den Cloud-Runnern und binden den eigenen Server an private Repositorys an.
Warum Build und Produktivserver getrennt sein sollten
- Last. Ein Frontend-Build oder eine Kompilierung belegt minutenlang alle Kerne. Läuft der Runner auf dem Server der Website, antwortet die Website in dieser Zeit langsamer.
- Sicherheit. Der Runner führt Code aus jedem Branch aus, auch aus einem, den ein externer Dienstleister eingereicht hat. Laut GitLab-Dokumentation stellt der Shell-Executor ein hohes Risiko für Server und Netzwerk dar, und ein Container im privileged-Modus gibt dem Job volle Root-Rechte auf dem Server. Läuft auf demselben Server eine Website mit Kundendatenbank, ist sie für jeden Job zugänglich.
- Speicherplatz. Docker-Images, Abhängigkeits-Caches und Artefakte belegen Dutzende Gigabyte. Ist die Festplatte voll, steht nicht nur der Build still, sondern auch die Website.
Ein Schema für ein kleines Team: ein VPS für den Runner, ein separater für die produktive Website und bei Bedarf ein dritter für die Testumgebung (dazu der Artikel VPS für Entwickler: Testumgebungen). Der Runner liefert auf den Produktivserver nur das fertige Ergebnis.
Wie viele Ressourcen ein Build braucht
Als Maßstab dienen die Cloud-Runner von GitHub. Laut Referenz hat der Standard-Linux-Runner für private Repositorys 2 Kerne, 8 GB Arbeitsspeicher und 14 GB SSD, für öffentliche 4 Kerne und 16 GB. Kommen Ihre Builds mit diesen Ressourcen aus, schafft ein gleich ausgestatteter VPS sie nicht schlechter, mit schneller Festplatte und erhaltenem Cache meist sogar schneller.
- Kerne. Ein Build mit paralleler Kompilierung und Tests nutzt alle Kerne. Laufen die Builds nacheinander, verkürzen 4 Kerne die Warteschlange gegenüber zwei deutlich.
- Arbeitsspeicher. Ein Frontend-Build, die Kompilierung von Java oder Go und Tests mit einer Datenbank im Container brauchen 4–8 GB pro Job. Die Zahl gleichzeitiger Jobs legt der Parameter
concurrentin derconfig.tomlvon GitLab Runner fest (Beschreibung der Parameter); multiplizieren Sie den Speicherbedarf eines Jobs mit dieser Zahl. - Festplatte. Die Installation von Abhängigkeiten bedeutet Tausende kleiner Dateien, auf NVMe geht das schneller. Nehmen Sie mindestens 50 GB und leeren Sie den Docker-Cache regelmäßig per Zeitplan:
docker builder pruneunddocker image prune. Warum die Festplatte zählt, erklärt der Artikel NVMe oder SSD: Gibt es einen Unterschied für die Website?. - Virtualisierung. Der Docker-Executor braucht KVM: Unter OpenVZ läuft Docker nur eingeschränkt oder gar nicht (KVM oder OpenVZ: was Sie wählen sollten).
Laut Katalog von accsly.io mit Stand November 2026 kostet ein VPS-Tarif mit 2 Kernen und 4 GB Arbeitsspeicher im Marktmedian etwa 18,50 € im Monat, ein Viertel dieser Tarife liegt unter 11 €. Mit 4 Kernen und 8 GB sind es etwa 35,60 €, ein Viertel unter 20 €. Mit 8 Kernen und 16 GB etwa 71 € (aus Rubel umgerechnet und gerundet). Passende Tarife sammeln die Auswahlen VPS für CI/CD-Runner und, wenn Sie auch GitLab selbst betreiben, VPS für GitLab.
Ein Build-Server nach Stunden
Der Runner wird nur gebraucht, solange Builds laufen, meist einige Stunden pro Arbeitstag. Laut Katalog von accsly.io mit Stand November 2026 bieten 173 von 2.248 Hostern mit VPS-Tarifen stündliche Abrechnung, 298 eine API zum Anlegen und Löschen von Servern und 96 beides. Ein Tarif mit 4 Kernen und 8 GB kostet bei Hostern mit stündlicher Abrechnung im Marktmedian etwa 45 € im Monat, also rund 0,06 € pro Stunde. Ein Server, der an jedem der 22 Arbeitstage 3 Stunden läuft, kostet damit etwa 4,10 € statt des Monatspreises.
Damit das ohne Handarbeit funktioniert, wird der Server vor dem Build über die API angelegt und danach gelöscht. GitLab Runner kann mit Plugins für Cloud-Anbieter auf diese Weise skalieren, GitHub empfiehlt kurzlebige (ephemeral) Runner, die genau einen Job übernehmen und danach entfernt werden. Ein Plus für die Sicherheit: Der nächste Job sieht keine Spuren des vorherigen. Wann stündliche Abrechnung günstiger ist als monatliche, erklärt der Artikel Stündliche Abrechnung: wann sie sich lohnt; Tarife finden Sie in den Auswahlen VPS mit stündlicher Abrechnung und VPS mit API.
Deployment per SSH mit eingeschränktem Schlüssel
Am einfachsten bringen Sie einen Build per SSH auf den Produktivserver: mit rsync der Dateien oder einem Befehl, der die neue Version abholt und den Dienst neu startet. Damit der Schlüssel aus der CI nicht zum Schlüssel für den ganzen Server wird:
- Eigener Benutzer. Legen Sie einen Benutzer
deployohne sudo an, der nur Rechte auf das Verzeichnis der Website hat. Muss er einen Dienst neu starten, erlauben Sie in der sudoers-Datei genau diesen einen Befehl. - Eigener Schlüssel. Erzeugen Sie ein Schlüsselpaar nur für die CI und verwenden Sie es nirgendwo sonst.
- Einschränkungen in authorized_keys. Schreiben Sie vor den Schlüssel
restrict, das Portweiterleitung und Terminal verbietet, undfrom="Adresse des Runners", damit der Schlüssel nur vom Build-Server aus funktioniert. Der Parametercommand="…"erlaubt dem Schlüssel, nur ein einziges Deployment-Skript zu starten (man authorized_keys). - Prüfung des Servers. Speichern Sie den Fingerabdruck des Host-Schlüssels des Produktivservers in einer CI-Variable und schreiben Sie ihn vor der Verbindung in
known_hosts. Die Prüfung mitStrictHostKeyChecking=noabzuschalten ist keine Option: Dann verbindet sich der Runner mit jedem Server, der sich als Ihrer ausgibt.
Damit die Website während des Deployments erreichbar bleibt, startet man die neue Version neben der alten und schaltet den Verkehr erst um, wenn sie den Health-Check besteht; die vorherige Version bleibt für ein Rollback erhalten. Wie das auf einem einzelnen Server funktioniert, beschreibt der Artikel Server ohne Ausfallzeit aktualisieren.
Den Grundschutz des Produktivservers selbst, also Anmeldung nur per Schlüssel, Firewall und fail2ban, beschreibt der Artikel Die ersten 10 Schritte nach dem VPS-Kauf: Sicherheit. Die Einträge über Anmeldungen mit dem Deployment-Schlüssel helfen, wenn etwas schiefgeht; wo Sie sie finden, erklärt der Artikel Logs und Login-Audit auf dem Server.
Wo Sie Secrets aufbewahren
- Nicht im Repository. Datenbankpasswörter, API-Tokens und Deployment-Schlüssel gehören in CI-Variablen. In GitLab kann eine Variable geschützt sein, dann steht sie nur Pipelines geschützter Branches und Tags zur Verfügung, und maskiert, dann wird ihr Wert im Job-Log durch
[MASKED]ersetzt. Ein maskierter Wert muss eine einzelne Zeile ohne Leerzeichen und mindestens 8 Zeichen lang sein (GitLab-Dokumentation). - Ein Wert pro Secret. GitHub weist darauf hin, dass ein Secret im JSON- oder YAML-Format in Logs schlechter verborgen wird; speichern Sie jeden Wert einzeln und geben Sie Secrets nie im Build-Log aus.
- Getrennte Secrets für Testumgebung und Produktivserver. Der Deployment-Schlüssel für den Produktivserver darf nur aus einem geschützten Branch erreichbar sein, nicht aus jedem beliebigen Entwickler-Branch.
- Kurzlebige Tokens statt dauerhafter. Für Cloud-Dienste empfiehlt GitHub OpenID Connect: Der Job erhält ein Token für die Dauer des Builds, einen dauerhaften Schlüssel müssen Sie nicht speichern.
- Rotation. Wechseln Sie die Schlüssel, wenn jemand mit Zugriff auf die CI-Einstellungen das Team verlässt, und nach jedem Leck eines Build-Logs.
Die Datei .env mit den Einstellungen des Produktivservers wird dort einmal mit den Rechten 600 angelegt, und das Deployment überschreibt sie nicht. Dann enthält selbst eine vollständige Kopie des Repositorys keine Passwörter für die Produktivdatenbank.