In den meisten Cloud-Projekten spielt Sicherheit eine wichtige Rolle. Ein zentraler Bestandteil davon ist die sogenannte Landing Zone, die als Basis für alle weiteren Workloads dient. Ein essenzieller Sicherheitsaspekt in solchen Setups ist es, öffentlichen Zugriff (Public Access) nur in Ausnahmefällen zu gewähren. Doch genau hier taucht oft eine Herausforderung auf: Wie kann ich aus einem privaten Netzwerk heraus weiterhin Deployments mit Azure DevOps durchführen?
Ohne public Access, haben externe Tools, wie Azure DevOps, keinen Zugriff auf die Ressourcen im privaten Netzwerk.

In diesem Beitrag zeige ich Schritt für Schritt, wie dieses Problem gelöst werden kann. Nach dem Lesen solltest du in der Lage sein, eine vergleichbare Architektur selbst umzusetzen.
Ausgangslage: Kein Public Access, kein Deployment?
Wenn wir in Azure ein privates virtuelles Netzwerk (VNET) aufbauen und den Public Access sperren, bedeutet das zunächst, dass keinerlei Verbindungen aus dem Internet zu den Ressourcen zugelassen werden.
Öffentlich zugängliche Endpunkte stellen immer ein Sicherheitsrisiko dar: Sie können potenziell von Angreifern aus dem Internet erreicht werden, wodurch Angriffe wie Port-Scanning, Brute-Force-Attacken oder Exploits gegen Schwachstellen möglich werden. Besonders bei geschäftskritischen Anwendungen oder sensiblen Daten ist es daher gängige Praxis, die Angriffsfläche durch den Verzicht auf Public Access so stark wie möglich zu reduzieren.
Ein virtuelles Netzwerk (VNET) in Azure ist eine logische Isolation innerhalb der Azure-Cloud, vergleichbar mit einem eigenen Netzwerksegment in einem klassischen Rechenzentrum. In einem VNET kannst du Ressourcen wie virtuelle Maschinen (VMs), Datenbanken, App Services oder andere Dienste platzieren und diese untereinander über private IP-Adressen kommunizieren lassen.
Durch Network Security Groups (NSGs), Private Endpoints und eigene Routingregeln kann genau gesteuert werden, welcher Datenverkehr erlaubt ist und welcher nicht. Das VNET wirkt dabei wie ein Sicherheitszaun um alle enthaltenen Ressourcen.
Wenn wir konsequent auf Public Access verzichten, stehen Dienste wie Azure DevOps zunächst vor einer unüberwindbaren Hürde: Standardmäßig verwenden Pipelines Microsoft-gehostete Build Agents, die außerhalb unserer Azure-Umgebung laufen. Diese Agents können nicht einfach in unser privates VNET "hineingreifen", da sie keinen Zugriff auf die privaten IP-Adressen und keine Route ins interne Netzwerk haben.
Das führt dazu, dass Deployments in private Subnetze, interne Datenbanken oder geschützte API-Endpunkte fehlschlagen würden — obwohl genau diese Ressourcen eigentlich unser Ziel sind.
Wir brauchen also eine Möglichkeit, Deployments durchzuführen, ohne dafür den Sicherheitsansatz zu kompromittieren. Die Lösung: Wir bringen den Agent ins private VNET, also dorthin, wo auch unsere Ressourcen stehen. Auf diese Weise können wir Deployments intern abwickeln und gleichzeitig die Vorteile eines abgeschotteten Netzwerks voll ausnutzen.
Schritt 1: Self-hosted Agent im privaten VNET einrichten
Auch wenn der Agent im privaten Netzwerk sitzt, muss er sich mit Azure DevOps verbinden können, um Builds auszuführen und Artefakte herunterzuladen. Dafür müssen in der Firewall gezielt Ausnahmen zugelassen werden.
Bevor du den Agent selbst installierst, muss dein VNET entsprechend vorbereitet sein. Dafür solltest du ein eigenes Subnetz anlegen, in dem die VM für den Agent später betrieben wird. Dieses Subnetz sollte so konfiguriert sein, dass es über geeignete Sicherheitsrichtlinien verfügt und sauber vom restlichen Netzwerk getrennt ist. Eine Network Security Group (NSG) regelt hier den eingehenden und ausgehenden Verkehr. Für den self-hosted Agent ist in erster Linie der ausgehende Verkehr relevant, da er mit Azure DevOps kommunizieren muss. Eingehend wird in der Regel nur ein Verwaltungszugriff (beispielsweise per SSH oder über Azure Bastion) benötigt, damit du die VM im Fehlerfall erreichen und warten kannst.
Beim Erstellen der VM selbst ist es wichtig, eine geeignete Größe und ein passendes Image zu wählen. In vielen Fällen reicht eine mittelgroße Standard-VM, etwa der Typ Standard_D2s_v3, aus. Als Betriebssystem kannst du zwischen Windows Server oder Linux (z. B. Ubuntu) wählen, abhängig davon, welche Tools und Build-Umgebungen du benötigst. Entscheidend ist, dass du die VM direkt in das vorbereitete Subnetz einhängst und dabei keine öffentliche IP-Adresse zuweist. Damit stellst du sicher, dass die VM nur intern erreichbar ist und keine unnötigen Angriffsflächen nach außen bietet.
Damit der Agent mit Azure DevOps kommunizieren kann, muss er trotz des privaten Netzwerks bestimmte Ziele im Internet erreichen dürfen. Dafür sind gezielte Freigaben in der Firewall oder in der NSG erforderlich. Die VM benötigt Zugriff auf mindestens:
-
https://dev.azure.com
https://*.dev.azure.com
https://aex.dev.azure.com
- weitere Domains
Nur durch diese Freigaben kann der Agent Builds registrieren, Artefakte herunterladen und Statusmeldungen an Azure DevOps zurücksenden.
Schritt 2: Agent auf der VM installieren
Sobald die VM und die Firewall-Regeln stehen, kannst du den Agent in der VM installieren. Dafür lädst du das Agent-Paket von Azure DevOps herunter und registrierst den Agent über ein persönliches Zugriffstoken (PAT) oder über OAuth.
Bevor du mit der eigentlichen Installation startest, solltest du sicherstellen, dass die VM über eine funktionierende Verbindung ins Internet verfügt und die in Schritt 1 genannten URLs erreichbar sind. Außerdem muss das Betriebssystem aktuell sein, und auf Windows-Systemen sollte PowerShell installiert sein, auf Linux-Systemen eine aktuelle Shell-Umgebung. In vielen Fällen wird zusätzlich die .NET Core Runtime benötigt, insbesondere bei neueren Agent-Versionen.
Für die Authentifizierung des Agents gegenüber Azure DevOps erstellst du zunächst ein persönliches Zugriffstoken. Dieses Token wird später benötigt, damit sich der Agent mit deinem Azure DevOps Tenant verbinden kann. Du findest die Erstellung im Azure DevOps Portal unter deinem Benutzerkonto (oben rechts), dort unter Personal access tokens. Beim Anlegen des Tokens vergibst du einen Namen, wählst die gewünschte Gültigkeitsdauer und gibst die benötigten Berechtigungen an, in diesem Fall vor allem Agent Pools (Read & manage). Nach dem Erstellen kopierst du den Token und bewahrst ihn sicher auf, da er nur einmal angezeigt wird.

Im nächsten Schritt lädst du das passende Agent-Paket herunter. Dazu gehst du in Azure DevOps in die Projekteinstellungen, öffnest den Bereich Agent Pools und wählst den Pool, in dem der neue Agent erscheinen soll. Falls noch kein Pool existiert, kannst du hier auch einen neuen erstellen. Nach dem Klick auf New agent wählst du dein Betriebssystem aus und lädst das angebotene ZIP-Archiv herunter. Dieses Paket entpackst du in ein lokales Verzeichnis auf der VM, zum Beispiel C:\azagent
auf Windows oder /opt/azagent
auf Linux.

Anschließend öffnest du auf der VM eine Konsole mit administrativen Rechten, wechselst in das entpackte Verzeichnis und startest das Konfigurationsskript. Unter Windows heißt dieses Skript config.cmd
, unter Linux oder macOS config.sh
. Beim Start wirst du durch eine Reihe von Fragen geführt: Zunächst gibst du die URL deines Azure DevOps Servers an (z. B. https://dev.azure.com/mein-unternehmen
). Danach wählst du die Authentifizierungsmethode PAT aus und fügst den zuvor erstellten Token ein. Im nächsten Schritt wählst du den Agent Pool, in dem der Agent registriert werden soll, und vergibst einen Namen für den Agent selbst, etwa private-vnet-agent-01
. Optional kannst du Tags definieren, mit denen du den Agent später in Pipelines gezielt ansprechen kannst.
Nachdem der Assistent durchgelaufen ist, wirst du gefragt, ob der Agent als Dienst eingerichtet werden soll. Diese Option solltest du unbedingt bestätigen, da der Agent dadurch bei jedem Neustart der VM automatisch gestartet wird und dauerhaft verfügbar bleibt. Nach Abschluss der Konfiguration kannst du den Dienst auf Windows mit .\svc install
und .\svc start
aktivieren, auf Linux entsprechend mit sudo ./svc.sh install
und sudo ./svc.sh start
.
Wenn alle Schritte erfolgreich abgeschlossen sind, kannst du im Azure DevOps Portal prüfen, ob der Agent im ausgewählten Pool als online angezeigt wird. Ab diesem Moment ist der Agent einsatzbereit, kann Build- und Release-Jobs entgegennehmen und hat über das private VNET Zugriff auf interne Ressourcen, ohne dass diese öffentlich erreichbar sein müssen.

Schritt 3: App Registration und Service Connection erstellen
Was uns jetzt noch fehlt, ist die Authentifizierung gegenüber Azure. Denn bisher haben wir nur den Agent erstellt, der im privaten Netzwerk läuft und unsere Pipeline-Schritte lokal ausführt. Damit dieser Agent jedoch auch Ressourcen in Azure erstellen, ändern oder löschen darf — zum Beispiel eine App Service Instanz deployen oder eine Datenbank konfigurieren — braucht er Berechtigungen. Genau hier kommt die Service Connection ins Spiel.
An dieser Stelle ist es wichtig, den Unterschied zwischen Agent und Service Connection klar zu verstehen: Der Agent ist der „Arbeiter“, der die eigentliche Arbeit erledigt, also Builds, Tests und Deployments ausführt. Die Service Connection hingegen ist der „Schlüssel“, mit dem dieser Arbeiter Zugang zu Azure-Ressourcen bekommt. Ohne diesen Schlüssel könnte der Agent zwar theoretisch Befehle absetzen, hätte aber keinerlei Rechte, um etwas in Azure zu verändern.
Die Service Connection basiert auf einer sogenannten App Registration in Microsoft Entra ID (ehemals Azure AD). Diese App Registration stellt eine technische Identität bereit, die über ein Secret abgesichert wird. Sie ermöglicht es Azure DevOps, sich sicher bei deinem Azure-Tenant anzumelden, ohne dass ein persönlicher Benutzer oder unsichere Zugangsdaten verwendet werden müssen.
Um die App Registration zu erstellen, navigierst du zunächst im Azure Portal zu Microsoft Entra ID und wählst dort den Bereich App-Registrierungen. Dort legst du eine neue App Registration an, vergibst einen sprechenden Namen, zum Beispiel devops-service-connection, und registrierst die Anwendung. Die Optionen für unterstützte Kontotypen kannst du in den meisten Fällen auf „Nur dieses Organisationsverzeichnis“ belassen, ein Redirect-URI wird für diesen Anwendungsfall nicht benötigt.

Nach der Registrierung musst du ein Secret erstellen, das als Passwort für diese technische Identität dient. Unter dem Menüpunkt Zertifikate & Geheimnisse legst du ein neues Geheimnis an, vergibst eine Beschreibung und wählst eine passende Gültigkeitsdauer. Nach dem Erstellen erhältst du einen Wert (Value), den du unbedingt sofort kopieren und sicher speichern musst, da dieser später nicht erneut angezeigt wird.
Zusätzlich zu diesem Secret benötigst du zwei weitere IDs aus der App Registration: die Anwendungs-ID (Client-ID) und die Verzeichnis-ID (Tenant-ID). Beide findest du direkt in der Übersicht der App Registration. Zusammen mit dem Secret bilden sie die Zugangsdaten, die später in Azure DevOps hinterlegt werden.
Damit die App Registration auch tatsächlich Ressourcen in Azure verwalten darf, muss sie mit den entsprechenden Berechtigungen ausgestattet werden. Hierfür gehst du im Azure Portal entweder auf die gesamte Subscription oder — wenn du granularer steuern möchtest — auf die jeweilige Ressourcengruppe. Unter Zugriffskontrolle (IAM) weist du der App Registration die Rolle Mitwirkender (Contributor) zu. Diese Rolle erlaubt es, Ressourcen zu erstellen, zu ändern und zu löschen. In manchen Fällen kann es sinnvoll sein, eine restriktivere Rolle zu wählen oder sogar eine benutzerdefinierte Rolle zu definieren, je nachdem, wie genau du die Berechtigungen einschränken möchtest.
Sind die App Registration und die Berechtigungen vorbereitet, folgt die Einrichtung der Service Connection in Azure DevOps. Dazu öffnest du dein Projekt in Azure DevOps, wechselst in die Projekt-Einstellungen, dort in den Bereich Serviceverbindungen, und erstellst eine neue Verbindung vom Typ Azure Resource Manager. Wähle hier die Option Service principal (manuell).
Im nächsten Schritt trägst du alle zuvor gesicherten Informationen ein: die Subscription-ID, den Namen der Subscription, die Anwendungs-ID (Client-ID), das Secret sowie die Verzeichnis-ID (Tenant-ID). Nach dem Speichern überprüft Azure DevOps die Verbindung automatisch. Wenn alles korrekt konfiguriert ist, kannst du die Service Connection anschließend benennen, beispielsweise Azure-Prod-Connection, und speichern.
Ab diesem Zeitpunkt verfügt deine Pipeline über eine autorisierte Verbindung zu Azure.

Fazit
Durch die Verwendung eines self-hosted Agents in einem privaten VNET lässt sich eine sichere und kontrollierte Deployment-Architektur aufbauen, die ohne Public Access auskommt. Die Kombination mit einer sauber konfigurierten Service Connection über eine App Registration bietet die nötige Flexibilität für Builds und Deployments.
Auch wenn der initiale Aufwand etwas höher ist, wird dadurch langfristig ein erheblicher Sicherheits- und Stabilitätsgewinn erzielt — ein entscheidender Vorteil für Unternehmen mit hohen Sicherheitsanforderungen oder Compliance-Vorgaben.