Files
deploy-kit/MIGRATION.md
Lucas Orth 5b7418138e Anleitung zum Umstellen bestehender Container
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>
2026-08-20 19:23:21 +02:00

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:

  1. 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.
  2. Fester Container-Name. Der alte Container belegt ihn noch. Ohne vorheriges down schlaegt 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_default haengt
  • 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