Inloggen mislukte met DJANGO_SECURE=1 achter een reverse proxy die TLS afhandelt, terwijl het inlogscherm zelf wel laadde. - nginx.conf gaf `X-Forwarded-Proto $scheme` door. Achter een TLS-proxy komt het verzoek als http binnen, dus overschreef nginx de `https` van de proxy met `http`. Django zag de verbinding daardoor als onveilig en stuurde elk verzoek naar /api en /admin door naar https, waarna de proxy het weer als http aanleverde: een oneindige redirect-lus. - nginx neemt nu de X-Forwarded-Proto van de proxy over via een map, en valt alleen terug op $scheme als die header ontbreekt. Werkt daardoor zowel achter een externe proxy als bij TLS op nginx zelf. - .env.example: voorbeelden voor CSRF-origin en frontend-URL op https gezet, DJANGO_SECURE standaard op 1, met uitleg waarom een http-origin het inloggen met een CSRF-fout laat stranden. - DEPLOY.md: checklist "Draaien achter TLS" toegevoegd met de drie vereisten (proxy-header, CSRF-origin, allowed hosts), commando's om te controleren of de header aankomt, en het advies poort 8080 aan de loopback te binden zodat de app niet buiten TLS om bereikbaar is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FAQ4Np13v8fwbTtKHKnwDz
7.2 KiB
Deploy-workflow (kort)
Belangrijk — gebruik de scripts. Eerdere mislukte pushes kwamen door een achtergebleven
.git/index.lockdiegit commitblokkeerde (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:
.\scripts\push.ps1 "korte omschrijving van de wijziging"
Dit verwijdert een eventuele lock, commit (alleen als er iets is), pusht naar
origin/<branch> 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:
./scripts/deploy.sh
Dit haalt de laatste code op (git reset --hard origin/<branch>), herbouwt de
containers (incl. migraties via de entrypoint) en controleert of er een nieuwe
frontend-bundel staat.
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 <noreply@jouwschool.nl>
FRONTEND_BASE_URL=http://rooster-test.example.nl:8080
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
-
Zorg dat Docker + Compose op de test-server staan:
docker --version docker compose version -
Haal de code op:
git clone <jouw-forgejo-url>/Roostersoftware.git cd Roostersoftware -
Maak het
.env-bestand aan vanuit het voorbeeld en vul echte waarden in:cp .env.example .env nano .envBelangrijk om aan te passen:
DJANGO_SECRET_KEY— genereer metpython3 -c "import secrets; print(secrets.token_urlsafe(50))"DJANGO_ALLOWED_HOSTS— de hostnaam/IP van de test-serverDJANGO_CSRF_TRUSTED_ORIGINS—http://<host>:8080(of je echte URL)POSTGRES_PASSWORD— een sterk wachtwoord
.envstaat in.gitignoreen komt dus niet in git — dat hoort zo.
Starten
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://<server-host>: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
docker compose exec backend python manage.py createsuperuser
Daarna kun je inloggen op http://<server-host>:8080/admin/.
Updaten na nieuwe code
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=1in.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).
- De proxy moet
X-Forwarded-Proto: httpsmeesturen. 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/apien/admindoor 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. DJANGO_CSRF_TRUSTED_ORIGINSmoet de https-origin zijn, exact zoals de browser hem gebruikt (bv.https://rooster.example.nl, zonder poort op 443). Staat hier nog eenhttp://-adres, dan geeft elke POST — inclusief het inloggen — een CSRF-fout (HTTP 403).DJANGO_ALLOWED_HOSTSmoet de publieke hostnaam bevatten, anders volgt een 400 Bad Request.
Controleren of de header goed aankomt:
docker compose logs backend | grep -i "Mislukte inlogpoging" # bereikt de login de backend?
curl -sI https://<jouw-domein>/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.ymlstaat"8080:80", wat op álle netwerkinterfaces luistert. Daardoor is de app ook rechtstreeks viahttp://<server>:8080te 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 deX-Forwarded-Protovan 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 metdocker compose exec db pg_dump -U rooster rooster > backup.sql. DJANGO_DEBUGblijft 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.