De installatiestappen in de README waren niet uitvoerbaar en het deploy-script kon stilzwijgend werk vernietigen. README: - `python manage.py migrate` liep vast op `RuntimeError: DJANGO_SECRET_KEY ontbreekt`. DEBUG staat standaard uit en dan is die sleutel verplicht; de instructies noemden `DJANGO_DEBUG` nergens. Nu toegevoegd, met uitleg. - `createsuperuser` stond als "optioneel, voor /admin/". Dat klopt niet: de hele API staat op `IsAuthenticated`, dus zonder account kom je de app helemaal niet in. Nu gemarkeerd als verplichte stap. - Stap voor een virtual environment toegevoegd (pip installeerde anders in de systeem-Python, wat op recente distributies afketst op PEP 668). - Node-eis vermeld: Vite 8 vereist 20.19+ of 22.12+. - Verouderde beschrijvingen bijgewerkt: de tabs van het roosterscherm (Week/Genereren/Instellingen in plaats van Weekrooster/Conflicten/ Schooljaar/Instellingen), Groepsindeling is een eigen scherm en geen tab, en er zijn twaalf modules in plaats van "twee voorbeeldmodules". DEPLOY.md: - `git clone .../Roostersoftware.git` + `cd Roostersoftware` gebruikte de verkeerde repo-naam; dat is `roosterwijs`. - CSRF-origin en FRONTEND_BASE_URL stonden als http-voorbeeld; achter TLS moet dat https zijn. - Rechtgezet: `DJANGO_CSRF_TRUSTED_ORIGINS` blokkeert het inloggen in de app niet (DRF-views zijn `csrf_exempt`), maar wel de Django-admin. - Vermeld dat er nergens automatisch een account wordt aangemaakt. scripts/deploy.sh: - Stopt nu als `.env` ontbreekt; anders start de stack met lege variabelen en faalt de backend met een onduidelijke fout. - Stopt bij lokale wijzigingen in plaats van ze met `git reset --hard` weg te gooien; bewust overschrijven kan met `--force`. - Controleert vooraf of Docker en de compose-plugin er zijn. - Toont welke commits erbij komen (oud -> nieuw) in plaats van alleen de nieuwe HEAD. - Controleert na afloop of de backend antwoordt en of er een actief account bestaat, met het createsuperuser-commando als dat er niet is. frontend/Dockerfile: - Bouwt op node:22-alpine in plaats van node:20-alpine, gelijk aan de CI en ruim binnen de Node-eis van Vite 8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FAQ4Np13v8fwbTtKHKnwDz
8 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 controleert eerst of Docker en .env aanwezig zijn, haalt dan de laatste
code op (git reset --hard origin/<branch>), 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:
./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 <noreply@jouwschool.nl>
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
-
Zorg dat Docker + Compose op de test-server staan:
docker --version docker compose version -
Haal de code op:
git clone <jouw-forgejo-url>/roosterwijs.git cd roosterwijs -
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— exact de origin die de browser gebruikt, dushttps://<host>achter TLS (ofhttp://<host>:8080zonder TLS)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
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
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). Dit geldt voor de Django-admin en andere gewone Django-formulieren: staat hier eenhttp://-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 zijncsrf_exemptenSessionAuthenticationcontroleert CSRF pas als er al een sessie is.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.