# VCH-Plattform — die Regeln dieser Umgebung Diese Datei gehört VCH und wird bei jedem Abgleich **überschrieben**. Projektwissen gehört in die `CLAUDE.md` daneben, nicht hierher. Sie beschreibt, was VCH mit diesem Projekt macht — vor allem die Stellen, an denen ein Verstoß **nicht als Fehler auffällt, sondern als falsches Ergebnis**: ein Deploy, der grün meldet und nichts ausliefert, eine Datenbank, die niemand verbindet, ein Audit, das etwas prüft, das du gar nicht gepusht hast. ## 1. Zwei Maschinen, nicht eine - **Hier (DEV):** ein Debian-LXC. Nutzer `vchuser`, Heimat `/home/vchuser`, Projekte unter `/home/vchuser/projects/`. `vchuser` hat `sudo` ohne Passwort und ist in der Docker-Gruppe. Vorhanden: Node 20 (npm; pnpm und yarn sind NICHT installiert — `corepack enable` holt sie, wenn dein Projekt sie braucht), Python samt venv, Docker, tmux, git, code-server auf Port 8080, Claude Code als `claude`, OpenCode als `opencode`; Git gegen Gitea vorkonfiguriert. Das Terminal läuft in tmux — ein Dev-Server im Vordergrund blockiert die Sitzung, starte ihn in einem eigenen tmux-Fenster oder mit `&`. - **Dort (PROD):** eine **andere** Maschine mit demselben Werkzeugsatz. VCH klont dein Repository dorthin und startet es. Der Build läuft dort mit **gelöschtem `NODE_ENV`**, damit devDependencies installiert werden; ein `NODE_ENV=production` aus `.env.example` gilt erst zur Laufzeit. Daraus folgt die wichtigste Regel überhaupt: **VCH deployt und auditiert deinen gepushten Commit, nie dein Arbeitsverzeichnis.** Nicht committete Änderungen existieren für Deploy, Audit und Veröffentlichung nicht — auch dann nicht, wenn hier alles läuft. ## 2. Wegweiser — welcher Weg in welcher Lage Bevor du baust: such deine Lage, nimm den Weg, kenn die Folgen. Die Abschnitte dahinter erklären das Wie. **Eine Datenbank, Schlüssel in `.env.example`.** Der Mensch stellt sie im Projektarbeitsplatz bereit, VCH trägt die Werte in genau deine Schlüssel ein (Abschnitt 8). Folgen: Datenbank und Benutzer heißen `app`, sofern beim Anlegen nichts anderes gewählt wurde; Migrationen laufen in deinem `build` oder `start`, VCH führt keine aus; DB-Zeilen im Env-Editor werden gestrichen, solange die Datenbank läuft; erst der nächste Deploy trägt die Werte ein. **Zwei Datenbanken — oder Redis, Mongo, Elasticsearch.** Compose mit eigenen Diensten (Abschnitt 13). Folgen: ein Compose mit eigener Datenbank deployt **nicht blau-grün, sondern in place** — kurze Auszeit beim Umschalten, das Audit läuft als Torprüfung vor dem Start; VCH sichert diese Datenbank nicht (kein Backup über die Karte); jeder Datenbankdienst braucht ein Named Volume; den DB-Port **nicht** als festen Host-Port veröffentlichen. An der verwalteten Datenbank landen zwei deklarierte Verbindungen (`DATABASE_URL` und `ANALYTICS_DATABASE_URL`) auf **derselben** — deshalb Compose. **Nur Dateien oder SQLite.** Keine Datenbank; alles, was bleiben soll, nach `VCH_DATA_DIR` (Abschnitt 9). Folgen: `HISTORY_DB=/pfad.sqlite` ist bewusst keine Verbindung und wird nicht überschrieben; ein Pfad relativ zum Release ist beim nächsten Deploy weg. **Eine Datenbank zum Entwickeln, hier auf DEV.** VCH stellt hier keine bereit. Starte selbst eine — Docker ist da, `vchuser` ist in der Docker-Gruppe: `docker run -d --name dev-db -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=dev postgres:16`. Lege eine lokale `.env` an, deklariere die Schlüssel in `.env.example`. Folgen: die Bindung an `127.0.0.1` ist Pflicht — ohne Adresse vor dem Port lauscht die Datenbank für jeden im LAN; die `.env` gehört nicht ins Repository; für PROD trägt VCH die verwaltete Datenbank ein. **Ein zweiter Prozess neben der App** (Worker, Cron, Queue). Compose (Abschnitt 13). Folgen: systemd startet genau **einen** Befehl, den aus `start`; im Compose läuft jeder Dienst mit, Dienste hinter `profiles:` bleiben aus. **Reines Frontend ohne Server.** Statisch ausliefern, irgendetwas muss auf `$PORT` lauschen (Abschnitt 6.2, „Statische Seite"). Folgen: relative Pfade und Vite `base: './'` (Abschnitt 3), sonst bricht die Vorschau hinter dem Pfad-Proxy. **Code in einem Unterordner.** `cd` in `build` und `start` (Abschnitt 6.2, „Monorepo"), die `.env.example` in den Ordner, der sie liest. Folgen: jede Beispieldatei bestimmt ihr eigenes `.env`-Ziel (Abschnitt 7). **Die Vorschau zeigt nichts, der Port wird aber erkannt.** Dein Server lauscht auf `127.0.0.1`. Lausche auf `0.0.0.0`: `vite --host`, `next dev -H 0.0.0.0`, `HOST=0.0.0.0`. Folgen: die Porterkennung sieht auch lokale Lauscher, der Proxy erreicht sie nicht — ein erkannter Port beweist keine erreichbare Vorschau. **Ein Geheimnis wird gebraucht** (API-Schlüssel, Token). In `.env.example` **leer** deklarieren (`API_KEY=`); den Wert hinterlegt der Mensch im Env-Editor (Abschnitt 7). Folgen: leer ohne Vorgabe blockiert den Deploy, bis der Wert da ist — gewollt; die Prüfung `secrets` findet eingecheckte Werte (Abschnitt 11). **Der Deploy schlägt fehl.** Du siehst weder das Deploy-Log noch Logs der PROD-Maschine — lass dir den Fehlertext aus dem Projektarbeitsplatz geben. Dann prüfen: lauscht die App auf `PORT`, antwortet der `health`-Pfad innerhalb von 30 Sekunden, liest sie ihre Werte aus der Umgebung? Folgen: im Deploy-Log stehen die letzten 50 Logzeilen der App — logge nach stdout, nicht in Dateien. **Dem Projekt ist eine Integration zugeordnet.** Die Zugangsdaten liegen in `.vch-integrations.env` im Projektstamm: `_API_URL`, `_USER`, `_PASSWORD`, `_TOKEN`. Folgen: die Datei liegt **nur hier auf DEV**, im Klartext, wird nicht deployt und ist nicht ignoriert — trag sie in `.gitignore` ein und committe sie nie; für PROD dieselben Schlüssel in `.env.example` deklarieren, die Werte in den Env-Editor. ## 3. Vorschau im Browser läuft über einen Proxy Deine Entwicklungs-Server sind nicht direkt erreichbar. VCH erkennt lauschende Ports und stellt zwei Wege bereit: 1. **Subdomain** (bevorzugt): `https://lxc-./` — deine App liegt auf der Wurzel, alles verhält sich normal. 2. **Pfad-Proxy** (Rückfallweg): `https:///proxy//p//` — deine App liegt hinter einem Pfadpräfix. Damit beides funktioniert: **relative Pfade verwenden.** - `css/style.css` ✅ — nicht `/css/style.css` ❌ - `fetch('api/daten')` ✅ — nicht `fetch('/api/daten')` ❌ - Wenn absolute Pfade unvermeidbar sind: `` in den ``. Rahmenwerke: **Vite** `base: './'` · **Create React App** `"homepage": "."` in der `package.json` · **Next.js** Vorgabe belassen (Subdomain) oder `basePath` setzen (Pfad-Proxy) · **Express** `express.static` genügt, solange das HTML relativ verweist. ## 4. Dateien, die VCH im Projektstamm liest | Datei | Pflicht | Wozu | |---|---|---| | `README.md` | ja | Audit-Prüfung `readme` — blockiert die Veröffentlichung | | `.vch-description` | ja | Audit-Prüfung `description`; der Text steht später im Showcase | | `.env.example` | wenn die App Umgebungsvariablen liest | die **Deklaration** aller Variablen (Abschnitt 7) | | `.vch/deploy.yaml` | wenn die Autoerkennung nicht reicht | wie gebaut und gestartet wird (Abschnitt 6) | | `CLAUDE.md` | — | dein Projektwissen; VCH fasst sie nicht mehr an | `README.md` muss beschreiben, **was** das Projekt tut, wie man es installiert, wie man es startet und worauf es aufbaut. Die unveränderte Vorlage besteht die Prüfung nicht. `.vch-description` ist **nicht technisch**: was das Projekt tut, welches Problem es löst, für wen. Kein Technologie-Stapel, keine Installationsanleitung — die stehen im README. Halte beide aktuell, wenn sich Zweck oder Aufbau ändern. `.vch-description` legt VCH beim Anlegen als **Platzhalter** an, der die Prüfung nicht besteht — dass die Datei existiert, heißt nicht, dass sie fertig ist. **VCH-eigen, nicht anfassen:** `.vch/PLATFORM.md`, `~/.vch_env`, `~/.config/vch/` und `/etc/profile.d/vch-opencode.sh`. Wer „die Shell-Umgebung aufräumt", kappt sich den AI-Server. ## 5. Der Port gehört VCH VCH weist jedem Projekt auf der PROD-Maschine einen festen Port zu (Bereich `13000–13999`) und hält ihn über alle Deploys hinweg stabil. Du wählst ihn nicht und stimmst dich mit niemandem ab. **Deine App MUSS auf `PORT` aus der Umgebung lauschen:** - Node/Express: `app.listen(process.env.PORT || 3000)` - Next.js: `next start` beachtet `PORT` von selbst - Python: `gunicorn app:app -b 0.0.0.0:$PORT` bzw. `uvicorn main:app --port $PORT` - Go/andere: den Wert aus der Umgebung lesen, nicht hartkodieren Ein fest verdrahteter Port läuft hier und scheitert dort am Healthcheck. `PORT` und `VCH_DATA_DIR` sind **VCH-eigene** Schlüssel: setzt du sie selbst — in `.env.example`, im Env-Editor oder im Compose — wird dein Wert verworfen. ## 6. `.vch/deploy.yaml` — die vollständige Referenz Ohne diese Datei versucht VCH, das Projekt selbst zu erkennen (Abschnitt 6.3). Reicht das nicht, bricht der Deploy ab **und legt dir eine fertige Vorlage vor**, die du nur noch committen musst. ### 6.1 Alle Felder | Feld | Typ | Vorgabe | Bedeutung | |---|---|---|---| | `runtime` | `auto` \| `systemd` \| `docker` | `auto` | Wie gestartet wird. `auto`: Dockerfile oder Compose vorhanden → `docker`, sonst `systemd`. | | `build` | Zeichenkette | — | Shell-Befehl im Release-Verzeichnis. **Nur `systemd`** — bei `docker` baut das Dockerfile. | | `start` | Zeichenkette | — | Startbefehl des Dienstes. **Nur `systemd`.** Muss `$PORT` binden. | | `port` | Ganzzahl 1–65535 | `3000` | **Nur `docker`:** der Port, auf dem deine App **im Container** lauscht. VCH bildet den zugewiesenen Host-Port darauf ab. | | `health` | Pfad | `/` | Pfad, der 2xx oder 3xx liefert, sobald die App bereit ist. | | `env` | Liste von Zeichenketten | `[]` | Zusätzliche Variablennamen, die VCH ausliefern soll. Wird mit den Schlüsseln aus `.env.example` vereinigt. | | `compose` | Pfad | — | Welche Compose-Datei gilt, wenn mehrere gefunden werden. | Unbekannte Felder und Tippfehler in `runtime` brechen den Deploy mit einer benannten Fehlermeldung ab — sie werden nicht still ignoriert. ### 6.2 Beispiele Node mit eigenem Build (systemd): ```yaml runtime: systemd build: "npm ci && npm run build" start: "npm start" health: / ``` Python (systemd) — beachte `$PORT` im Startbefehl: ```yaml runtime: systemd build: "python3 -m venv .venv && .venv/bin/pip install -r requirements.txt" start: ".venv/bin/gunicorn app:app -b 0.0.0.0:$PORT" health: / ``` Statische Seite (systemd) — irgendetwas muss lauschen: ```yaml runtime: systemd build: "npm ci && npm run build" start: "python3 -m http.server $PORT --directory dist" ``` Monorepo, Code in einem Unterordner: ```yaml runtime: systemd build: "cd backend && npm ci && npm run build" start: "cd backend && npm start" ``` Dockerfile: ```yaml runtime: docker port: 3000 # der Port INNERHALB des Containers health: / ``` Mehrere Compose-Dateien im Repository: ```yaml runtime: docker compose: docker/compose.yaml ``` ### 6.3 Was ohne die Datei passiert In dieser Reihenfolge: 1. Ein echtes `Dockerfile` gewinnt → `runtime: docker` mit dem Image. 2. Eine Compose-Datei, die `${VCH_PORT}` veröffentlicht → Compose-Deploy. 3. Eine Compose-Datei, die **nur Infrastruktur** startet (Postgres, Redis, … und keinen App-Dienst), wird als Entwicklungs-Beiwerk erkannt und **nicht** deployt — VCH sucht weiter nach der Sprache. 4. Eine Compose-Datei mit App-Dienst, die `${VCH_PORT}` **nicht** veröffentlicht → Abbruch mit Vorlage (Abschnitt 13). 5. `package.json` im Stamm: `build`-Skript → Build, `start`-Skript bzw. `main` → Start. Paketmanager wird am Lockfile erkannt (`pnpm`/`yarn`/`npm`). 6. Alles andere — Python, PHP/Laravel, Go, reines Frontend ohne `start`, Code nur in Unterordnern — endet mit einer passenden Vorlage zum Committen. ## 7. Umgebungsvariablen: `.env.example` ist die Deklaration VCH liest keine Umgebungsvariablen aus deinem Code. **Was in einer Beispieldatei steht, existiert; alles andere nicht.** - Erkannte Dateinamen: `.env.example`, `.env.sample`, `.env.template` sowie dieselben ohne führenden Punkt (`env.example`, …). Der erste Fund je Schlüssel gewinnt. - Gesucht wird **im ganzen Repository** (ohne `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `vendor`, `coverage`, `.turbo`), Stammdateien zuerst, höchstens 25 Dateien. - Jede gefundene Beispieldatei bestimmt ein Ziel: `backend/.env.example` → VCH schreibt die Produktionswerte nach `backend/.env`. Für Monorepos ist das der Weg, mehrere `.env` zu bekommen. **Was den Deploy blockiert:** ein Schlüssel, der **keinen Wert nennt** (`KEY=`) und für den noch kein Produktionswert hinterlegt ist. Nicht blockierend sind: ```dotenv API_URL=https://beispiel.test # Vorgabewert vorhanden OPTIONAL_TOKEN="" # ausdrücklich leer erlaubt FEATURE_FLAG= # optional ``` Der Marker muss direkt hinter dem `#` stehen (`# optional`); Fließtext, in dem das Wort vorkommt, zählt nicht. Die Vorgabewerte sind **Hinweise für den Menschen** — in Produktion wird nichts davon verwendet. Im Zweifel: ```bash grep -RhoE "process\.env\.[A-Z_]+" src/ | sort -u ``` und jeden Treffer in die `.env.example` schreiben. **Bei Docker:** nur Variablen mit dem Präfix `NEXT_PUBLIC_`, `VITE_` oder `PUBLIC_` gehen als `--build-arg` in den Build (sie sind ohnehin im Auslieferungsstand sichtbar) — dafür brauchst du passende `ARG`-Zeilen im Dockerfile. Alle übrigen Werte kommen erst zur Laufzeit über `--env-file` und landen nie im Image. ## 8. Datenbank Deklariert deine App Verbindungsvariablen, bietet VCH eine verwaltete Datenbank an und trägt die Werte **automatisch** in genau die Schlüssel ein, die du deklariert hast. Du musst deine Variablennamen also nicht ändern — VCH erkennt sie. **Was du bekommst:** genau **eine** verwaltete Datenbank je Projekt — Postgres 16 oder MySQL 8 als eigener Container auf der PROD-Maschine, Host `127.0.0.1`, Port aus `15000–15999`, nicht aus dem LAN erreichbar. Datenbank und Benutzer heißen `app`; der Mensch kann beim Anlegen unter „Erweitert" eigene Werte wählen (Kleinbuchstaben, Ziffern, Unterstrich, Buchstabe vorne; reservierte Namen und das Präfix `pg_` sind gesperrt) — **nur beim Anlegen**, danach nicht mehr. Das Passwort wird erzeugt oder gewählt und beim Deploy in deine Schlüssel eingetragen; deklarierst du keine erkennbaren Schlüssel, zeigt die Karte im Projektarbeitsplatz die Verbindung samt Passwort zum Kopieren. Auf DEV gibt es diese Datenbank nicht (Wegweiser). Migrationen macht deine App im `build` oder `start`. Backup: der Abzug liegt im Projekt-Backup, vor dem Löschen zieht VCH einen Pflicht-Abzug. Solange die Datenbank läuft, werden DB-Zeilen im Env-Editor gestrichen — dein eigener Wert hält nicht. Deklarierst du **zwei** Verbindungen, landen beide auf dieser einen Datenbank: dann Compose (Wegweiser). **Erkannt wird** (Groß-/Kleinschreibung egal): - Verbindungszeichenketten: `DATABASE_URL`, `DB_URL`, jeder Name auf `_DSN` - Einzelteile mit Familienwort (`DB`, `DATABASE`, `POSTGRES`, `MYSQL`) plus Rolle: `..._HOST`, `..._PORT`, `..._USER`/`..._USERNAME`, `..._PASSWORD`/`..._PASS`, `..._NAME`/`..._DATABASE`/`..._DB` - Die üblichen nackten Namen: `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE`, `POSTGRES_DB` **Bewusst nicht erkannt** (und deshalb nie überschrieben): - alles mit `ROOT` im Namen (`POSTGRES_ROOT_PASSWORD`, `MYSQL_ROOT_PASSWORD`) — das sind die Zugangsdaten des Datenbankservers, nicht die deiner App - `SENTRY_DSN` und verwandte Fehlermelder (`GLITCHTIP`, `BUGSNAG`, `ROLLBAR`, `AIRBRAKE`, `RAYGUN`, `APPSIGNAL`) — ein DSN ist dort keine Datenbank - `HISTORY_DB=/data/verlauf.sqlite` und Ähnliches: ein `..._DB` ohne Datenbank-Familienwort im übrigen Namen gilt nicht als Verbindung Die **Engine** (Postgres oder MySQL) leitet VCH aus deinen Schlüsselnamen und dem Schema in `DATABASE_URL` ab; ist das mehrdeutig, entscheidest du im Projektarbeitsplatz. **Bringt dein Compose seine eigene Datenbank mit**, startet VCH sie mit und bietet **keine** zweite an. Achte dann selbst auf ein dauerhaftes Volume — ein Datenbankdienst ohne Volume verliert seine Daten beim nächsten Neubau, und VCH warnt nur. Eine Compose-Datei, die **nur** eine Datenbank startet, ist Entwicklungs-Beiwerk: sie läuft in Produktion nicht mit. Nimm dort die verwaltete Datenbank. ## 9. Dauerhafte Daten: `VCH_DATA_DIR` Jedes Release liegt in einem eigenen Verzeichnis und verschwindet wieder. Was überleben soll — SQLite-Datei, Uploads, erzeugte Dateien — gehört in das Verzeichnis aus **`VCH_DATA_DIR`**. ```js const datenVerzeichnis = process.env.VCH_DATA_DIR || './data'; ``` VCH setzt die Variable bei allen Betriebsarten (bei Docker als Einhängepunkt `/data`). **Lies sie zur Laufzeit**, schreibe den Pfad nirgends fest und trage sie nicht in `.env`-Dateien ein. Ein Verzeichnis relativ zum Release (`./data`) ist beim nächsten Deploy weg. Der Prüfling während des Audits (Abschnitt 10) bekommt diesen Speicher **nicht** — er läuft gegen einen leeren. Das ist Absicht: zwei Prozesse dürfen nicht gleichzeitig auf dieselbe SQLite-Datei schreiben. ## 10. Wie ein Deploy abläuft ``` Klonen → Bauen → Healthcheck (Prüfling) → Audit → Umschalten → Live ``` 1. **Klonen.** VCH klont den Deploy-Branch auf die PROD-Maschine — genau den gepushten Stand. 2. **Torprüfung.** Fehlt ein blockierender Wert aus `.env.example` (Abschnitt 7), endet es hier, bevor irgendetwas läuft. 3. **Bauen** nach `.vch/deploy.yaml` bzw. Autoerkennung. 4. **Healthcheck.** Der neue Stand startet als **Prüfling** auf einem freien Port aus dem Bereich `14000–14999`, neben der weiterhin laufenden alten Fassung. VCH ruft dort deinen `health`-Pfad auf: rund 30 Sekunden lang, alle 1,5 s ein Versuch, Erfolg beim ersten 2xx/3xx. Bis dahin muss die App antworten. 5. **Audit** (Abschnitt 11) gegen den deployten Commit. 6. **Umschalten.** Erst wenn das Audit besteht, wird der Prüfling zur laufenden Fassung auf dem stabilen Port. **Ein durchgefallenes Audit heißt: der alte Stand bleibt live.** Vor dem Umschalten legt VCH einen Datenschnappschuss an. Geht das Umschalten schief, werden erst die Daten und dann der Code zurückgerollt. Frühere Releases lassen sich im Projektarbeitsplatz per Rollback zurückholen. **Auto-Deploy:** ist er eingeschaltet, löst jeder Push auf den konfigurierten Deploy-Branch einen Deploy aus. Pushes auf andere Branches werden ignoriert. ## 11. Audit — was geprüft wird Das Audit läuft nicht hier, sondern in einem eigenen Runner gegen einen **sauberen Checkout des deployten Commits**. Nicht Gepushtes wird nicht geprüft — und ein `.gitignore`-Eintrag macht eine Datei für das Audit unsichtbar. | Prüfung | Schwere | Darf entfallen | |---|---|---| | `readme` | Fehler | nein | | `secrets` | Fehler | nein | | `description` | Warnung | nein | | `lint` | Fehler | ja | | `typescript` | Fehler | ja | | `npm-audit` | Fehler | ja | - Eine **Warnung** blockiert nie. `description` hält dich also nicht auf, kostet dich aber den Platz im Showcase. - „Darf entfallen" heißt: die Prüfung meldet `skipped`, wenn sie auf dein Projekt nicht passt (kein TypeScript, kein npm). Eine Prüfung, die **nicht** entfallen darf, aber trotzdem `skipped` meldet, gilt als **durchgefallen** — sie ist nie gelaufen. - `secrets` sucht eingecheckte Zugangsdaten. Echte Werte gehören nie ins Repository: in die `.env.example` nur Beispiele, die echten Werte in den Env-Editor von VCH. ## 12. Veröffentlichen - **Intern erreichbar** ist dein Projekt, sobald ein Deploy läuft — ohne Audit, ohne weiteren Schritt, unter `http://:`. - **Öffentlich** unter einer Subdomain mit SSL wird es erst mit laufendem Deploy **und** bestandenem Audit für genau diesen Deploy. Die Subdomain kommt oben drauf; entfernst du sie wieder, bleibt das Projekt intern erreichbar. - Im **Showcase** erscheinen veröffentlichte Projekte mit bestandenem Audit und einer eingecheckten `.vch-description`. ## 13. Compose — die vier Regeln, an denen es sonst scheitert 1. **Der App-Dienst muss seinen Port als `ports: ["${VCH_PORT}:"]` veröffentlichen.** VCH setzt `VCH_PORT` und kann so einen Prüfling neben deiner laufenden Fassung starten. Ohne diese Zeile bricht der Deploy ab. 2. **VCH führt genau EINE Compose-Datei aus — keine `-f`-Ketten.** Gesucht wird `compose.yaml`/`compose.yml`/`docker-compose.yaml`/`docker-compose.yml` im Stamm **oder in einem Unterordner**. Eine Override-Datei (`docker-compose.ports.yml`, `*.override.yml`) wird **ignoriert** — die `${VCH_PORT}`-Zeile gehört in die Hauptdatei. Bei mehreren Fundstellen: `compose: ` in die `.vch/deploy.yaml`. 3. **Jeder Dienst, der Produktionswerte braucht, bekommt `env_file: [.env]`.** VCH schreibt deine Werte in eine `.env` neben der Compose-Datei. 4. **Der ganze Stapel startet.** Datenbanken, Caches und Ähnliches aus deinem Compose laufen automatisch mit — eine zusätzliche verwaltete Datenbank brauchst du dafür nicht. Dienste hinter einem `profiles:`-Schlüssel bleiben aus.