Logins, Bestellungen, ein Dashboard mit Daten: Dann ist dein Projekt eine Node- oder Next-App und keine statische Seite. Die läuft bei uns in einem eigenen Container mit eigener Postgres-Datenbank (€ 15 im Monat nach 14 Tagen Testphase). Das Image wird auf GitHub gebaut, nie auf unserem Server, also läuft das immer über einen Workflow in deinem Repository:

```yaml
name: Deploy app to Dorelli
on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: dorelli-cloud/action@v1
        with:
          plan: app
          email: you@example.com
          site-id: ${{ secrets.DORELLI_SITE_ID }}
          registry-user: ${{ secrets.DORELLI_REGISTRY_USER }}
          registry-password: ${{ secrets.DORELLI_REGISTRY_PASSWORD }}
```

Der erste Lauf legt die Seite und ein Registry-Token an, bindet die Seite an dein Repository und sagt dir, welche drei Secrets du setzt: die Site-ID und die beiden Registry-Werte. Mit `permissions: id-token: write` beweist jeder weitere Lauf, dass er aus deinem Repository kommt, also gibt es kein Deploy-Token zu verwahren; die Registry-Werte bleiben, weil docker push sie braucht. In einem öffentlichen Repository kann jeder lesen, was ein Lauf zeigt, also leg das Registry-Passwort dort im Portal an, wie unten beschrieben. Gab es in einem öffentlichen Repository schon eine erste Veröffentlichung? Dann leg im Portal auch ein neues Deploy-Token an und mach einen Lauf mit diesem Token (`deploy-token`) und `bind-github: true`: Ein Token, das einmal in einem Lauf stand, funktioniert dann nicht mehr, und die Seite ist wieder gebunden. Lass den Workflow auf `on: push` nach `main` und vermeide `pull_request_target`: Die Plattform vertraut jedem Lauf aus deinem Repository, also könnte ein Workflow, der auch für Pull Requests von Fremden läuft, sie deine Seite veröffentlichen lassen. Läufe, die mit `pull_request_target`, `issues`, `issue_comment`, `discussion` oder `discussion_comment` starten, lehnt die Plattform ohnehin ab. Hast du schon eine Seite mit Deploy-Token? Setze für einen Lauf `bind-github: true` in den Schritt: Die Plattform bindet die Seite dann an dein Repository, und das Token kann weg. Deine App bekommt `DATABASE_URL` und `PORT` (standardmäßig 3000) als Umgebungsvariablen. Kein Dockerfile nötig: Wir nutzen dann `npm ci`, `npm run build`, `npm start`. Eigenes Dockerfile? Das wird so genutzt, wie es ist. Registry-Passwort verloren, oder scheitert ein Lauf bei docker login? Drück im Portal auf dem Tab Agents deiner App unter Registry-Token auf Neues Passwort und trag das Passwort im Secret `DORELLI_REGISTRY_PASSWORD` ein; der Benutzer bleibt gleich, und nur das zuletzt angezeigte Passwort gilt.

Eigene Secrets, etwa ein API-Schlüssel: Leg sie in den Secrets deines Repositorys ab und gib dem Schritt einen Block `secrets`, ein `NAME=Wert` pro Zeile, zum Beispiel `API_KEY=${{ secrets.API_KEY }}`. Sie gehen nur an deinen Container und kommen als Umgebungsvariablen an, ab dem zweiten Lauf, wenn der Container steht; wir speichern die Namen, nie die Werte. Namen sind Großbuchstaben, Ziffern und `_`, höchstens 20 pro App; `PORT`, `DATABASE_URL`, `POSTGRES_URL`, `REDIS_URL`, `STORAGE_CONNECTION_STRING` (auch mit einem Suffix) und `DORELLI_*` setzen wir. Secrets funktionieren an unseren eigenen Azure-Standorten; eine pausierte Site bekommt sie erst, wenn sie wieder läuft.

Die App skaliert ohne Verkehr auf null; der erste Besuch nach einer stillen Phase dauert 10 bis 30 Sekunden, siehe [Kaltstarts](/docs/apps/cold-start). Logs, Neustart und Umgebungsvariablen stehen in deinem Portal, siehe [Logs, Neustart und Variablen](/docs/apps/portal).