Files
deploy-kit/MIGRATION.md
Lucas Orth f5bdb9a988 deploy-kit zum Gitea-Template-Repo umbauen
Statt eines zweiten Repos wird deploy-kit selbst die Vorlage. Ein Template
kopiert das gesamte Repo, die Projektdateien muessen also im Wurzel-
verzeichnis liegen - template/ ist entsprechend aufgeloest.

- bootstrap.sh entfaellt: das Template uebernimmt seine Aufgabe, und
  Gitea kopiert serverseitig, funktioniert also auch bei privatem Repo.
- .gitea/template laesst Gitea ${REPO_NAME} in README.md und
  docker-compose.yml ersetzen. Projektname, Image und container_name
  stimmen damit ohne Handarbeit, offen bleibt nur der Port.
- CLAUDE.md zeigt auf DEPLOY.md, damit die Konventionen beim Arbeiten am
  erzeugten Projekt gelesen werden.
- DEPLOY.md liegt jetzt nur noch einmal - die Kopie im geplanten zweiten
  Repo waere unweigerlich auseinandergelaufen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 19:29:31 +02:00

5.2 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: docker-compose.yml.

Schritt 3: Workflow anlegen

.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