Nieuw: scripts/install.sh — installeert de stack in zeven zichtbare stappen
met drie vragen, standaard in /srv/server-up (aan te passen met
ROOSTERWIJS_DIR). Het script:
- controleert git, docker, de compose-plugin en of de daemon bereikbaar is;
- maakt de doelmap aan, of legt uit welk commando daarvoor nodig is als de
rechten ontbreken (roept zelf geen sudo aan);
- kloont of werkt de checkout bij;
- laat kiezen waar de gegevens komen (zie hieronder);
- genereert .env met een willekeurige DJANGO_SECRET_KEY en een sterk
databasewachtwoord, en zet hostnaam, CSRF-origin en DJANGO_SECURE op basis
van twee vragen; een bestaande .env wordt nooit zomaar overschreven;
- bouwt en start de containers;
- meldt of er een account is en biedt aan er een aan te maken.
Opslagkeuze bij de installatie:
- docker-compose.yml gebruikt nu ${DATA_DB}, ${DATA_MEDIA} en ${DATA_STATIC}.
Een naam = Docker-volume, een pad = gewone map naast de code.
- De standaardwaarden zijn de bestaande named volumes, dus installaties die
al draaien veranderen niet en er gaan geen gegevens verloren.
- Kies je voor mappen, dan komt alles onder /srv/server-up/data/.
Verder:
- .gitignore: /data/ uitgesloten. Zonder dat zou de gegevensmap als lokale
wijziging gelden en zou deploy.sh weigeren te draaien.
- deploy.sh maakt de gegevensmappen aan als de opslagkeuze paden gebruikt,
zodat Docker ze niet als root aanmaakt.
- DEPLOY.md en README: installatie via het script, de opslagkeuze in een
tabel, en de dump/restore-stappen die nodig zijn bij het omzetten van
volumes naar mappen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAQ4Np13v8fwbTtKHKnwDz
9.5 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
Het installatiescript doet alles hieronder in zeven stappen en stelt drie
vragen. Standaard komt alles in /srv/server-up.
git clone <jouw-forgejo-url>/roosterwijs.git /tmp/roosterwijs-install
/tmp/roosterwijs-install/scripts/install.sh
Heb je nog geen schrijfrechten op /srv, dan zegt het script welk commando je
eenmalig als beheerder moet draaien. Een andere map kan met:
ROOSTERWIJS_DIR=/pad/naar/map ./scripts/install.sh
Het script controleert Docker, haalt de code op, vraagt waar de gegevens moeten
staan, genereert .env met een willekeurige DJANGO_SECRET_KEY en een sterk
databasewachtwoord, bouwt de containers en biedt aan een beheerder aan te maken.
De opslagkeuze
Bij stap 4 kies je waar database, media en statics terechtkomen:
| Keuze | Waar | Wanneer |
|---|---|---|
| 1. Docker-volumes | door Docker beheerd (docker volume ls) |
standaard; afgeschermd, maar alleen bereikbaar via docker-commando's |
| 2. Gewone mappen | /srv/server-up/data/ |
alles zichtbaar naast de code, makkelijk te back-uppen en in te zien |
De keuze staat als DATA_DB, DATA_MEDIA en DATA_STATIC in .env en is
later te wijzigen. Let op: omzetten verplaatst geen gegevens. Wissel je van
volumes naar mappen, maak dan eerst een dump en zet die daarna terug:
docker compose exec db pg_dump -U rooster rooster > ~/rooster.sql # vóór het omzetten
# ... DATA_* aanpassen in .env, dan:
docker compose down && docker compose up -d db
cat ~/rooster.sql | docker compose exec -T db psql -U rooster rooster
docker compose up -d
Zonder die stap start de app met een lege database — de oude gegevens staan dan nog wel in het oude volume, maar worden niet meer gebruikt.
Handmatig, zonder script
git clone <jouw-forgejo-url>/roosterwijs.git /srv/server-up
cd /srv/server-up
cp .env.example .env
nano .env
Belangrijk om aan te passen:
DJANGO_SECRET_KEY— genereer metopenssl rand -base64 48DJANGO_ALLOWED_HOSTS— de hostnaam/IP van de serverDJANGO_CSRF_TRUSTED_ORIGINS— exact de origin die de browser gebruikt, dushttps://<host>achter TLS (ofhttp://<host>:8080zonder TLS)POSTGRES_PASSWORD— een sterk wachtwoordDATA_DB/DATA_MEDIA/DATA_STATIC— zie de tabel hierboven
.env staat in .gitignore en 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.