Skip to main content
  1. Posts/

Bookstack (Docker) - How To Backup

·4 mins· loading ·
Backup Application
Timo
Author
Timo
Business Applications Architect, Network Engineer, Self-hosting Hobbyist.
Table of Contents
Bookstack - This article is part of a series.
Part 2: This Article

After a while of using Bookstack you’ll soon notice your collection of knowledgeable things increase, and maybe you’ll start to worry about backing things up or keeping all this data accessible.

Worry no more, I got you covered!
Backing up Bookstack is easier than you might think, let me share you my way of backing up my dockerized Bookstack instance.

Backing up the config Dir
#

Your Bookstack docker-compose.yml contains a volume mount for all of your uploaded data similar to this:

    volumes:
      - /path/to/config:/config

The following command will archive this directory and safe a tarball like bookstack_config_backup-<timestamp>.tar.gz in the destination directory.
This can be used in a cron job for example:

/bin/tar -czvf /path/to/bookstack_config-backups/bookstack_config_backup-$(date '+%Y%m%d-%H%M').tar.gz /path/to/bookstack/config
Destination directory: /path/to/bookstack_config-backups/
Source directory: /path/to/bookstack/config

Exporting all Books as HTML files
#

PDF exports are a great way to keep your documentation at hand even when your Bookstack wiki might be down.
I found a great PHP script to accomplish exactly that but can’t remember the source, so if you’re the author or know him feel free to contact me and I gladly add the source.

Creating API Token
#

To authenticate this script to your Bookstack API you have to create an API token first.
Edit your profile (top right corner), scroll all the way down to the section “API Tokens” and click on “CREATE TOKEN”.

Create Token
Creating a new API Token for Bookstack.
Set a name and leave Expiry Date empty.
Create Token
Token Settings.
Obtain Token ID and Token Secret of the freshly generated token.
Create Token
Token Details.

PHP Script
#

Use this bookstack-export.php script in a cron job for example:

Click for bookstack-export.php ...
#!/usr/bin/env php
<?php

// API Credentials
// You can either provide them as environment variables
// or hard-code them in the empty strings below.
$apiUrl = 'https://bookstack.domain.tld' ?: ''; // http://bookstack.local/
$clientId = 'API_CLIENT_ID' ?: '';
$clientSecret = 'API_CLIENT_SECRET' ?: '';

// Export Format & Location
// Can be provided as a arguments when calling the script
// or be hard-coded as strings below.
$exportFormat = 'html' ;
$exportLocation = '/path/to/bookstack-export' ; // Destination path for the export

// Script logic
////////////////

$books = getAllBooks();
$outDir = realpath($exportLocation);

$extensionByFormat = [
    'pdf' => 'pdf',
    'html' => 'html',
    'plaintext' => 'txt',
];

foreach ($books as $book) {
    $id = $book['id'];
    $extension = $extensionByFormat[$exportFormat] ?? $exportFormat;
    $content = apiGet("api/books/{$id}/export/{$exportFormat}");
    $outPath = $outDir  . "/{$book['slug']}.{$extension}";
    file_put_contents($outPath, $content);
}

/**
 * Get all books from the system API.
 */
function getAllBooks() {
    $count = 100;
    $offset = 0;
    $total = 0;
    $allBooks = [];

    do {
        $endpoint = 'api/books?' . http_build_query(['count' => $count, 'offset' => $offset]);
        $resp = apiGetJson($endpoint);

        // Only set total on first request, due to API bug:
        // https://github.com/BookStackApp/BookStack/issues/2043
        if ($offset == 0) {
            $total = $resp['total'] ?? 0;
        }

        $newBooks = $resp['data'] ?? [];
        array_push($allBooks, ...$newBooks);
        $offset += $count;
    } while ($offset < $total);

    return $allBooks;
}

/**
 * Make a simple GET HTTP request to the API.
 */
function apiGet(string $endpoint): string {
    global $apiUrl, $clientId, $clientSecret;
    $url = rtrim($apiUrl, '/') . '/' . ltrim($endpoint, '/');
    $opts = ['http' => ['header' => "Authorization: Token {$clientId}:{$clientSecret}"]];
    $context = stream_context_create($opts);
    return file_get_contents($url, false, $context);
}

/**
 * Make a simple GET HTTP request to the API &
 * decode the JSON response to an array.
 */
function apiGetJson(string $endpoint): array {
    $data = apiGet($endpoint);
    return json_decode($data, true);
}

/**
 * DEBUG: Dump out the given variables and exit.
 */
function dd(...$args) {
    foreach ($args as $arg) {
        var_dump($arg);
    }
    exit(1);
}
Use the earlier obtained Token ID and Token Secret as $clientId and $clientSecret in the script. Set $apiUrl and $exportLocation accordingly to your setup.

Final thoughts
#

If you follow this guide your Bookstack instance is both, backed up and always available to you.
Both Scripts can be combined in a single Bash-Script like this:

#!/bin/bash

/bin/tar -czvf /path/to/bookstack_config-backups/bookstack_config_backup-$(date '+%Y%m%d-%H%M').tar.gz /path/to/bookstack/config &&
/usr/bin/php /path/to/bookstack-export.php 

Create a daily cron job, and you’re all set:

# Bookstack Backup (daily at 08:00 am)
0 8 * * * /path/to/bookstack_backup.sh > /dev/null
Bookstack - This article is part of a series.
Part 2: This Article