# Deploy-workflow (kort) > **Belangrijk — gebruik de scripts.** Eerdere mislukte pushes kwamen door een > achtergebleven `.git/index.lock` die `git commit` blokkeerde (dan valt er > niets te pushen en zegt git "up-to-date"). De scripts ruimen dat automatisch > op en **controleren** of de push echt is doorgekomen. **1. Op je dev-machine** (Windows / PowerShell), in de repo-map: ```powershell .\scripts\push.ps1 "korte omschrijving van de wijziging" ``` Dit verwijdert een eventuele lock, commit (alleen als er iets is), pusht naar `origin/` en meldt **OK** of **LET OP** (met reden) — geen stille mislukkingen meer. Krijg je "LET OP", dan staat de oorzaak in de git-uitvoer erboven (meestal een SSH-/sleutelprobleem). **2. Op de test-server**, in de repo-map: ```bash ./scripts/deploy.sh ``` Dit controleert eerst of Docker en `.env` aanwezig zijn, haalt dan de laatste code op (`git reset --hard origin/`), herbouwt de containers (incl. migraties via de entrypoint) en controleert daarna of de frontend-bundel er staat, of de backend antwoordt en of er een actief account is om mee in te loggen. Staan er **lokale wijzigingen** in de map op de server, dan stopt het script: `git reset --hard` zou die weggooien. Zet ze eerst veilig, of forceer bewust: ```bash ./scripts/deploy.sh --force ``` Nieuwe **modules** verschijnen daarna pas in de app nadat je ze aanzet via **⚙ → Modulebeheer**. --- # E-mail aanzetten (wachtwoordreset-links) Zonder configuratie gaan e-mails naar de **console-log** van de backend-container (zichtbaar met `docker compose logs backend`) — prima om te testen. Voor échte e-mail via SMTP zet je in `.env` op de server: ``` DJANGO_EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend EMAIL_HOST=smtp.jouwprovider.nl EMAIL_PORT=587 EMAIL_HOST_USER=postvak@jouwschool.nl EMAIL_HOST_PASSWORD=... EMAIL_USE_TLS=1 DEFAULT_FROM_EMAIL=Roosterwijs FRONTEND_BASE_URL=https://rooster.example.nl ``` `FRONTEND_BASE_URL` bepaalt de basis van de resetlink in de e-mail (zonder `FRONTEND_BASE_URL` wordt die afgeleid uit het verzoek). Daarna `docker compose up -d` (of `./scripts/deploy.sh`) om de nieuwe waarden te laden. --- # Draaien op de test-server (Docker) De stack bestaat uit drie containers: | Container | Rol | |-----------|-----| | `db` | PostgreSQL (data in een Docker-volume) | | `backend` | Django via gunicorn | | `nginx` | serveert de React-frontend en stuurt `/api` + `/admin` door naar de backend | Je hoeft op de server **niets** te installeren behalve Docker zelf (met de `docker compose`-plugin). Python, Node, Postgres enz. zitten in de images. ## Eenmalig: server klaarzetten 1. Zorg dat Docker + Compose op de test-server staan: ```bash docker --version docker compose version ``` 2. Haal de code op: ```bash git clone /roosterwijs.git cd roosterwijs ``` 3. Maak het `.env`-bestand aan vanuit het voorbeeld en vul echte waarden in: ```bash cp .env.example .env nano .env ``` Belangrijk om aan te passen: - `DJANGO_SECRET_KEY` — genereer met `python3 -c "import secrets; print(secrets.token_urlsafe(50))"` - `DJANGO_ALLOWED_HOSTS` — de hostnaam/IP van de test-server - `DJANGO_CSRF_TRUSTED_ORIGINS` — exact de origin die de browser gebruikt, dus `https://` achter TLS (of `http://:8080` zonder TLS) - `POSTGRES_PASSWORD` — een sterk wachtwoord `.env` staat in `.gitignore` en komt dus **niet** in git — dat hoort zo. ## Starten ```bash docker compose up -d --build ``` Wat er dan gebeurt: Postgres start, de backend wacht tot de database er is, voert automatisch de migraties en `collectstatic` uit, en gunicorn + nginx komen op. De app is bereikbaar op: ``` http://:8080 ``` (Poort `8080` staat ingesteld in `docker-compose.yml` bij de `nginx`-service; pas hem daar aan als die poort bezet is.) ### Eerste keer: beheerder aanmaken ```bash docker compose exec backend python manage.py createsuperuser ``` Dit account heb je **hoe dan ook** nodig: de hele API staat op `IsAuthenticated`, dus zonder gebruiker kom je ook de app zelf niet in — niet alleen `/admin/`. Zonder dit commando wordt er nergens automatisch een account aangemaakt; `entrypoint.sh` draait alleen migraties en `collectstatic`. ## Updaten na nieuwe code ```bash git pull docker compose up -d --build ``` Migraties draaien automatisch mee bij het opstarten van de backend. ## Handige commando's | Doel | Commando | |------|----------| | Logs volgen | `docker compose logs -f` | | Alleen backend-logs | `docker compose logs -f backend` | | Status containers | `docker compose ps` | | Stoppen | `docker compose down` | | Stoppen **incl. database wissen** | `docker compose down -v` (let op: gegevens weg!) | | Migratie handmatig | `docker compose exec backend python manage.py migrate` | | Django-shell | `docker compose exec backend python manage.py shell` | ## Aandachtspunten - **HTTPS/domein:** deze opzet draait op poort 8080 zonder TLS. Voor productie zet je er een reverse proxy (bv. Caddy of Traefik) vóór die HTTPS regelt, of je breidt de nginx-config uit met certificaten. De nginx-container is daar al het logische punt voor. **Zet daarna `DJANGO_SECURE=1` in `.env`** — dat activeert veilige cookies, HSTS en een https-redirect in Django. Zie hieronder wat er dan verder moet kloppen. ### Draaien achter TLS: checklist Met `DJANGO_SECURE=1` moeten drie dingen kloppen, anders lukt **inloggen niet** terwijl het inlogscherm zelf gewoon laadt (dat wordt door nginx geserveerd, zonder tussenkomst van Django). 1. **De proxy moet `X-Forwarded-Proto: https` meesturen.** Django leest die header (`SECURE_PROXY_SSL_HEADER`) om te bepalen of de verbinding veilig is. Ontbreekt hij, dan denkt Django dat het http is en stuurt het elk verzoek naar `/api` en `/admin` door naar https — waarna de proxy het weer als http aanlevert: een oneindige redirect-lus (`ERR_TOO_MANY_REDIRECTS`) en dus een mislukte login. Caddy en Traefik zetten deze header standaard; controleer het als je een eigen nginx/Apache ervoor hebt. 2. **`DJANGO_CSRF_TRUSTED_ORIGINS` moet de https-origin zijn**, exact zoals de browser hem gebruikt (bv. `https://rooster.example.nl`, zonder poort op 443). Dit geldt voor de **Django-admin** en andere gewone Django-formulieren: staat hier een `http://`-adres, dan weigert `/admin/` je aanmelding met een CSRF-fout (HTTP 403). Het inloggen in de app zelf loopt hier níet op stuk — DRF-views zijn `csrf_exempt` en `SessionAuthentication` controleert CSRF pas als er al een sessie is. 3. **`DJANGO_ALLOWED_HOSTS` moet de publieke hostnaam bevatten**, anders volgt een 400 Bad Request. Controleren of de header goed aankomt: ```bash docker compose logs backend | grep -i "Mislukte inlogpoging" # bereikt de login de backend? curl -sI https:///api/auth/me/ | head -n 1 # 200/401 = goed, 301 = lus ``` Krijg je bij die `curl` een `301`, dan komt `X-Forwarded-Proto` niet goed door. - **Beperk poort 8080 tot de proxy:** in `docker-compose.yml` staat `"8080:80"`, wat op álle netwerkinterfaces luistert. Daardoor is de app ook rechtstreeks via `http://:8080` te bereiken — buiten TLS om. Draait je reverse proxy op dezelfde machine, maak er dan `"127.0.0.1:8080:80"` van. Draait de proxy op een andere host, laat het dan staan en zet er een firewall op: nginx vertrouwt de `X-Forwarded-Proto` van de aanroeper. - **Inloggen verplicht:** de hele API vereist een ingelogde gebruiker; de frontend toont eerst een inlogscherm. Maak gebruikers aan via `createsuperuser` (zie boven) of in de Django-admin. Modulebeheer is voorbehouden aan beheerders (`is_staff`). - **Backups:** de database leeft in het volume `pgdata`. Maak hiervan backups, bijvoorbeeld met `docker compose exec db pg_dump -U rooster rooster > backup.sql`. - **`DJANGO_DEBUG` blijft 0** op de server. Zet hem nooit op 1 in productie. - **Test eerst hier, dan productie:** bouw het image één keer, test op deze server, en draai exact dezelfde code/compose op de productieserver met een eigen `.env`.