47 typed options covering the full defaults file plus the OIDC and backup-timer subsystems. The three secrets the role asserts on (db_root_password, db_password, admin_password) are marked required: true so ansible refuses the play with a clear error before the validate task even runs. Loads cleanly through ansible-core's ArgumentSpecValidator with 100% defaults/spec coverage. Matches the spec convention used by traefik, authentik, drawio, garage, nextcloud, opnform, coturn, talk and send. |
||
|---|---|---|
| .. | ||
| defaults | ||
| handlers | ||
| meta | ||
| tasks | ||
| templates | ||
| tests | ||
| vars | ||
| README.md | ||
Ansible Role: bookstack
Deploys BookStack as a self-contained Docker Compose stack behind Traefik, with its own MariaDB container, OIDC SSO (Entra ID by default) and a daily systemd-timer driven backup of database and uploads.
Requirements
- Docker Engine + Compose plugin on the target host
- Traefik already running, with the external network referenced by
bookstack_traefik_network(default:proxy) community.dockercollection on the controller- DNS for
bookstack_domainpointing at the Traefik host
Required variables
The role asserts these are set; the play fails fast if any is empty:
| Variable | Description |
|---|---|
bookstack_db_root_password |
MariaDB root password |
bookstack_db_password |
MariaDB user password |
bookstack_admin_password |
Initial local admin password |
bookstack_oidc_client_id |
Entra ID App Registration ID (if OIDC on) |
bookstack_oidc_client_secret |
Entra ID client secret (if OIDC on) |
bookstack_entra_tenant_id |
Entra tenant UUID (if OIDC on) |
Provide via OpenBao lookup, Ansible Vault or --extra-vars. Never commit
real secrets.
Optional variables
See defaults/main.yml. Frequently overridden:
bookstack_domain,bookstack_base_urlbookstack_image,bookstack_db_image(pin in production)bookstack_oidc_enabled(setfalseto disable OIDC entirely)bookstack_oidc_auto_initiate(trueredirects straight to IdP)bookstack_oidc_user_to_groups(truesyncs roles from Entra groups)bookstack_backup_enabled,bookstack_backup_schedule,bookstack_backup_retention_days
Entra ID app registration
- Azure Portal → Entra ID → App registrations → New registration
- Redirect URI (Web):
https://<bookstack_domain>/oidc/callback - Front-channel logout URL:
https://<bookstack_domain>/logout - Certificates & secrets → New client secret →
bookstack_oidc_client_secret - For group sync (
bookstack_oidc_user_to_groups: true):- Token configuration → Add groups claim → Security groups
- In BookStack, create roles whose External Auth ID equals the Entra group Object ID, so the mapping resolves on first login.
What the role does
| Phase | Action |
|---|---|
| Validate | assert all required secrets are set |
| Prepare | install packages, create volume dirs, generate persistent APP_KEY, verify Traefik network |
| Deploy | render docker-compose.yml, pull images, bring stack up |
| Configure | wait for the app, create the initial local admin via php artisan bookstack:create-admin (idempotent) |
| Backup | render /usr/local/bin/bookstack-backup.sh + systemd timer (daily 03:00, 14-day retention) |
Example playbook
- name: Deploy BookStack service
hosts: bookstack_servers
become: true
roles:
- digitalboard.core.bookstack
With inventory variables:
# group_vars/bookstack_servers.yml
bookstack_domain: wiki.digitalboard.ch
bookstack_base_url: "https://wiki.digitalboard.ch"
bookstack_entra_tenant_id: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.tenant_id }}"
bookstack_oidc_client_id: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.client_id }}"
bookstack_oidc_client_secret: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.client_secret }}"
bookstack_db_root_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.db_root_password }}"
bookstack_db_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.db_password }}"
bookstack_admin_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.admin_password }}"
Backup / restore
Backups land in {{ bookstack_backup_dir }} (default
/srv/data/bookstack/backup) with three files per run:
bookstack-db-<stamp>.sql.gz— mariadb-dumpbookstack-files-<stamp>.tar.gz— uploads, attachmentsbookstack-appkey-<stamp>.txt— APP_KEY (required for restore!)
Manual trigger: systemctl start bookstack-backup.service
Timer status: systemctl list-timers bookstack-backup.timer
Restore procedure:
- Stop the stack:
docker compose downinbookstack_docker_compose_dir - Restore the APP_KEY: copy the
.txtcontent to{{ bookstack_docker_volume_dir }}/.app_key(the key MUST match or encrypted DB values become unreadable) - Start only the DB container, then load the dump:
gunzip -c bookstack-db-<stamp>.sql.gz \ | docker exec -i bookstack-db \ mariadb -u root -p"<root-pw>" bookstack - Extract the files:
tar -xzf bookstack-files-<stamp>.tar.gz -C {{ bookstack_appdata_dir }}/www/ - Bring the stack back up:
docker compose up -d
Notes
bookstack_oidc_auto_initiate: false(default) shows a login page with an SSO button alongside the local login form. Withtrue, users go straight to the IdP — the local admin then has to usehttps://<domain>/login?email_login=1.bookstack_oidc_user_to_groups: trueonly makes sense once BookStack roles with the correct External Auth IDs (= Entra group Object IDs) exist; otherwise users lose their role assignment on every login.- Image tags default to pinned versions; bump them deliberately rather
than chasing
latest. - BookStack officially supports MySQL/MariaDB only — no PostgreSQL.
License
MIT