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>
163 lines
5.3 KiB
Markdown
163 lines
5.3 KiB
Markdown
# 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 |
|