# 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}}' ``` 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: ` | CI-Job hat ein anderes Arbeitsverzeichnis als der Host | | `build: .` | `image: :${IMAGE_TAG:-latest}` **plus** `build: .` | Versionstag ermoeglicht Rollback ohne Rebuild | | kein/wechselnder `container_name` | `container_name: ` | 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: -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= ``` Container stoppen (kein Kopieren aus einem laufenden Datenbank-Volume): ```bash docker compose stop ``` Kopieren: ```bash docker volume create -data docker run --rm -v _:/from -v -data:/to \ alpine sh -c 'cd /from && cp -a . /to' ``` Pruefen, dass etwas angekommen ist: ```bash docker run --rm -v -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= --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 /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 ` zeigt welcher |