Files
Proxmox-AIS-Server/docs/deployment.md
T
BartelLuis 9c528c6eca
CI / container-policy (push) Successful in 8s
CI / javascript-check (push) Successful in 18s
CI / python-tests (push) Failing after 4m44s
CI / container-verify (push) Skipped
CI / container-publish (push) Skipped
ci: migrate workflows to Gitea Actions
2026-09-14 20:23:20 +02:00

245 lines
10 KiB
Markdown

# Deployment mit einem Image aus der Container Registry
Proxmox AIS wird auf dem Zielserver aus einem bereits veröffentlichten
Containerimage gestartet. Der [Gitea-Actions-Workflow](gitea-actions.md) erstellt dieses Image; auf dem
Zielserver werden nur Docker Compose und die Betriebskonfiguration benötigt.
Diese Anleitung setzt einen erfolgreichen `container-publish`-Job und ein
weiterhin abrufbares Image voraus.
## Voraussetzungen und Dateien
Der Zielserver benötigt Linux auf amd64, Docker Engine, das Docker-Compose-Plugin
und einen HTTPS-Reverse-Proxy. Die Docker-Befehle unter einem Deploymentkonto
mit Docker-Zugriff ausführen. Dasselbe Konto für `docker login` und
`docker compose` verwenden, damit die Registry-Anmeldung verfügbar ist.
Die [Docker-Installationsanleitung](https://docs.docker.com/engine/install/debian/)
beschreibt beispielsweise die Installation auf Debian.
Python, Node.js, ein CI-Runner und Build-Werkzeuge gehören zur Build-Umgebung;
die Anwendung samt Laufzeit ist im veröffentlichten Image enthalten. Ein
Checkout des Anwendungsquellcodes ist auf dem Zielserver nicht erforderlich.
Ein beschreibbares Deploymentverzeichnis bereitstellen, beispielsweise
`/opt/proxmox-ais`, und diese beiden Dateien aus dem gewünschten Projektstand
herunterladen beziehungsweise dorthin kopieren:
| Datei aus dem Repository | Dateiname auf dem Zielserver |
| --- | --- |
| [compose.yaml](../compose.yaml) | `/opt/proxmox-ais/compose.yaml` |
| [config/deployment.env.example](../config/deployment.env.example) | `/opt/proxmox-ais/.env` |
Im weiteren Verlauf alle Compose-Befehle in diesem Verzeichnis ausführen:
```bash
cd /opt/proxmox-ais
chmod 600 .env
docker version
docker compose version
```
Die Compose-Datei verwendet das Image aus `PROVISIONER_IMAGE`. Ein fehlender
Wert führt bereits bei der Konfigurationsprüfung zu einem Fehler.
## Image und öffentliche Adresse festlegen
In Gitea unter **Actions** den erfolgreichen Workflow-Lauf mit
`container-publish` öffnen, unter **Artifacts** das Archiv `container-deploy`
herunterladen und entpacken. Es enthält `build.env` und `deploy.env`.
Die vollständige Zeile `PROVISIONER_IMAGE=…@sha256:…` aus `deploy.env` in die
`.env` auf dem Zielserver übernehmen. Der Digest legt genau das veröffentlichte
Image fest. Compose liest `deploy.env` nicht automatisch ein.
Alternativ in Gitea beim Benutzer `BartelLuis` das zugehörige **Packages**-Paket
öffnen und den vollständigen
Imagepfad mit einem tatsächlich vorhandenen Versions-Tag kopieren. Der aktuelle
Imagepfad der Beispieldatei sieht so aus:
```dotenv
PROVISIONER_IMAGE=gitlab.bartelluis.de/bartelluis/proxmox-ais-server:0.1.0
PUBLIC_URL=https://provision.example.net
MAINTENANCE=false
```
`PROVISIONER_IMAGE` und `PUBLIC_URL` für die Installation festlegen. Der
Imagepfad entspricht dem aktuellen Wert der Vorlage; die Verfügbarkeit des
Tags `0.1.0` wird damit nicht vorausgesetzt. Maßgeblich sind das
Pipeline-Artefakt oder die Registry-Anzeige deines Projekts.
Der Registry-Endpunkt der Vorlage ist `gitlab.bartelluis.de`. Bei einer abweichenden
CI-Registry den Host aus `deploy.env` auch für `docker login` verwenden.
Für reproduzierbare Deployments den Digest
bevorzugen und die bisher verwendeten Digests für spätere Updates aufbewahren.
`PUBLIC_URL` ist die vollständige HTTPS-Basisadresse ohne zusätzlichen Pfad,
unter der Browser, Installer und installierte Hosts den Dienst erreichen.
DNS und Zertifikatskette müssen für diese Systeme gültig sein. Der
Reverse-Proxy auf dem Zielserver leitet diese Adresse an
`http://127.0.0.1:8080` weiter. Der Compose-Port ist nur auf Loopback gebunden.
Bei einem Proxy auf einem anderen Host oder in einem anderen Container dessen
Verbindung gezielt nach der [Betriebsanleitung](operations.md#tls-und-reverse-proxy)
einrichten; `127.0.0.1` bezeichnet dort jeweils das eigene System.
## An der Registry anmelden und Image laden
Öffentliche Gitea-Pakete lassen sich ohne Anmeldung herunterladen. Für ein
privates Paket einen Gitea Personal Access Token mit dem Scope
`read:package` verwenden. Das zugehörige Gitea-Konto benötigt Lesezugriff auf
das Paket. Der Token wird
bei der Anmeldung als Passwort verdeckt eingegeben und gehört nicht in `.env`.
Das kurzlebige `GITEA_TOKEN` des CI-Jobs ist kein Deployment-Zugang.
[Gitea-Container-Registry](https://docs.gitea.com/usage/packages/container/),
[Paketberechtigungen](https://docs.gitea.com/usage/packages/overview/#access-restrictions).
Nur für private Pakete anmelden und `GITEA_USERNAME` durch den Gitea-Namen
des Token-Inhabers ersetzen:
```bash
docker login gitlab.bartelluis.de --username GITEA_USERNAME
```
Konfiguration prüfen und das gewählte Image herunterladen:
```bash
docker compose config --quiet
docker compose pull provisioner
```
Erst nach erfolgreichem Pull fortfahren. `unauthorized` oder `denied` weist
auf Anmeldung beziehungsweise Zugriffsrechte hin; bei `manifest unknown` den
Imagepfad und den verfügbaren Tag/Digest prüfen. Bei internen Zertifikaten
den CA-Vertrauensanker im Docker-Dienst des Zielservers einrichten.
## Einmalige Initialisierung und Start
Nur bei der ersten Inbetriebnahme mit neuen Daten- und Schlüsselvolumes den
Administrator und den Master-Key anlegen:
```bash
docker compose run --rm --no-deps \
--volume proxmox-ais-keys:/run/secrets:rw provisioner init --username admin
```
Das Passwort wird zweimal interaktiv abgefragt. Es gibt kein Standardpasswort.
Das Schlüsselvolume ist für diesen einen Aufruf beschreibbar. Im Regelbetrieb
wird es schreibgeschützt eingebunden. Bei einer bestehenden Installation oder
einem Update diesen Initialisierungsbefehl nicht erneut ausführen.
Anschließend den Dienst starten:
```bash
docker compose up -d --no-build --wait
docker compose ps
docker compose logs --tail=100 provisioner
```
`--wait` wartet auf einen laufenden, gesunden Dienst gemäß dem Healthcheck im
Image. Der Proxyzugriff wird separat geprüft.
[Compose-Startoptionen](https://docs.docker.com/reference/cli/docker/compose/up/).
Mit `curl`, sofern auf dem Zielserver verfügbar, den lokalen Dienst und die
öffentliche HTTPS-Adresse prüfen; die Beispieladresse ersetzen:
```bash
curl --fail http://127.0.0.1:8080/health/ready
curl --fail https://provision.example.net/health/ready
```
Beide Aufrufe sollen `"status":"ready"` liefern. Danach die öffentliche
Adresse im Browser öffnen und mit dem angelegten Administrator anmelden.
## Daten und Schlüssel
Die Anwendung läuft im Container als UID/GID `10001:10001`. Die Compose-Datei
verwendet diese dauerhaften, von Docker verwalteten Volumes:
| Volume | Inhalt | Containerpfad |
| --- | --- | --- |
| `proxmox-ais-data` | SQLite-Datenbank und Artefakte | `/var/lib/proxmox-ais` |
| `proxmox-ais-keys` | Master-Key für verschlüsselte Geheimnisse | `/run/secrets` |
Für neue Volumes übernimmt Docker die vorbereiteten Verzeichnisse aus dem
Image; sie sind für den Dienstbenutzer angelegt. Beim Wiederverwenden oder
Wiederherstellen bestehender Volumes müssen Eigentümer und Rechte weiterhin
zu UID/GID 10001 passen. Lokale Bind-Mount-Verzeichnisse benötigen dieselben
passenden Rechte, falls die Compose-Datei später entsprechend angepasst wird.
[Docker-Volumes](https://docs.docker.com/engine/storage/volumes/).
Containerneustarts und Updates erhalten diese Volumes.
`docker compose down --volumes` würde sie löschen. Für mehrere getrennte Instanzen auf demselben
Host die Volume-Namen in Compose und beim Initialisierungsaufruf je Instanz
anpassen. Daten und Master-Key nach der
[Backup-Anleitung](operations.md#backup) getrennt sichern.
## Updates
Ein Wartungsfenster vorsehen. Vor dem ersten Neustart den Digest des
tatsächlich laufenden Images ermitteln, insbesondere bei Verwendung eines
beweglichen Tags:
```bash
AIS_RUNNING_IMAGE_ID=$(docker inspect --format '{{.Image}}' "$(docker compose ps -q provisioner)")
docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$AIS_RUNNING_IMAGE_ID"
```
Den zum Projekt passenden Registry-Digest zusammen mit dem Backup aufbewahren
und in `.env` als `PROVISIONER_IMAGE` eintragen. Damit verwendet auch der
Wartungsneustart genau die bisherige Version. Falls kein Registry-Digest
angezeigt wird, die bisherige Version vor dem Update eindeutig zuordnen.
Jetzt in `.env` `MAINTENANCE=true` setzen und die Einstellung mit der
bisherigen Version übernehmen:
```bash
docker compose up -d --no-build --wait
```
Der Wartungsmodus sperrt neue Installationsfreigaben. Bereits gestartete
Installer und Host-Runner werden dadurch nicht beendet. Ihre laufenden
Phasen vor dem eigentlichen Versionswechsel prüfen und einen geeigneten
Zeitpunkt für den Dienststopp abwarten.
Jetzt die Daten mit der bisher eingesetzten Anwendungsversion sichern und
den Master-Key separat verwahren. Die [Backup-Anleitung](operations.md#backup)
enthält dafür die Containerbefehle. Zusätzlich die aktuelle `.env`, den
bisherigen Image-Digest und die Proxykonfiguration sichern.
Danach die neue `PROVISIONER_IMAGE`-Zeile aus dem erfolgreichen
`container-publish`-Job in `.env` übernehmen. `PUBLIC_URL` und die
Volume-Namen beibehalten, `MAINTENANCE=true` gesetzt lassen. Folgender Ablauf
prüft und lädt das neue Image, bevor er den bisherigen Dienst stoppt:
```bash
(
set -eu
docker compose config --quiet
docker compose pull provisioner
docker compose stop provisioner
docker compose up -d --no-build --wait
docker compose ps
)
```
Bei einem fehlgeschlagenen Pull läuft der bisherige Dienst weiter. Nach dem
Update Readiness über HTTPS, Anmeldung und Inventar prüfen. Erst nach
erfolgreicher Prüfung in `.env` `MAINTENANCE=false` setzen und übernehmen:
```bash
docker compose up -d --no-build --wait
```
Der Initialisierungsbefehl gehört ausschließlich zur ersten Inbetriebnahme.
## Rückkehr zum vorherigen Stand
Falls der neue Dienst nicht gesund startet, zunächst
`docker compose logs --tail=100 provisioner` prüfen. Das vorherige Image darf
nur auf eine damit kompatible Datenbank zugreifen. Bei bestätigter
Kompatibilität die gesicherte `.env` beziehungsweise den bisherigen exakten
Image-Digest wiederherstellen und den Updateablauf erneut ausführen; den
Wartungsmodus erst nach erfolgreicher Prüfung beenden.
Hat die neue Version das Schema bereits verändert, den Dienst stoppen und
mit der zum Backup passenden Anwendungsversion nach der
[Restore-Anleitung](operations.md#restore) in ein neues leeres Datenvolume
wiederherstellen. Die vorhandenen Daten erhalten, bis die Wiederherstellung
geprüft ist. Der Restore setzt Maschinenberechtigungen zurück und erfordert
den dort beschriebenen Abgleich laufender Installationen.