1. Context & Philosophy
BookStack is an open-source platform for organising and storing information using a structured "Book > Chapter > Page" hierarchy.
This guide documents the installation of BookStack on a YunoHost VPS (base.kalvin.my) using Docker Compose. Instead of a standard root-based installation, this setup prioritises a Humane-Tech approach: strictly controlling data ownership by running containers as a specific non-root administrative user (kalvin0x58c) to prevent permission locks and keep the system clean.
2. Prerequisites
System Requirements
- OS: Debian 12 (Bookworm) with YunoHost installed.
- Docker: Installed and running.
- Domain: A subdomain configured in YunoHost (e.g.,
base.kalvin.my). - User Info: A non-root user with
sudoprivileges.- User:
kalvin0x58c. - PUID (User ID):
23691. - PGID (Group ID):
23691.
- User:
3. Installation & Directory Setup
I store Docker configurations in my home directory to ensure full ownership without needing root access for backups.
mkdir -p ~/docker/bookstack
cd ~/docker/bookstack
nano compose.yaml
The compose.yaml Configuration
We use the LinuxServer.io images because they handle PUID/PGID mapping excellently.
To generate the required unique 32-byte APP_KEY for security, use:
echo "base64:$(openssl rand -base64 32)"
4. Troubleshooting Logs (The "Gotchas")
While the setup seemed straightforward, I ran into specific issues regarding permissions, database connections, and encryption keys.
Issue A: "Permission Denied" on Docker Socket
Symptom: Error accessing /var/run/docker.sock when running docker compose up.
Cause: My non-root user was not in the docker group.
Fix:
sudo usermod -aG docker kalvin0x58c
newgrp docker
Issue B: Laravel APP_KEY Cipher Error
Symptom: A white screen or 500 Error. Logs showed RuntimeException: Unsupported cipher or incorrect key length.
Cause:
- Format: The key must be prefixed with
base64:. - Conflict: Docker Compose
environmentvariables override local configuration files (.env). A broken key incompose.yamlwas overwriting the manual fixes.
The Fix:
I removedAPP_KEYfromcompose.yamlto force the container to respect the file-based.envconfig, then cleared the application cache, which is crucial:
<!-- end list -->
docker exec bookstack php artisan config:clear
docker exec bookstack php artisan cache:clear
Issue C: "Access Denied" (Stale Configuration)
Symptom: SQLSTATE[HY000] [1045] Access denied for user.... This showed the app was trying to use the wrong or default database credentials.
Cause: The container generates an internal configuration file in the /config volume during the first run, and it ignores later changes to the compose.yaml database variables.
The "Nuclear" Fix:
To force a configuration regeneration, I had to wipe the generated persistent volumes:
# STOPS containers, DELETES config/data, and RECREATES fresh
docker compose down -v
sudo rm -rf ./config ./db_data
docker compose up -d --force-recreate
5. Final Working Configuration
This configuration runs securely under my own user ID, isolated in Docker.
compose.yaml:
services:
bookstack:
image: lscr.io/linuxserver/bookstack:latest
container_name: bookstack
environment:
- PUID=23691
- PGID=23691
- TZ=Asia/Kuala_Lumpur
- APP_URL=https://base.kalvin.my
- DB_HOST=bookstack_db
- DB_USER=bookstack
- DB_PASS=SecretPassword
- DB_DATABASE=bookstackapp
volumes:
- ./config:/config
ports:
- 6875:80
restart: unless-stopped
depends_on:
- bookstack_db
bookstack_db:
image: lscr.io/linuxserver/mariadb:latest
container_name: bookstack_db
environment:
- PUID=23691
- PGID=23691
- TZ=Asia/Kuala_Lumpur
- MYSQL_ROOT_PASSWORD=RootPassword
- MYSQL_DATABASE=bookstackapp
- MYSQL_USER=bookstack
- MYSQL_PASSWORD=SecretPassword
volumes:
- ./db_data:/config
restart: unless-stopped
./config/www/.env (Key snippets):
APP_KEY=base64:YourActualGeneratedKey==
# Mail Settings (Always wrap passwords with special characters in double quotes)
MAIL_PASSWORD="!PasswordWithSymbols@"
6. Exposing to the Web (YunoHost Reverse Proxy)
Since the Docker app runs internally on localhost:6875, YunoHost is used as the bridge to provide HTTPS access.
- Installed the Redirect app in the YunoHost Web Admin.
- Configured:
- Domain:
base.kalvin.my - Destination:
http://127.0.0.1:6875
- Domain:
7. Lessons Learnt
- Variable Precedence: Docker Compose
environmentvariables always overwrite file-based configurations. - Caching: When in doubt about configuration changes,
php artisan config:clearis essential. - Mail Config: Always wrap passwords with special characters in double quotes
""to prevent parsing errors. - Security: Immediately change the default credentials (
admin@admin.com/password) in the user profile settings after the first successful login.