Fuer Apps, die heute per SCP deployt werden. Schwerpunkt liegt auf den beiden Aenderungen, die nicht rueckwirkungsfrei sind: der feste Projektname benennt die Volumes um, der feste Container-Name kollidiert mit dem noch laufenden alten Stack. Enthaelt das Rezept zum Umkopieren der Volumes und einen Rueckweg, solange das alte Volume noch existiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.3 KiB
Bestehenden Container umstellen
Anleitung fuer Apps, die heute manuell deployt werden (Dateien per SCP auf
den Server, dort docker compose up -d --build) und auf Tag-Deployment
umziehen sollen.
Die Zielkonventionen stehen in DEPLOY.md. Hier steht nur, was beim Umstieg zu tun ist und wo dabei Daten verloren gehen koennen.
Der gefaehrliche Teil zuerst
Zwei Aenderungen sind nicht rueckwirkungsfrei:
- Fester Projektname (
name:im Compose). Compose leitet den Projektnamen sonst aus dem Verzeichnisnamen ab. Aendert er sich, heissen auch die Volumes anders - die App startet mit leerem Datenbestand. - Fester Container-Name. Der alte Container belegt ihn noch. Ohne
vorheriges
downschlaegt der erste Deploy fehl.
Beides ist beherrschbar, aber nicht durch Ausprobieren. Schritt 5 und 6 gehoeren zusammen und in dieser Reihenfolge ausgefuehrt.
Schritt 0: Bestand aufnehmen
Auf dem Server, im Verzeichnis der App:
docker compose config --format json | head -40
docker compose ps
docker volume ls
docker inspect -f '{{range $n,$v := .NetworkSettings.Networks}}{{$n}} {{end}}' <container>
Notieren:
- aktueller Projektname (= Verzeichnisname, falls
name:fehlt) - aktuelle Volume-Namen (Praefix ist der Projektname)
- ob der Container schon im Netz
nginx-proxy-manager_defaulthaengt - ob
ports:verwendet wird und worauf der NPM-Host zeigt - Inhalt der
.env
Schritt 1: Repo anlegen
Code lokal in ein Git-Repo, in Gitea pushen. Noch keinen Tag setzen.
Falls der Code bisher nur auf dem Server lag: per SCP herunterholen, ohne
.env, node_modules, dist und Datenverzeichnisse.
Schritt 2: docker-compose.yml umbauen
| Vorher | Nachher | Warum |
|---|---|---|
kein name: |
name: <app> |
CI-Job hat ein anderes Arbeitsverzeichnis als der Host |
build: . |
image: <app>:${IMAGE_TAG:-latest} plus build: . |
Versionstag ermoeglicht Rollback ohne Rebuild |
kein/wechselnder container_name |
container_name: <app> |
Das ist die Adresse, auf die der Proxy zeigt |
environment: mit Klartextwerten |
env_file: .env |
Wird im Workflow aus dem Secret erzeugt |
volumes: [foo:/data] |
zusaetzlich name: <app>-data im volumes-Block |
Loest das Volume vom Projektnamen |
ports: ["8080:8080"] |
streichen | Der Proxy erreicht den Container ueber das gemeinsame Netz |
kein healthcheck: |
einen ergaenzen | Der Deploy nutzt --wait und macht ihn zum Gate |
relative Bind-Mounts ./x:/x |
named volume | Compose laeuft im Job-Container, der Pfad zeigt ins Leere |
Vorlage: template/docker-compose.yml.
Schritt 3: Workflow anlegen
template/.gitea/workflows/deploy.yml uebernehmen, Build- und Test-Schritte des Projekts einsetzen. Tests gehoeren vor den Deploy-Schritt.
In .dockerignore .gitea ergaenzen.
Schritt 4: Secret setzen
Inhalt der .env vom Server als Repo-Secret DOTENV anlegen
(Repo -> Einstellungen -> Actions -> Secrets), mehrzeilig am Stueck.
Vorher eine Kopie in den Passwortmanager - Gitea gibt den Wert nicht mehr heraus.
Schritt 5: Volumes migrieren
Nur noetig, wenn die App persistente Daten hat, die nicht neu erzeugt werden koennen. Bei einem regenerierbaren Cache: ueberspringen.
Alte Volumes finden:
docker volume ls --filter name=<alter-projektname>
Container stoppen (kein Kopieren aus einem laufenden Datenbank-Volume):
docker compose stop
Kopieren:
docker volume create <app>-data
docker run --rm -v <alter-projektname>_<volume>:/from -v <app>-data:/to \
alpine sh -c 'cd /from && cp -a . /to'
Pruefen, dass etwas angekommen ist:
docker run --rm -v <app>-data:/d alpine ls -la /d
Das alte Volume nicht loeschen, bis der erste Deploy laeuft. Es ist der Rueckweg.
Schritt 6: Cutover
docker compose down
Damit ist der Container-Name frei. Der Dienst ist ab hier bis zum Ende des ersten Deploys offline.
Dann lokal:
git tag v1.0.0 && git push origin v1.0.0
Schritt 7: Pruefen
docker ps --filter name=<app> --format '{{.Names}}\t{{.Status}}'
Status muss healthy zeigen, nicht Restarting. Der Workflow faengt das
zwar durch --wait ab, aber nachsehen kostet nichts.
Danach die App ueber ihre Domain aufrufen. Bei 502 im NPM: Container haengt
nicht im Netz nginx-proxy-manager_default, oder der Proxy-Host zeigt noch
auf IP:Port statt auf den Container-Namen.
Wenn es schiefgeht
Der alte Zustand ist noch da, solange du das alte Volume nicht geloescht hast:
docker compose -f <altes-verzeichnis>/docker-compose.yml up -d
Der Workflow selbst faellt in den Schritten 1-7 folgenlos aus - erst der Deploy-Schritt fasst den Server an.
Fallstricke
| Symptom | Ursache |
|---|---|
Conflict. The container name "/x" is already in use |
Schritt 6 uebersprungen, alter Stack laeuft noch |
| App startet mit leerem Datenbestand | Schritt 5 uebersprungen, Volume heisst jetzt anders |
| Deploy gruen, App antwortet nicht | ports: entfernt, aber NPM-Host zeigt noch auf IP:Port |
network nginx-proxy-manager_default not found |
Netz im Compose nicht als external: true deklariert |
| Container startet endlos neu | Wert in DOTENV fehlt; docker logs <app> zeigt welcher |