Inhalt
Getestet mit
- Portainer CE 2.45.2
- Docker Engine 29.9.0
- Docker Compose 5.6.0
- Debian 13
Technisch verifiziert am
Reality Check
Stand: Oktober 2026
Kurz gesagt: Ein Stack ist in Portainer eine Compose-Datei, die als Einheit deployt wird. Du legst ihn unter Stacks → Add stack an, fügst die YAML im Web editor ein, trägst Variablen ein und klickst auf Deploy the stack. Updates laufen über eine geänderte Image-Version und Update the stack; ein echtes Rollback deiner Daten gibt es dabei nicht, das musst du über Backups lösen.
Sobald ein Dienst mehr als einen Container, ein Volume oder ein paar Variablen braucht, wird das Anlegen über einzelne Formulare unübersichtlich. Stacks lösen das: Die komplette Konfiguration steht in einer Datei, die du lesen, kopieren und später in Git ablegen kannst. Dieser Guide richtet sich an alle, die Portainer schon installiert haben und jetzt den ersten Dienst sauber als Stack betreiben wollen. Die Oberflächenschritte folgen der offiziellen Portainer-Doku und der Oberfläche von Portainer CE 2.45.2; Deployment, Variablen und die Ablage der Dateien wurden mit Portainer CE 2.45.2 auf Docker Engine 29.9.0 geprüft.
Voraussetzungen #
- Portainer CE läuft und du kannst dich anmelden (Portainer installieren).
- Das lokale Docker-Environment ist verbunden.
- Grundbegriffe wie Image, Container und Port kennst du (siehe Docker-Grundlagen); wie Portainer einzelne Container verwaltet, zeigt Container in Portainer verwalten.
- Für den optionalen Test auf der Kommandozeile: Shell-Zugang zum Docker-Host.
Lernziel: Du kannst eine Compose-Datei lesen, sie als Stack deployen, Variablen sinnvoll setzen, den Stack aktualisieren und weißt, wo die Grenzen beim Zurückrollen liegen.
Was ein Stack in Portainer ist #
Hinter einem Stack steckt Docker Compose, das Werkzeug, das mehrere Container aus einer YAML-Datei beschreibt und gemeinsam startet. YAML ist ein Textformat, in dem die Einrückung die Struktur bestimmt. Portainer liefert dafür einen Editor und eine Verwaltungsansicht; die eigentliche Arbeit macht Compose auf dem Docker-Host.
Im Test mit Portainer CE 2.45.2 war das gut nachzuvollziehen:
- Der Stackname wird zum Compose-Projektnamen. Jeder Container trägt das
Label
com.docker.compose.projectmit diesem Namen, und Volumes sowie das Standardnetzwerk bekommen ihn als Präfix. - Portainer legt die Compose-Datei in seinem Datenvolume unter
/data/compose/<Stack-ID>/docker-compose.ymlab. - Stack-Variablen landen daneben in
stack.env, und zwar im Klartext.
Daraus folgt eine praktische Regel: Wähle Stacknamen kurz, klein geschrieben
und ohne Leerzeichen, etwa monitoring oder uptime-kuma. Sie tauchen in
jedem Container-, Volume- und Netzwerknamen wieder auf.
Eine Compose-Datei lesen #
Das Beispiel für diesen Guide ist Uptime Kuma, ein schlanker Dienst, der Webseiten und Server überwacht. Er braucht genau einen Container, ein Volume für seine Daten und einen Port.
services:
uptime-kuma:
image: louislam/uptime-kuma:2.5.6
restart: unless-stopped
ports:
- "3001:3001"
environment:
TZ: ${TZ:-Europe/Berlin}
volumes:
- kuma-data:/app/data
volumes:
kuma-data:Von oben nach unten gelesen:
services:listet die Dienste. Hier gibt es einen, er heißtuptime-kuma. Unter diesem Namen erreichen ihn auch andere Container im selben Stack.image:nennt das Image mit fester Version. Ein Release-Tag wie2.5.6wird vom Herausgeber üblicherweise nicht mehr verändert;latestdagegen kann morgen auf eine andere Version zeigen. Die Version2.5.6war zum Testzeitpunkt die neueste stabile Version auf Docker Hub.restart: unless-stoppedstartet den Container nach einem Absturz oder Neustart des Hosts wieder, außer du hast ihn bewusst gestoppt.ports:veröffentlicht einen Port nach dem MusterHost:Container. Der Dienst lauscht im Container auf 3001 und ist auf dem Host ebenfalls unter 3001 erreichbar – auf allen Adressen des Hosts und an ufw und firewalld vorbei. Soll Uptime Kuma nur im LAN lauschen, schreibst du"192.168.10.20:3001:3001"(Beispieladresse); warum das keine Firewall ersetzt, erklären die Docker-Grundlagen.environment:setzt Umgebungsvariablen im Container.${TZ:-Europe/Berlin}heißt: Nimm den Wert der VariableTZ, und wenn sie fehlt oder leer ist,Europe/Berlin.volumes:beim Dienst bindet das benannte Volumekuma-datanach/app/data, wo Uptime Kuma seine Datenbank speichert. Der Blockvolumes:ganz unten legt dieses Volume an.
Ein version:-Feld am Anfang brauchst du nicht. Compose 5.6.0 ignorierte es
im Test und gab nur die Warnung aus, das Attribut sei überholt. Eigene Dateien kannst du mit dem
Docker-Compose-Generator zusammenstellen
und dann hier einfügen.
Den Stack im Web-Editor anlegen #
- Öffne in Portainer das Environment, links Stacks, dann Add stack.
- Gib einen Namen ein, zum Beispiel
uptime-kuma. - Wähle als Methode Web editor und füge die YAML von oben ein.
- Optional: Unter Environment variables trägst du
TZmit einem anderen Wert ein. Ohne Eintrag greift der Standard aus der Datei. - Klicke auf Deploy the stack.
Portainer legt Volume, Netzwerk und Container an. Das Image wird beim ersten Mal heruntergeladen, das kann je nach Leitung einen Moment dauern.
Ergebnis prüfen #
Auf dem Docker-Host zeigt docker ps, ob der Container läuft und welchen
Gesundheitsstatus er meldet:
docker ps --filter name=uptime-kuma --format '{{.Names}} {{.Status}} {{.Ports}}'Ein Stack namens uptime-kuma ergibt etwa diese Ausgabe:
uptime-kuma-uptime-kuma-1 Up 18 seconds (healthy) 0.0.0.0:3001->3001/tcp, [::]:3001->3001/tcpDas (healthy) stammt von einem Healthcheck, also einem Befehl, den
Docker regelmäßig im Container ausführt, um zu prüfen, ob der Dienst wirklich
antwortet und nicht nur der Prozess läuft. Uptime Kuma bringt diesen Check im
Image selbst mit; du musst ihn nicht in die YAML schreiben. Im Test stand der
Status nach wenigen Sekunden von starting auf healthy.
Danach öffnest du http://docker01.example.lan:3001 im Browser. Version 2
leitet beim ersten Aufruf auf eine Einrichtungsseite um, auf der du die
Datenbank wählst; im Test antwortete der Dienst mit einer Weiterleitung auf
/setup-database.
Dieselbe Datei auf der Kommandozeile testen #
Die YAML im Web-Editor ist eine normale Compose-Datei. Willst du sie vorher
prüfen, speicherst du sie auf dem Host als compose.yaml und lässt Compose die
aufgelöste Konfiguration ausgeben:
mkdir -p ~/stack-test && cd ~/stack-test
nano compose.yaml
docker compose -p stacktest configdocker compose config meldet Einrückungsfehler sofort und zeigt, welche
Werte nach dem Einsetzen der Variablen tatsächlich verwendet werden. Mit
docker compose -p stacktest up -d startest du den Test, mit
docker compose -p stacktest down -v räumst du ihn wieder ab; das -v
löscht dabei auch das Volume samt Daten, also nur bei Testdaten verwenden.
Genau dieses Beispiel lief so mit Docker Engine 29.9.0 und Compose 5.6.0.
Upload und Git als Alternativen #
Neben dem Web-Editor bietet Add stack weitere Wege an:
- Upload: Du wählst eine Compose-Datei von deinem Rechner. Das Ergebnis entspricht dem Web-Editor, nur ohne Tippen.
- Repository: Portainer holt die Datei aus einem Git-Repository. Im Abschnitt „Git repository“ wählst du unter Source eine vorhandene Quelle (Select a source) oder legst mit Create new source eine neue an. Dazu kommen Repository reference (Branch oder Tag; ohne Angabe nimmt Portainer den Standard-Branch) und Compose path, der Pfad zur Datei im Repository. Spätere Änderungen machst du dann im Repository, nicht mehr in Portainer. Wie das mit Webhooks und Updates zusammenspielt, steht im Guide zu Git-Stacks (siehe Weiterlesen).
- Custom template: Deployment aus einer selbst angelegten Vorlage; für den Einstieg nicht nötig.
Für den Einstieg reicht der Web-Editor. Sobald du mehrere Stacks pflegst, lohnt sich Git, weil du dann eine Historie aller Änderungen hast.
Umgebungsvariablen im Stack #
Variablen trennen die Datei von den Werten, die sich je nach Host unterscheiden: Ports, Zeitzonen, Domains, Passwörter. Im Formular kannst du sie einzeln eintragen oder mit Load variables from .env file aus einer Datei übernehmen.
Zwei Wege, wie sie im Container ankommen:
- Einsetzen in die YAML: Steht
${TZ:-Europe/Berlin}in der Datei, setzt Compose den Wert beim Deployment ein. Im Test mit Portainer CE 2.45.2 wurde ein solcher Platzhalter aus der Stack-Variable befüllt. - Als Datei: Mit
env_file: stack.envbeim Dienst gibt Portainer alle Stack-Variablen als Umgebungsvariablen in den Container. Laut Doku funktioniert das auf Docker Standalone, nicht unter Swarm. Im Test legte Portainerstack.envan, sobald Stack-Variablen gesetzt waren; ob die Datei auch ohne Variablen entsteht, wurde nicht getestet – lege im Zweifel mindestens eine Variable an.
services:
app:
image: traefik/whoami:v1.11
env_file:
- stack.envFür Pflichtwerte gibt es eine nützliche Schreibweise:
${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt} bricht das Deployment mit
dieser Meldung ab, wenn die Variable leer ist. So startet keine Datenbank
versehentlich ohne Passwort.
Wichtig für die Sicherheit: Stack-Variablen sind kein Tresor. Portainer
speichert sie im Klartext in stack.env, und sie stehen auch im
Portainer-Backup – lesbar für jeden, solange du das Backup nicht mit einem
Passwort verschlüsselst.
Für Passwörter bietet Compose deshalb Secrets: Der Wert liegt in einer
Datei auf dem Host und erscheint im Container als Datei unter
/run/secrets/<name> statt als Umgebungsvariable. Viele Images lesen solche
Dateien über Variablen mit der Endung _FILE, etwa POSTGRES_PASSWORD_FILE.
In Portainer brauchst du dafür einen absoluten Host-Pfad:
services:
app:
image: traefik/whoami:v1.11
secrets:
- app_passwort
secrets:
app_passwort:
file: /opt/secrets/app_passwort.txtIm Test mit Portainer CE 2.45.2 lief ein solcher Stack mit absolutem Pfad,
die Datei lag im Container unter /run/secrets/<name>. Mit einem relativen Pfad wie
./app_passwort.txt scheiterte das Deployment dagegen mit
bind source path does not exist: /data/compose/<Stack-ID>/app_passwort.txt
(gekürzt): Portainer löst relative Pfade gegenüber seinem eigenen
Stack-Verzeichnis auf. Die Datei auf dem Host schützt du mit chmod 600.
Läuft das Programm im Container nicht als root, muss die Datei dessen
Benutzer-ID gehören, sonst meldet es beim Lesen Permission denied.
Volumes und Netzwerke im Stack #
Compose legt pro Stack automatisch an:
- ein Netzwerk
<stackname>_default, in dem sich alle Dienste des Stacks über ihren Dienstnamen erreichen, - benannte Volumes mit dem Präfix
<stackname>_, im Beispiel alsouptime-kuma_kuma-data.
Auf dem Host liegt das Volume unter /var/lib/docker/volumes/. Prüfen kannst
du das so:
docker volume inspect -f '{{.Mountpoint}}' uptime-kuma_kuma-data/var/lib/docker/volumes/uptime-kuma_kuma-data/_dataBenannte Volumes sind für den Anfang die bessere Wahl als relative Pfade wie
./data. Die Compose-Datei liegt bei Portainer nicht in einem Projektordner
auf dem Host, sondern im Datenverzeichnis von Portainer; ein relativer Pfad
zeigt deshalb nicht dorthin, wo du ihn erwartest – wie beim Secret oben. Für Git-Stacks gibt es dafür
die Option Enable relative path volumes, die laut Doku der Business
Edition vorbehalten ist. Wenn du Daten direkt auf dem Host bearbeiten willst, nimm
einen absoluten Pfad wie /opt/uptime-kuma/data. Die Unterschiede, Rechte und
eigene Netzwerke erklärt der Guide zu Netzwerken und Volumes (siehe Weiterlesen).
Einen Stack aktualisieren #
Bei fest gepinnten Versionen ist ein Update eine bewusste Änderung in der Datei:
- Stacks → Stack auswählen → Tab Editor.
- Die Image-Zeile auf die neue Version anpassen – hättest du mit
louislam/uptime-kuma:2.5.5begonnen, also auflouislam/uptime-kuma:2.5.6. - Update the stack klicken.
Compose erkennt die geänderte Konfiguration, lädt das neue Image, falls es
noch nicht auf dem Host liegt, und erzeugt den Container neu. Getestet wurde
das mit Compose direkt auf der Kommandozeile, also mit dem Mechanismus, den
Portainer für Stacks nutzt: Nach dem Wechsel von 2.5.5 auf 2.5.6 meldete
Compose Recreate, der Container hatte eine neue ID, und eine vorher ins
Volume geschriebene Datei war unverändert vorhanden. Das Volume überlebt also,
der Container selbst wird ersetzt.
Hast du einen Dienst aus der Datei gelöscht, wählst du vor dem Update Prune services, damit auch dessen Container entfernt wird. Für Git-Stacks gibt es stattdessen Pull and redeploy mit der Option Re-pull image and redeploy, die das Image auch bei gleichem Tag neu zieht.
Ein Tipp aus der Praxis: Notiere dir vor dem Update die alte Version. Sie ist dein Rückweg, falls die neue Version nicht startet.
Rollback: was geht und was nicht #
Die Compose-Datei zurückzudrehen ist einfach: alte Image-Version eintragen, Update the stack. Die Portainer-Doku beschreibt im Editor zusätzlich ein Auswahlfeld Version für frühere Fassungen der Stack-Datei. Ob es in deiner Installation angeboten wird, wurde nicht getestet; verlässlicher ist es, die Datei selbst zu versionieren.
Was kein Werkzeug zurückdreht, sind die Daten im Volume. Viele Anwendungen migrieren beim ersten Start einer neuen Version ihre Datenbank. Startet danach wieder die alte Version, findet sie ein Schema vor, das sie nicht kennt. Wie das aussieht, zeigte im Test Portainer selbst: Nach einem Update auf 2.45.2 verweigerte das ältere Image auf demselben Volume den Start mit einem Hinweis, dass das Datenbankschema nicht zur Serverversion passt. Erst das Zurückspielen der Datenbanksicherung, die Portainer vor der Migration selbst angelegt hatte, half, und alle Änderungen seit dem Update waren weg.
Für deine Stacks heißt das: Vor einem Update mit neuer Haupt- oder Nebenversion sicherst du das Volume oder machst einen Datenbank-Dump. Wie du das organisierst, steht in Portainer-Backup.
Typische Probleme #
- Deployment bricht mit YAML-Fehler ab → Einrückung falsch, oft Tabs statt
Leerzeichen → Datei mit
docker compose configprüfen; Compose meldet die Zeile, etwadid not find expected key. port is already allocated,not found,pull access denied→ reine Docker-Meldungen (Port belegt, Tag oder Image-Name falsch) → Ursachen und Lösungen stehen in den Docker-Grundlagen; im Stack änderst du dann den Host-Port, etwa auf"3002:3001", oder den Tag.bind source path does not exist: /data/compose/…→ Ein Bind Mount oder eine Secret-Datei ist relativ angegeben (./…) → absoluten Host-Pfad eintragen.- Variable bleibt leer → Name in Portainer und in der YAML unterscheidet
sich (Groß-/Kleinschreibung) → mit
${NAME:?…}Pflichtwerte erzwingen. - Stack „aktiv“, Dienst trotzdem nicht erreichbar → Im Test galt ein Stack als aktiv, obwohl einzelne Container beendet waren → Containerliste und Logs prüfen, nicht nur den Stack-Status.
- Container startet immer wieder neu → Anwendung beendet sich mit Fehler → Logs des Containers lesen (siehe auch Portainer-Fehlersuche).
Sicherheit und Betrieb #
- Feste Versionen statt
latestmachen Updates planbar und Rollbacks überhaupt erst möglich. - Passwörter gehören nicht in die YAML. Stack-Variablen sind der einfache
Weg, landen aber im Klartext in
stack.env, im Backup und perdocker inspectsichtbar im Container; verschlüssele Portainer-Backups mit einem Passwort. Wo das Image_FILE-Variablen unterstützt, sind Secrets mit absolutem Host-Pfad die bessere Wahl. - Ports nur veröffentlichen, wenn du von außen zugreifen musst. Dienste, die nur intern miteinander reden, brauchen keine Portfreigabe.
- Wer in Portainer Stacks anlegen darf, kann beliebige Container auf dem Host starten. Vergib diese Rechte entsprechend sparsam.
- Den Stack-Webhook für Web-Editor-Stacks (Create a Stack webhook) markiert Portainer CE 2.45.2 als Business Feature, die Doku beschreibt ihn ebenfalls als Funktion der Business Edition. Bei einem Git-Stack funktionierte im Test mit Portainer CE 2.45.2 dagegen der Webhook der GitOps-Updates; automatische Updates planst du in der CE deshalb am besten über Git (siehe Weiterlesen).
- Ein solcher Webhook ist ohne Anmeldung auslösbar – die URL selbst ist das Geheimnis. Behandle sie wie ein Passwort und lege sie nicht in öffentliche Repositorys oder CI-Logs.
Weiterlesen #
- Git-Stacks in Portainer zeigt, wie du Compose-Dateien versionierst und Updates per Webhook auslöst.
- Netzwerke und Volumes in Portainer erklärt, wann ein eigenes Netzwerk und wann ein Bind Mount sinnvoll ist.
- Portainer aktualisieren behandelt Versionen, Image-Updates und Rollback-Grenzen im Detail.
- Uptime Kuma und Beszel zeigt, was du mit dem Beispieldienst anschließend sinnvoll überwachst.
Quellen
Offizielle Dokumentation
Portainer Docs — Add a new stack (externer Link, neues Fenster)
Offizielle Dokumentation
Portainer Docs — Inspect or edit a stack (externer Link, neues Fenster)
Offizielle Dokumentation
Portainer Docs — Webhooks (Stacks) (externer Link, neues Fenster)
Offizielle Dokumentation
Docker Docs — Interpolation (Compose) (externer Link, neues Fenster)
Offizielle Dokumentation
Docker Docs — Manage secrets securely in Docker Compose (externer Link, neues Fenster)
Offizielle Dokumentation
Häufige Fragen
Was ist der Unterschied zwischen einem Stack und einem Container in Portainer?
Kann ich eine vorhandene docker-compose.yml einfach in Portainer einfügen?
Wo speichert Portainer die Compose-Datei eines Stacks?
Gehen meine Daten verloren, wenn ich einen Stack aktualisiere?
Passt dazu
Werkzeug, Übung und eigene Messung zum Thema.
Teil der Themenwelt Portainer – Docker einfach verwalten · Abschnitt „Container und Docker Compose“
Weiterlesen
- PortainerEinsteiger
Container mit Portainer verwalten: anlegen, Logs, Updates
Container in Portainer anlegen, starten, stoppen, Logs und Statistiken lesen, Restart-Policy setzen und mit neuem Image neu erzeugen – ohne Datenverlust.
- PortainerEinsteiger
Was ist Portainer? Docker per Weboberfläche verwalten
Portainer erklärt: was die Docker-Weboberfläche leistet, wie sie zu Docker und Compose steht, CE oder Business Edition – und wann die Kommandozeile besser ist.
- PortainerEinsteiger
Portainer installieren: Debian 13 und Ubuntu 24.04 mit Docker
Docker aus dem offiziellen Repository, Portainer CE per Compose, Setup-Token und erster Login auf Debian 13 und Ubuntu 24.04, getestet mit Portainer 2.45.2.