Skip to content

BookStack

BookStack organises documentation in shelves, books, chapters and pages. Two containers — the application and MariaDB.

Last run on a host 8 Nov 2026

Pinned at version-v26.09.2 (released 20 Oct 2026) · the newest upstream release

In the upstream images: 1 CRITICAL · 31 HIGH with a published fix — per image

To try it: Docker, nothing else. See Try it locally.

To deploy it behind the proxy: Traefik running and a domain.

On your own machine, with no proxy, domain or certificate. From apps/bookstack/:

Terminal window
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs
APP_TAG=$(sed -n 's/^APP_TAG=//p' .env.local)
{
echo "DB_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')"
echo "DB_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')"
echo "APP_KEY=$(docker run --rm --entrypoint /bin/bash "lscr.io/linuxserver/bookstack:$APP_TAG" appkey)"
} >> .env.local
mkdir -p volumes/local/config volumes/local/mysql
sudo chown -R 1000:1000 volumes/local/config
docker compose -f docker-compose.local.yml --env-file .env.local up -d

Then open http://localhost:8080. Stop it again with the same command and down.

From apps/bookstack/:

  1. Copy the environment file and set APP_TRAEFIK_HOST, TZ and PUID/PGID:

    Terminal window
    cp .env.example .env
  2. Generate the Laravel application key and paste the base64:… output into APP_KEY in .env:

    Terminal window
    docker run --rm --entrypoint /bin/bash \
    lscr.io/linuxserver/bookstack:version-v26.09.2 appkey
  3. Generate the database secrets and mirror the password into .env:

    Terminal window
    mkdir -p .secrets
    openssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txt
    openssl rand -base64 32 | tr -d '\n' > .secrets/db_root_pwd.txt
    sed -i "s|^DB_PWD_INLINE=.*|DB_PWD_INLINE=$(cat .secrets/db_pwd.txt)|" .env

    BookStack cannot read the password from a file, so the value exists twice — once as the secret for MariaDB, once inline.

  4. Create the volumes and hand the config directory to the user the image drops to:

    Terminal window
    mkdir -p volumes/config volumes/mysql
    sudo chown -R 1000:1000 volumes/config
  5. Start it and wait for the first-run migrations:

    Terminal window
    docker compose up -d
    docker compose logs bookstack-app --follow
  6. Over the VPN, open https://<APP_TRAEFIK_HOST> and log in with the default account — [email protected] with the password password — then change both immediately. The route ships closed (access-vpn) because that default login works on a new instance; open it for readers once the account is changed.

Terminal window
docker compose ps # two services, healthy

Then create a page and paste an image into it. The image is the check: page text goes to the database, the file goes to the upload directory, and only the second depends on the ownership from step 4.

The stack ships access-vpn with limit-standard and headers-standard, so the default login above is never reachable from the internet. A wiki people are meant to read has to be readable: once the account is changed, set APP_TRAEFIK_ACCESS=access-public in .env and run docker compose up -d; BookStack’s own permissions then decide who sees what. For internal content, keep access-vpn.

Database MariaDB, container bookstack-db, database bookstack, user bookstack
Password .secrets/db_pwd.txt
State volumes/mysql (database) · volumes/config (images, attachments)
Quiescing Not needed — the dump is consistent on its own.

Pages reference uploaded images by path, so the database and volumes/config come back from the same backup run.

For borgmatic:

mariadb_databases:
- name: bookstack
container: bookstack-db
username: bookstack
password: "{credential file /srv/docker/apps/bookstack/.secrets/db_pwd.txt}"

Page content is in the database; the images inside those pages are in volumes/config. A database-only restore gives a wiki where every page renders with broken images — recovered on paper, not in practice.

Restore order: database, then the application.

  1. Read the release notes.

  2. Back up the database and volumes/config together.

  3. Raise APP_TAG in .env, then:

    Terminal window
    docker compose pull && docker compose up -d
    docker compose logs bookstack-app --follow
  4. Open a page with an image. A migration that touched the attachment tables shows up there.

Uploads fail with a permission error. volumes/config is not owned by the PUID/PGID from .env. Re-run the chown from step 4.

The application reports a missing or invalid key. APP_KEY is empty or was pasted without the base64: prefix.

Adding user: to the app service breaks startup. The image starts as root to set up its process supervisor and drops privileges itself.