Inloggen, bestellingen, een dashboard met data: dan is je project een Node- of Next-app en geen statische site. Die draait bij ons in een eigen container met een eigen Postgres-database (€ 15 per maand na 14 dagen proef). Het image wordt op GitHub gebouwd, nooit op onze server, dus dit loopt altijd via een workflow in je repository:

```yaml
name: Deploy app naar 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: jij@voorbeeld.nl
          site-id: ${{ secrets.DORELLI_SITE_ID }}
          registry-user: ${{ secrets.DORELLI_REGISTRY_USER }}
          registry-password: ${{ secrets.DORELLI_REGISTRY_PASSWORD }}
```

De eerste run maakt de site en een registry-token aan, bindt de site aan je repository en zegt welke drie secrets je zet: het site-id en de twee registry-waarden. Met `permissions: id-token: write` bewijst elke volgende run dat hij uit jouw repository komt, dus er is geen deploy-token om te bewaren; de registry-waarden blijven, want docker push heeft ze nodig. In een openbare repository kan iedereen lezen wat een run laat zien, dus maak het registry-wachtwoord daar in het portaal, zoals hieronder staat. Had je in een openbare repository al een eerste publicatie? Maak dan ook een nieuw deploy-token in het portaal, en draai één run met dat token (`deploy-token`) en `bind-github: true`: een token dat ooit in een run stond, werkt dan niet meer, en de site is weer gebonden. Houd de workflow op `on: push` naar `main` en vermijd `pull_request_target`: het platform vertrouwt elke run uit jouw repository, dus een workflow die ook draait voor pull requests van vreemden laat hen jouw site publiceren. Runs die beginnen met `pull_request_target`, `issues`, `issue_comment`, `discussion` of `discussion_comment` weigert het platform sowieso. Heb je al een site met een deploy-token? Zet één run lang `bind-github: true` in de stap: het platform bindt de site dan aan je repository, en het token kan weg. Je app krijgt `DATABASE_URL` en `PORT` (standaard 3000) als omgevingsvariabelen. Geen Dockerfile nodig: wij gebruiken dan `npm ci`, `npm run build`, `npm start`. Eigen Dockerfile? Die wordt gewoon gebruikt. Registry-wachtwoord kwijt, of faalt een run bij docker login? Druk in het portaal op het tabblad Agents van je app, onder Registry-token, op Nieuw wachtwoord, en zet het wachtwoord in het secret `DORELLI_REGISTRY_PASSWORD`; de gebruiker blijft dezelfde, en alleen het laatst getoonde wachtwoord werkt.

Eigen secrets, zoals een API-sleutel: zet ze in de secrets van je repository en geef de stap een blok `secrets`, één `NAAM=waarde` per regel, bijvoorbeeld `API_KEY=${{ secrets.API_KEY }}`. Ze gaan alleen naar je container en komen binnen als omgevingsvariabelen, vanaf de tweede run, als de container er staat; wij bewaren de namen, nooit de waarden. Namen zijn hoofdletters, cijfers en `_`, hooguit 20 per app; `PORT`, `DATABASE_URL`, `POSTGRES_URL`, `REDIS_URL`, `STORAGE_CONNECTION_STRING` (ook met een achtervoegsel) en `DORELLI_*` zetten wij. Secrets werken op onze eigen Azure-plekken; een site die stilstaat krijgt ze pas als hij weer draait.

De app schaalt naar nul bij geen bezoek; het eerste bezoek na een stille periode duurt 10 tot 30 seconden, zie [Koude starts](/docs/apps/cold-start). Logs, herstart en omgevingsvariabelen staan in je portaal, zie [Logs, herstart en variabelen](/docs/apps/portal).