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>
This commit is contained in:
2026-08-20 19:23:21 +02:00
parent eeb4477476
commit 5b7418138e
3 changed files with 180 additions and 0 deletions

View File

@@ -159,6 +159,12 @@ Danach Let's-Encrypt-Zertifikat ausstellen und "Force SSL" aktivieren.
Weil der Container-Name die Adresse ist: aendert sich `container_name`,
zeigt der Proxy-Host ins Leere.
## Bestehende App umstellen
Laeuft die App heute schon manuell auf dem Server, gilt statt des folgenden
Abschnitts [MIGRATION.md](MIGRATION.md) - dort steht insbesondere, wie die
Volumes den Wechsel des Projektnamens ueberleben.
## Erstes Deployment einer App
1. Repo in Gitea anlegen, pushen.

162
MIGRATION.md Normal file
View File

@@ -0,0 +1,162 @@
# 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](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:
```bash
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](template/docker-compose.yml).
## Schritt 3: Workflow anlegen
[template/.gitea/workflows/deploy.yml](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:
```bash
docker volume ls --filter name=<alter-projektname>
```
Container stoppen (kein Kopieren aus einem laufenden Datenbank-Volume):
```bash
docker compose stop
```
Kopieren:
```bash
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:
```bash
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
```bash
docker compose down
```
Damit ist der Container-Name frei. Der Dienst ist ab hier bis zum Ende des
ersten Deploys offline.
Dann lokal:
```bash
git tag v1.0.0 && git push origin v1.0.0
```
## Schritt 7: Pruefen
```bash
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:
```bash
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 |

View File

@@ -28,6 +28,18 @@ Das Skript legt an, was fehlt, und laesst Vorhandenes unangetastet:
`__APP__` und `__PORT__` werden dabei ersetzt.
## Bestehende App umstellen
Fuer Apps, die heute per SCP deployt werden, gibt es eine eigene Anleitung -
inklusive Volume-Migration, damit der Wechsel des Projektnamens keine Daten
kostet:
```bash
curl -fsSL -o MIGRATION.md https://gitea.lucas-orth.de/lucas.orth/deploy-kit/raw/branch/main/MIGRATION.md
```
Dann Claude bitten, die Schritte darin auf das Projekt anzuwenden.
## Nur die Konventionen
Wenn ein Projekt schon eingerichtet ist und du nur die aktuelle Fassung der