What you will set up
BookStack is a wiki for a team's documentation: routines, server notes, how-tos, onboarding. Content is sorted like a library, into shelves, books, chapters and pages, and you write in an editor that works like a word processor, or in Markdown. It keeps every version of a page, searches all text, and has an audit log of who did what.
BookStack is created by Dan Brown, a developer in the United Kingdom, and developed with its community in the open on Codeberg, a code-hosting service run by a non-profit association in Berlin. It is open source under the MIT licence. Paid support is sold by HTTP Functions Ltd, a company registered in England. In our tests it made no update check and sent no usage data. Out of the box it uses two outside services: it fetches a profile picture for every new user from Gravatar, run by Automattic in the United States, by sending a hash of the user's email address, and its diagram editor loads in the browser from diagrams.net, run by draw.io Limited in the United Kingdom. Step 3 switches both off.
BookStack publishes no container image of its own; its documentation points to two made by the community. This guide uses the one from LinuxServer.io, a group of volunteers who maintain a large set of container images. It is the first one BookStack's documentation lists, the release we tested appeared there the next day, and it runs without root here. The other one is solidnerd/bookstack.
Here BookStack runs as a pod under a user of its own called bookstack, with MariaDB for its data, behind Caddy from the Podman guide.
Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:
- BookStack 26.09.1 started with MariaDB 11.8, and the default
admin@admin.comaccount was replaced before Caddy was set up. From outside, a login withadmin@admin.comandpasswordfailed. - Sign-ups and public access were off by default.
- The audit log showed each visitor's real address, over IPv4 and IPv6, and ignored a forged
X-Forwarded-Forheader. Times showed in Swedish time. - A shelf, a book and pages were created, with an uploaded image and a code block. Search found a word in a page's text, and an edit made a second revision. A PDF export included the image.
- The interface was shown in Swedish, both for one user and as the default for everyone.
- With outside services switched off, a capture of the server's traffic showed no connections when a user was created, and the diagram editor was gone.
- Without email set up, an invite and a password reset failed, as described in step 9.
- An update from BookStack 26.05.5 to 26.09.1 went through
podman auto-update. The wiki was restored from a backup into empty volumes, and everything came back by itself after a reboot.
The pod used about 160 MB of memory, most of it MariaDB.
Before you start
You need:
- a server set up as in the Podman guide, with Caddy running;
- an A record and an AAAA record for
wiki.example.compointing at your server.
The examples use wiki.example.com for the wiki and 203.0.113.10 for your server's IPv4 address, which ip -brief address show eth0 shows. Replace them throughout.
1. Create the user
As root:
useradd -m -s /bin/bash bookstack
loginctl enable-linger bookstack
machinectl shell bookstack@
Everything up to step 5 runs as bookstack.
2. Create the secrets
podman run --rm --entrypoint /bin/bash lscr.io/linuxserver/bookstack:latest appkey | tr -d '\n' | podman secret create bookstack-app-key -
openssl rand -hex 24 | tr -d '\n' | podman secret create bookstack-db-password -
The first line downloads the BookStack image and uses its appkey command to make the application key, which BookStack uses to encrypt sessions and the two-factor secrets of your users. Without it a restored backup is of little use, so step 11 saves a copy. The second line makes the database password. Both are stored by Podman, not in the files below.
3. Define the pod
mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd
Create bookstack.pod:
[Pod]
PodName=bookstack
# Only Caddy, on this server, can reach BookStack: the port is not open to the internet.
PublishPort=127.0.0.1:8104:80
[Install]
WantedBy=default.target
Create bookstack-db.container:
[Container]
ContainerName=bookstack-db
Image=docker.io/library/mariadb:11.8
Pod=bookstack.pod
Volume=bookstack-db:/var/lib/mysql
Environment=MARIADB_DATABASE=bookstack MARIADB_USER=bookstack MARIADB_RANDOM_ROOT_PASSWORD=1 MARIADB_AUTO_UPGRADE=1
Secret=bookstack-db-password,type=env,target=MARIADB_PASSWORD
HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=10s
Notify=healthy
AutoUpdate=registry
[Service]
Restart=always
And bookstack-app.container:
[Unit]
Requires=bookstack-db.service
After=bookstack-db.service
[Container]
ContainerName=bookstack-app
Image=lscr.io/linuxserver/bookstack:latest
Pod=bookstack.pod
Volume=bookstack-config:/config
Environment=APP_URL=https://wiki.example.com APP_PROXIES=203.0.113.10
Environment=TZ=Europe/Stockholm APP_DISPLAY_TIMEZONE=Europe/Stockholm
Environment=DISABLE_EXTERNAL_SERVICES=true
Environment=DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=bookstack DB_USERNAME=bookstack
Secret=bookstack-app-key,type=env,target=APP_KEY
Secret=bookstack-db-password,type=env,target=DB_PASSWORD
AutoUpdate=registry
[Service]
Restart=always
What the settings do:
APP_URLis the address BookStack puts in every link and form. It must be the publichttps://address.APP_PROXIESis the address BookStack trusts to tell it the visitor's real address. Caddy's connections arrive in the pod from the server's own IPv4 address, as the Podman guide explains, so that is the one to name. Without it, the audit log shows203.0.113.10for everyone.APP_DISPLAY_TIMEZONEshows times in Swedish time. BookStack still stores them in UTC, which is its default and keeps them right across daylight saving changes.TZsets the time zone of the container's own logs.DISABLE_EXTERNAL_SERVICES=truestops the Gravatar lookups and removes the draw.io diagram editor. Step 10 has the details.- MariaDB stays on 11.8, a long-term release, and
MARIADB_AUTO_UPGRADEupdates its system tables after an update within it. BookStack follows its releases, and updates its database by itself when it starts. - The uploaded images and files, and the container's web server settings, go in the
bookstack-configvolume; the pages themselves are in the database.
Start it:
systemctl --user daemon-reload
systemctl --user start bookstack-pod
systemctl --user enable --now podman-auto-update.timer
The first start downloads about 780 MB of images, and BookStack takes a minute to set up its database. It is ready when this prints 302:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8104/
4. Replace the default admin
BookStack creates its first account as admin@admin.com with the password password, and anyone who knows that could log in as soon as the wiki is online. Change it before you add the wiki to Caddy. An SSH tunnel does not help here: BookStack sends every link and form to https://wiki.example.com, which does not answer yet. Use BookStack's own command instead:
podman exec bookstack-app php /app/www/artisan bookstack:create-admin --initial --email=you@example.com --name="Your Name" --generate-password
--initial replaces the details of the first admin account, instead of adding another one. The command prints a new random password and nothing else. Keep it in your password manager. You can change it later under My Account > Access & Security.
5. Put Caddy in front
Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:
wiki.example.com {
reverse_proxy 127.0.0.1:8104
}
Restart Caddy with systemctl --user restart caddy, and open https://wiki.example.com. Log in with your email address and the password from step 4. The old login, admin@admin.com with password, gets "These credentials do not match our records."
6. Check who can get in
Under Settings:
- Registration: Enable registration is off, so nobody can sign up.
https://wiki.example.com/registertakes visitors back to the login page. - Features & Security: Allow public access is off, so every page needs a login.
You create accounts yourself under Settings > Users > Add New User. Until email works (step 9), switch off Send user invite email and set a password for the user.
Uploaded images are the exception to the login: a visitor who has an image's exact address can open it without logging in. BookStack does this for speed, and says so next to Enable higher security image uploads under Features & Security, which adds a random string to the names of new uploads. Keep that in mind before you upload screenshots that show passwords or customer data.
BookStack can also leave logins to a single sign-on service, such as authentik, over OpenID Connect or SAML 2.0, or to LDAP. That is set with AUTH_METHOD and the related settings in BookStack's documentation. We did not test it here.
7. Shelves, books and pages
Content is organised in four levels: shelves hold books, books hold chapters and pages. To start:
- Books > Create New Book, give it a name, such as
Servers, and Save Book. - Shelves > New Shelf, give it a name, such as
IT, add the book with the + next to it under Add books to this shelf, and Save Shelf. - Open the book and choose New Page. The editor saves drafts as you type.
In the editor, Insert image opens the image manager, where Upload Image adds a picture from your computer; select it and choose Select Image to put it in the page. The More button at the end of the toolbar has Insert code block, which opens a code editor with a list of languages. Save Page publishes the page.
Each save is a new revision. Set Changelog next to Save Page adds a note to it, and Revisions on the page lists every version, with Changes to compare and Restore to go back. BookStack keeps the last 100 revisions of each page by default.
The search box at the top searches the text of every page, book and shelf you are allowed to see. Export on a page or a book gives you a PDF, a web page, Markdown, plain text or a ZIP file. To write in Markdown instead, the menu next to Editing Draft in the editor has Switch to Markdown Editor.
8. Use BookStack in Swedish
BookStack comes translated into Swedish. Each user picks Svenska under My Account > Profile Details > Preferred Language, and you can set it when you create the user.
To make Swedish the default for everyone who has not picked a language, add this line to bookstack-app.container, as bookstack, and restart:
Environment=APP_LANG=sv
systemctl --user daemon-reload
systemctl --user restart bookstack-app
9. Email
BookStack sends email for invites, password resets, email confirmation on sign-up, and notifications about pages users watch. This guide does not set up email, and without it:
- creating a user with Send user invite email on fails with "Could not create user since invite email failed to send";
- Forgot Password? on the login page shows "An unknown error occurred" for an address that has an account;
- Settings > Maintenance > Send a Test Email shows the error,
Connection could not be established with host "localhost:587".
Accounts and logins with a password keep working.
To send email, point BookStack at an SMTP server, such as the one from the mail server guide or your email provider's. As bookstack, store the SMTP password as a secret:
printf %s 'the-smtp-password' | podman secret create bookstack-mail-password -
Then add these lines to bookstack-app.container, with your own server, account and sender address:
Environment=MAIL_DRIVER=smtp MAIL_HOST=mail.example.com MAIL_PORT=587 MAIL_ENCRYPTION=tls
Environment=MAIL_USERNAME=wiki@example.com MAIL_FROM=wiki@example.com MAIL_FROM_NAME=Wiki
Secret=bookstack-mail-password,type=env,target=MAIL_PASSWORD
Restart as in step 8, and use Send a Test Email to check it. Port 587 with MAIL_ENCRYPTION=tls uses STARTTLS; for port 465, which uses TLS from the start, change the port. We did not test sending, since this server has no mail server.
10. Outside services
With DISABLE_EXTERNAL_SERVICES=true from step 3, the server makes no outside connections in normal use. We checked by capturing its DNS lookups and outgoing connections while BookStack started, while a page was exported and while users were created. Without that line, two things change:
- When a user is created, the server fetches a picture from
www.gravatar.com, sending an MD5 hash of the user's email address. In our capture, each new user caused a lookup ofwww.gravatar.comand a connection to it. - The page editor gets a diagram button, and when a user opens it, their browser loads the draw.io editor from
embed.diagrams.net.
To keep diagrams without diagrams.net, run the draw.io web app yourself and point BookStack at it, after DISABLE_EXTERNAL_SERVICES=true:
Environment=DRAWIO=https://draw.example.com/?embed=1&proto=json&spin=1&configure=1
The draw.io web app has its own image, docker.io/jgraph/drawio. We did not test that setup.
Links and embedded videos that users add to pages, such as from YouTube, load from those sites in the reader's browser, as on any web page. Webhooks, under Settings > Webhooks, call the addresses you give them.
11. Back up
As bookstack:
umask 077
mkdir -p ~/backup
podman exec bookstack-db sh -c 'mariadb-dump -ubookstack -p"$MARIADB_PASSWORD" --single-transaction bookstack' > ~/backup/bookstack-db.sql
podman volume export bookstack-config --output ~/backup/bookstack-config.tar
podman secret inspect --showsecret --format '{{.SecretData}}' bookstack-app-key > ~/backup/app-key.txt
That is what BookStack's own backup advice asks for: the database, the uploaded images and files, and the application key. The pages, users, revisions and settings are in the database, and the images and attachments in the volume. The dump runs while BookStack keeps working. Copy ~/backup to another machine, for example with restic, and store it safely: it holds all your content and the application key.
To restore into an empty pod, as bookstack, with the backup in ~/backup:
systemctl --user stop bookstack-pod
podman volume rm bookstack-db bookstack-config
podman volume create bookstack-config
podman volume import bookstack-config ~/backup/bookstack-config.tar
systemctl --user start bookstack-db
podman exec -i bookstack-db sh -c 'mariadb -ubookstack -p"$MARIADB_PASSWORD" bookstack' < ~/backup/bookstack-db.sql
systemctl --user start bookstack-app
Starting only the database first loads the backup before BookStack sets up an empty one. On a new server, create the key from the backup instead of a new one in step 2:
tr -d '\n' < ~/backup/app-key.txt | podman secret create bookstack-app-key -
12. Update
podman auto-update checks once a day for new images of BookStack and MariaDB, and restarts the pod when one has changed. BookStack updates its database when it starts. To update now:
podman auto-update
LinuxServer.io say they do not support unattended updates. If you would rather update by hand, replace latest in bookstack-app.container with a version tag, such as v26.09.1-ls287 from the image's list of tags, remove the AutoUpdate=registry line, and change the tag when you want to update. Either way, keep your backups current, and read the release notes on BookStack's blog before larger updates.
Troubleshooting
Every entry in the audit log has the server's own address. APP_PROXIES is missing or names the wrong address. It must be the server's IPv4 address, not 127.0.0.1. Fix it in bookstack-app.container, then daemon-reload and restart bookstack-app.
"Could not create user since invite email failed to send". Email is not set up. Switch off Send user invite email and set a password, or set up email as in step 9.
"An unknown error occurred" after Forgot Password?. The same cause: BookStack could not send the reset email. Settings > Maintenance > Send a Test Email shows the error.