# DEPLOYMENT.md – Kontext & Aufgabe für Claude Code Dieses Dokument gibt dir (Claude Code) den nötigen Kontext, um die Agentur-Website sauber auf dem **bestehenden** VPS auszuliefern, ohne mit der laufenden Infrastruktur zu kollidieren. Lies es komplett, bevor du Änderungen vornimmst. Es gibt zwei Teile: - **Teil A – im Projekt (das machst du, Claude Code):** Dateien im Repo erstellen, committen, pushen. - **Teil B – auf dem Server (das macht der Mensch, Pascal, selbst):** nur Referenz, damit du die Dateien passend dazu erstellst. Führe Teil B NICHT aus und frage nicht nach Server-Zugängen. --- ## 1. Bestehende Infrastruktur (nicht verändern!) Auf dem Server läuft bereits ein Docker-Compose-Stack. **Wichtig für dich:** Es gibt schon einen zentralen Reverse Proxy (Caddy), der Ports 80/443 besitzt und per Domain verteilt. Du darfst **keinen zweiten Reverse Proxy** bauen und **keine Ports 80/443** belegen. Aufbau (Ordner `~/stack` auf dem Server = `/home/deploy/stack`): - `docker-compose.yml` mit den Diensten: `caddy`, `nextcloud`, `nextcloud-db` (MariaDB), `redis`, `nextcloud-cron`, `forgejo`. - Zwei Docker-Netzwerke: `web` (alles, was Caddy erreichen soll) und `internal` (Datenbank etc.). - `caddy/Caddyfile` verteilt nach Hostname: - `cloud.larpaso.de` → Nextcloud - `git.larpaso.de` → Forgejo - SSL/HTTPS macht Caddy vollautomatisch über Let's Encrypt. Neue Domains brauchen nur einen Caddy-Block + einen passenden DNS-A-Eintrag. **Regeln:** - Bestehende Dienste (`nextcloud*`, `forgejo`, `*-db`, `redis`, `caddy`) nicht anfassen/neu starten. - Der neue Website-Dienst hängt **nur** am Netzwerk `web` (nicht `internal`). - Der Website-Container exponiert **keine** Host-Ports. Caddy erreicht ihn intern über den Container-Namen im `web`-Netzwerk. - Keine Passwörter, Tokens, Keys oder Inhalte der `.env` ins Repo, ins Dockerfile oder in Chat-Ausgaben schreiben. ## 2. Ziel Die Astro-Website soll unter **`https://larpaso.de`** live gehen (optional zusätzlich `www.larpaso.de` → Weiterleitung auf `larpaso.de`). Sie wird **auf dem Server gebaut** (Multi-Stage-Docker-Build: Node baut die statischen Dateien, nginx liefert sie aus) und über den bestehenden Caddy ausgeliefert. --- ## TEIL A – Deine Aufgaben im Projekt (Claude Code) ### A1. Projekt prüfen - Zeig die Projektstruktur der obersten Ebene. - Bestätige: Ist es ein Astro-Projekt? Gibt es `package.json` (und Lockfile)? - Ermittle den **Paketmanager** (npm / pnpm / yarn) am Lockfile (`package-lock.json` → npm, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn) und die passenden Build-Befehle. Passe die Dockerfile-Befehle unten entsprechend an. ### A2. Astro auf statische Ausgabe + Domain konfigurieren - Stelle sicher, dass Astro **statisch** baut (Standard `output: 'static'` – KEIN Server-Adapter, da nginx nur statische Dateien ausliefert). - Setze in `astro.config.*` die Site-URL: ```js export default defineConfig({ site: 'https://larpaso.de', // output: 'static' ist Standard – nicht auf 'server' stellen }); ``` ### A3. Multi-Stage-Dockerfile anlegen (Repo-Wurzel: `Dockerfile`) Vorlage für **npm**. Bei pnpm/yarn die Install-/Build-Zeilen entsprechend anpassen (z. B. `corepack enable && pnpm install --frozen-lockfile && pnpm build`). ```dockerfile # --- Build-Stage --- FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # --- Serve-Stage --- FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ``` ### A4. nginx-Konfiguration anlegen (Repo-Wurzel: `nginx.conf`) ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Saubere URLs + Fallback location / { try_files $uri $uri/ $uri.html /index.html; } # Astro legt gehashte Assets unter /_astro ab -> lange cachen location /_astro/ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` ### A5. `.dockerignore` anlegen (Repo-Wurzel) ``` node_modules dist .git ``` ### A6. `.gitignore` sicherstellen (Repo-Wurzel) Muss mindestens enthalten: ``` node_modules dist ``` ### A7. Lokal gegenprüfen (optional, aber empfohlen) - Wenn Node lokal vorhanden: einmal `npm install` + `npm run build` ausführen und melden, ob der Build fehlerfrei einen `dist`-Ordner erzeugt. So fängst du Build-Fehler VOR dem Server-Deploy ab. (Falls Node lokal fehlt: überspringen und dem Menschen mitteilen.) ### A8. Committen & pushen - `git status` prüfen: Es dürfen **nur** Quellcode + die neuen Konfigdateien (`Dockerfile`, `nginx.conf`, `.dockerignore`, `.gitignore`, `astro.config.*`) mitgehen – **kein** `node_modules`, **kein** `dist`. - Sinnvolle Commit-Nachricht, dann `git push origin main`. - Danach dem Menschen bestätigen, dass alles in Forgejo liegt, und ihn auf **Teil B** verweisen. --- ## TEIL B – Server-Schritte (macht Pascal selbst; hier nur zur Abstimmung) > Diese Schritte führt der Mensch auf dem Server aus. Sie sind hier dokumentiert, damit die > Dateien aus Teil A dazu passen. Claude Code führt sie **nicht** aus. ### B1. Lese-Zugang des Servers zu Forgejo (einmalig) Auf dem Server als `deploy`: ```bash ssh-keygen -t ed25519 -C "server-deploy@larpaso" -f ~/.ssh/id_ed25519 cat ~/.ssh/id_ed25519.pub ``` Diesen öffentlichen Key in Forgejo eintragen: Repo `larpaso-website` → **Einstellungen → Deploy-Schlüssel → Schlüssel hinzufügen**, einfügen, **schreibgeschützt** lassen. ### B2. Repo auf den Server klonen (einmalig) ```bash mkdir -p ~/sites git clone ssh://git@git.larpaso.de:2222/pascal/larpaso-website.git ~/sites/larpaso-website ``` ### B3. Website-Dienst in `~/stack/docker-compose.yml` ergänzen Unter `services:` einfügen: ```yaml website: build: context: /home/deploy/sites/larpaso-website restart: unless-stopped networks: [web] ``` (Keine `ports:`-Angabe – Caddy erreicht den Container intern.) ### B4. Caddy-Block in `~/stack/caddy/Caddyfile` ergänzen ```caddyfile larpaso.de { reverse_proxy website:80 } # optional, nur falls DNS-A-Eintrag für www existiert: www.larpaso.de { redir https://larpaso.de{uri} permanent } ``` ### B5. DNS bei IONOS setzen - A-Eintrag: Hostname `@` → Server-IPv4 (den evtl. vorhandenen alten `@`-A-Eintrag **ersetzen**, nicht doppelt anlegen). - Optional A-Eintrag: Hostname `www` → Server-IPv4 (nur nötig, wenn der www-Block genutzt wird). - **Hinweis:** Ein A-Eintrag auf `@` ändert die MX-/Mail-Einträge NICHT – die E-Mails laufen unverändert weiter. - Vor dem nächsten Schritt kurz warten/prüfen: `nslookup larpaso.de` muss die Server-IP zeigen, sonst kann Caddy kein Zertifikat holen. ### B6. Bauen & starten ```bash cd ~/stack docker compose build website docker compose up -d website docker compose restart caddy # lädt den neuen Caddyfile-Block docker compose logs -f caddy # auf "certificate obtained successfully" für larpaso.de achten ``` Dann `https://larpaso.de` im Browser aufrufen. ### B7. Künftige Updates (Ablauf bei jeder Änderung) ```bash cd ~/sites/larpaso-website && git pull cd ~/stack && docker compose build website && docker compose up -d website ``` --- --- ## Status **Teil A ist erledigt und geprüft:** - `astro.config.mjs` → `site: 'https://larpaso.de'`, statischer Output (Standard, kein Adapter). - `Dockerfile`, `nginx.conf`, `.dockerignore` liegen im Repo, entsprechen exakt der Vorlage oben. - Lokaler `npm run build` läuft fehlerfrei durch (4 statische Seiten, `dist/` wird erzeugt). - Alles committet und nach `https://git.larpaso.de/pascal/larpaso-website` gepusht. **Teil B steht noch aus** – das macht Pascal selbst auf dem Server. Fertige Copy-Paste-Blöcke dafür: ### B1 – Deploy-Key erzeugen (als `deploy` auf dem Server) ```bash ssh-keygen -t ed25519 -C "server-deploy@larpaso" -f ~/.ssh/id_ed25519_larpaso-website -N "" cat ~/.ssh/id_ed25519_larpaso-website.pub ``` Den ausgegebenen Public Key in Forgejo eintragen: `https://git.larpaso.de/pascal/larpaso-website/settings/keys` → **Deploy-Schlüssel hinzufügen**, einfügen, **schreibgeschützt** lassen. ### B2 – Repo klonen ```bash mkdir -p ~/sites GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519_larpaso-website" \ git clone ssh://git@git.larpaso.de:2222/pascal/larpaso-website.git ~/sites/larpaso-website ``` ### B3 – Service in `~/stack/docker-compose.yml` ergänzen Unter `services:` einfügen (Einrückung an bestehende Datei anpassen): ```yaml website: build: context: /home/deploy/sites/larpaso-website restart: unless-stopped networks: [web] ``` ### B4 – Caddy-Block in `~/stack/caddy/Caddyfile` ergänzen ```caddyfile larpaso.de { reverse_proxy website:80 } www.larpaso.de { redir https://larpaso.de{uri} permanent } ``` (Den `www`-Block nur, wenn auch ein DNS-A-Eintrag für `www` existiert.) ### B5 – DNS bei IONOS - A-Eintrag `@` → Server-IPv4 (bestehenden `@`-Eintrag ersetzen). - Optional A-Eintrag `www` → Server-IPv4. - Prüfen: `nslookup larpaso.de` muss die Server-IP liefern, bevor B6 läuft. ### B6 – Bauen & starten ```bash cd ~/stack docker compose build website docker compose up -d website docker compose restart caddy docker compose logs -f caddy # auf "certificate obtained successfully" für larpaso.de warten ``` Danach `https://larpaso.de` im Browser prüfen. ### B7 – Künftige Updates ```bash cd ~/sites/larpaso-website && git pull cd ~/stack && docker compose build website && docker compose up -d website ``` --- ## Sicherheits-Leitplanken (Zusammenfassung) - Keine Secrets ins Repo/Dockerfile/Logs. - Kein zweiter Reverse Proxy, keine Belegung von 80/443 durch den Website-Dienst. - Website nur im `web`-Netzwerk, keine Host-Ports. - Bestehende Dienste nicht verändern oder neu starten (außer dem bewussten `caddy restart` in B6). - Astro bleibt statisch (`output: 'static'`).