- CSS 86.9%
- php 9%
- SCSS 2.4%
- Twig 1.3%
- JavaScript 0.2%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .ddev | ||
| config/sync | ||
| deploy | ||
| docker | ||
| docs | ||
| private | ||
| web | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .woodpecker.yml | ||
| BACKUP.md | ||
| CLAUDE.md | ||
| composer.json | ||
| composer.lock | ||
| Dockerfile | ||
| license.md | ||
| patches.lock.json | ||
| PRODUCT.md | ||
| readme.md | ||
| renovate.json | ||
SLB website
Drupal 11 website for SLB, built with the Bootstrap 5 base theme and a custom subtheme. Local development runs on DDEV with Nginx, PHP 8.3, and MariaDB 10.11.
Prerequisites
- DDEV
- Node.js (managed via Corepack inside DDEV)
Getting started
ddev start
ddev composer install
ddev launch
Theme development
The custom theme lives in web/themes/custom/b5_slb and uses Gulp with BrowserSync for live reload during development.
ddev ssh
cd web/themes/custom/b5_slb
npm install
npx gulp
This compiles SCSS to CSS (with Autoprefixer) and opens a BrowserSync proxy at the DDEV site URL. Changes to .scss, .php, and .twig files trigger automatic reloads.
Deployment
Production runs on Docker via Woodpecker CI (.woodpecker.yml). Pushing to main builds and deploys the prod image; pushing to staging builds and deploys the staging image.
Docker image
The Dockerfile produces a self-contained image with Nginx, PHP-FPM, and Supervisor. Runtime configuration is injected via environment variables:
| Variable | Description |
|---|---|
DRUPAL_ENV |
prod or staging — controls config split activation |
DB_HOST |
MariaDB hostname |
DB_NAME |
Database name |
DB_USER |
Database user |
DB_PASSWORD |
Database password |
DRUPAL_HASH_SALT |
Drupal hash salt |
TRUSTED_HOSTS |
Trusted host regex (e.g. ^sintlukasbasisschool\\.be$) |
Generating a hash salt
openssl rand -base64 55
Store the result as a Woodpecker secret (SLB_PROD_HASH_SALT / SLB_STAGING_HASH_SALT).
Woodpecker secrets
The following secrets must be configured in the Woodpecker repository settings:
SLB_PROD_DB_NAME,SLB_PROD_DB_USER,SLB_PROD_DB_PASSWORD,SLB_PROD_HASH_SALTSLB_STAGING_DB_NAME,SLB_STAGING_DB_USER,SLB_STAGING_DB_PASSWORD,SLB_STAGING_HASH_SALT
Trusted hosts
The TRUSTED_HOSTS env var is passed to Drupal's trusted_host_patterns setting. It must be a valid PHP regex anchored with ^ and $, with dots escaped as \\.:
- Production:
^sintlukasbasisschool\\.be$ - Staging:
^staging\\.sintlukasbasisschool\\.be$
Environment management
Settings files
Each environment loads a different settings file:
| Environment | Loads | Notes |
|---|---|---|
| Local DDEV | web/sites/default/settings.php → settings.ddev.php → settings.local.php |
All gitignored |
| Staging / Prod | docker/settings.docker.php (copied to settings.php in the image) |
Reads env vars |
settings.php— Drupal's stock file, gitignored, generated by DDEV.settings.ddev.php— DDEV-managed local DB credentials and Mailpit config.settings.local.php— local-only dev overrides (cache off, no aggregation, verbose errors, stage_file_proxy origin, activates both config splits). Never deployed.settings.docker.php— runtime config for staging/prod. Reads DB creds andDRUPAL_ENVfrom container env vars. Activates thenon_prodsplit whenDRUPAL_ENV != prod.
The Dockerfile copies settings.docker.php over settings.php at build time, so dev settings never reach the cloud.
Config sync
All exported Drupal config lives in config/sync/ (in the repo root, outside the webroot). The Docker image and local DDEV both point there:
- Local DDEV:
settings.local.phpsets$settings['config_sync_directory'] = $app_root . '/../config/sync' - Docker:
settings.docker.phpsets it to/var/www/html/config/sync
Export → commit → deploy flow:
ddev drush cex -y # export DB config to config/sync/
git add config/sync/ && git commit && git push # to staging, then merge to main
Woodpecker runs drush cim -y on every deploy.
Config splits (per-environment modules)
The config_split module manages modules that should only be enabled in some environments. Two splits exist:
| Split | Modules | Active on |
|---|---|---|
local |
browsersync, yaml_editor |
local only |
non_prod |
stage_file_proxy |
local + staging |
Activation:
- Local:
settings.local.phpsetsstatus = TRUEon both splits. - Staging:
settings.docker.phpactivatesnon_prodbased onDRUPAL_ENV=staging(set indocker-compose.staging.yml). - Prod: neither split active.
When a split is active, drush cex removes its modules from the main core.extension.yml, keeping prod config clean. drush cim on each environment then enables/disables modules according to which splits are active.
Adding a module to a split:
ddev drush config-set config_split.config_split.local module.MY_MODULE 0 -y
ddev drush cex -y
git add config/sync/ && git commit
Database sync from prod
Files are pulled on-demand via Stage File Proxy (no local copy needed). The database is pulled manually:
- Export DB from prod via phpMyAdmin (use gzip compression) or
drush sql:dumpon the server. - Save to
.backup/prod-db.sql(or.sql.gz) — gitignored. - Run:
This drops the local DB, imports the dump, runsddev pull-prod-dbdrush updatedbanddrush cr.
For staging DB syncs, copy the dump to the server and pipe into the container:
scp .backup/prod-db.sql.gz user@server:/tmp/
ssh user@server
gunzip -c /tmp/prod-db.sql.gz | docker exec -i slb-drupal-staging sh -c \
'mysql -h mariadb -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME"'
docker exec slb-drupal-staging drush updatedb -y
docker exec slb-drupal-staging drush cim -y
docker exec slb-drupal-staging drush cr
Content management
Bulk calendar import (Feeds)
The feeds module is installed to allow bulk importing of gebeurtenis (event) nodes from a CSV file via the Drupal admin UI.
First-time setup (run once after a fresh install or database restore):
-
Go to
/nl/admin/structure/feeds→ Add feed type -
Configure:
- Label:
Gebeurtenis import - Fetcher: Upload form
- Parser: CSV
- Processor: Node — Content type: Gebeurtenis
- Label:
-
Under Mapping, add:
CSV column Drupal target Note titleTitle ✓ Mark as unique key startDatum en tijd → Start Format: Y-m-d H:iorY-m-dendDatum en tijd → End Same format descriptionBeschrijving -
Save, then export config:
ddev drush cex -y
Annual import workflow (for the school admin):
- Prepare a CSV file — one row per event occurrence:
title,start,end,description Herfstvakantie,2026-10-27,2026-10-31, Oudercontacten,2026-11-12 18:00,2026-11-12 20:00,Info avond klassen 1 en 6 Zwemmen klas 1,2026-10-07 14:00,2026-10-07 15:00, Zwemmen klas 1,2026-10-14 14:00,2026-10-14 15:00, - Go to
/nl/admin/content/feed→ Add feed → choose Gebeurtenis import - Upload the CSV → click Import
- Re-importing the same file updates existing events (matched by title) instead of creating duplicates
Note: recurring events with multiple sessions (e.g. Zwemmen) should be flattened to one row per session in the CSV. Truly multi-date single nodes (e.g. a week-long holiday) can be entered manually via the smartdate recurring UI.
Tech stack
- CMS: Drupal 11
- Base theme: Bootstrap 5 (
bootstrap5) - Custom subtheme:
b5_slb - Admin theme: Gin
- Local environment: DDEV (Nginx, PHP 8.3, MariaDB 10.11)
- Build tools: Gulp, Sass (embedded), PostCSS, BrowserSync
- Dependency management: Composer, Drush