--- title: Wikijs Backup description: Backup Wikijs published: true date: 2026-02-23T04:35:32.870Z tags: editor: markdown dateCreated: 2026-02-23T04:35:24.121Z --- # Wiki.js Backup & Recovery **Service:** Wiki.js (Netgrimoire) **Stack:** Docker Compose — Wiki.js + PostgreSQL **Backup Targets:** PostgreSQL database dump, Git content repository, Docker Compose config **Backup Destinations:** Local vault path → Kopia → offsite vaults --- ## Overview Wiki.js data lives in two separate places that must be backed up independently: **PostgreSQL database** — stores page metadata, navigation, user accounts, permissions, page history, assets, and all configuration. This is the critical component for a portable restore. Without it, a new instance has no knowledge of your wiki structure. **Git content repository** — stores the actual page content in markdown files, synced from Forgejo. This is already mirrored on the VAULT SSD at `/vault/repos/wiki/`. It is inherently redundant as long as Forgejo is healthy, but is included in backups for completeness and offline portability. **Docker Compose config** — the `docker-compose.yml` and `.env` files needed to recreate the stack. --- ## What Gets Backed Up | Component | Location | Method | Critical? | |---|---|---|---| | PostgreSQL database | Docker volume | `pg_dump` → SQL file | Yes — primary restore target | | Git content repo | `/vault/repos/wiki/` | Already on VAULT SSD | Yes — page content | | Docker Compose files | `/opt/stacks/wikijs/` | rsync copy | Yes — stack config | | Wiki.js data volume | Docker volume | Optional rsync | No — DB + Git covers this | --- ## Backup Strategy ### Tier 1 — Daily Dump to Vault Path A script runs daily via systemd timer. It produces a portable `pg_dump` SQL file written to `/vault/backups/wiki/`. These local dumps are retained for 14 days. **Key choices:** - `--format=plain` — plain SQL, portable to any PostgreSQL version and any host - `--no-owner` — strips role ownership, so the dump restores cleanly on a new instance with a different postgres user (critical for Pocket Grimoire restores) - `--no-acl` — strips GRANT/REVOKE statements for the same reason - No application downtime required — PostgreSQL handles consistent dumps natively ### Tier 2 — Kopia Snapshot to Offsite Vaults After the daily dump completes, Kopia snapshots the entire `/vault/backups/wiki/` directory and replicates to your offsite vaults. Kopia deduplication means only changed blocks are transferred after the first run. --- ## Setup ### Step 0 — Confirm Kopia Repository Exists If Kopia is not yet initialized on this host, initialize it first. If you already initialized Kopia for Mailcow or another service, skip this step — all services share the same Kopia repository. ```bash # Check if repository already exists kopia repository status # If not initialized, create it against your vault path kopia repository create filesystem --path=/vault/kopia # Connect on subsequent logins if disconnected kopia repository connect filesystem --path=/vault/kopia ``` ### Step 1 — Create Backup Directories ```bash sudo mkdir -p /vault/backups/wiki sudo chown $(whoami):$(whoami) /vault/backups/wiki ``` ### Step 2 — Create the Backup Script ```bash sudo nano /usr/local/sbin/wikijs-backup.sh ``` ```bash #!/usr/bin/env bash # wikijs-backup.sh — Daily Wiki.js backup: pg_dump + git repo + config # Writes to /vault/backups/wiki/, then snapshots with Kopia set -euo pipefail # ── Configuration ───────────────────────────────────────────────────────────── BACKUP_DIR="/vault/backups/wiki" DATE=$(date +%Y%m%d_%H%M%S) CONTAINER_DB="wikijs_db" # Adjust to your actual container name PG_USER="wikijs" PG_DB="wikijs" WIKI_STACK_DIR="/opt/stacks/wikijs" # Location of docker-compose.yml and .env GIT_REPO_DIR="/vault/repos/wiki" # Git content mirror (already on vault SSD) RETAIN_DAYS=14 # Local dump retention LOG="/var/log/wikijs-backup.log" touch "$LOG" log() { echo "$(date -Is) $*" | tee -a "$LOG"; } # ── Step 1: PostgreSQL dump ──────────────────────────────────────────────────── log "Starting Wiki.js PostgreSQL dump..." docker exec "$CONTAINER_DB" pg_dump \ -U "$PG_USER" \ "$PG_DB" \ --format=plain \ --no-owner \ --no-acl \ > "${BACKUP_DIR}/wikijs-db-${DATE}.sql" gzip "${BACKUP_DIR}/wikijs-db-${DATE}.sql" log "PostgreSQL dump complete: wikijs-db-${DATE}.sql.gz" # ── Step 2: Docker Compose config backup ────────────────────────────────────── log "Backing up Docker Compose config..." CONFIG_BACKUP="${BACKUP_DIR}/wikijs-config-${DATE}.tar.gz" tar -czf "$CONFIG_BACKUP" \ -C "$(dirname "$WIKI_STACK_DIR")" \ "$(basename "$WIKI_STACK_DIR")" log "Config backup complete: wikijs-config-${DATE}.tar.gz" # ── Step 3: Git repo snapshot (content mirror) ──────────────────────────────── # The git repo lives on the VAULT SSD and is already versioned. # We record the current HEAD commit for reference. if [ -d "${GIT_REPO_DIR}/.git" ]; then GIT_HEAD=$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || echo "unknown") echo "Git HEAD at backup time: ${GIT_HEAD}" \ > "${BACKUP_DIR}/wikijs-git-ref-${DATE}.txt" log "Git content repo HEAD: ${GIT_HEAD}" else log "WARNING: Git repo not found at ${GIT_REPO_DIR} — skipping git ref" fi # ── Step 4: Cleanup old local dumps ─────────────────────────────────────────── log "Cleaning up dumps older than ${RETAIN_DAYS} days..." find "$BACKUP_DIR" -name "wikijs-db-*.sql.gz" -mtime +"$RETAIN_DAYS" -delete find "$BACKUP_DIR" -name "wikijs-config-*.tar.gz" -mtime +"$RETAIN_DAYS" -delete find "$BACKUP_DIR" -name "wikijs-git-ref-*.txt" -mtime +"$RETAIN_DAYS" -delete # ── Step 5: Kopia snapshot ──────────────────────────────────────────────────── log "Running Kopia snapshot of /vault/backups/wiki/..." kopia snapshot create "$BACKUP_DIR" \ --tags "service:wikijs,host:$(hostname -s)" log "Kopia snapshot complete." # ── Done ────────────────────────────────────────────────────────────────────── log "Wiki.js backup finished successfully." ``` ```bash sudo chmod +x /usr/local/sbin/wikijs-backup.sh ``` ### Step 3 — Create systemd Service and Timer ```bash sudo nano /etc/systemd/system/wikijs-backup.service ``` ```ini [Unit] Description=Wiki.js daily backup (pg_dump + config + Kopia snapshot) After=docker.service [Service] Type=oneshot ExecStart=/usr/local/sbin/wikijs-backup.sh ``` ```bash sudo nano /etc/systemd/system/wikijs-backup.timer ``` ```ini [Unit] Description=Run Wiki.js backup daily at 02:00 [Timer] OnCalendar=*-*-* 02:00:00 Persistent=true [Install] WantedBy=timers.target ``` ```bash sudo systemctl daemon-reload sudo systemctl enable wikijs-backup.timer sudo systemctl start wikijs-backup.timer # Verify systemctl list-timers | grep wikijs ``` ### Step 4 — Configure Kopia Retention Policy ```bash # Set retention policy for wiki backups kopia policy set /vault/backups/wiki \ --keep-daily 14 \ --keep-weekly 8 \ --keep-monthly 12 \ --compression zstd # Verify policy kopia policy show /vault/backups/wiki ``` ### Step 5 — Test the Backup ```bash # Run manually first time sudo /usr/local/sbin/wikijs-backup.sh # Verify output ls -lh /vault/backups/wiki/ # Should show: wikijs-db-YYYYMMDD_HHMMSS.sql.gz # wikijs-config-YYYYMMDD_HHMMSS.tar.gz # wikijs-git-ref-YYYYMMDD_HHMMSS.txt # Verify Kopia snapshot was created kopia snapshot list /vault/backups/wiki # Check backup log tail -n 30 /var/log/wikijs-backup.log ``` --- ## Verifying Backups ### Check dump is readable ```bash # Inspect the SQL dump without extracting zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | head -50 # Should show PostgreSQL header, version info, and CREATE TABLE statements ``` ### Verify Kopia snapshots ```bash # List recent snapshots kopia snapshot list /vault/backups/wiki # Show snapshot details kopia snapshot list /vault/backups/wiki --all # Verify snapshot integrity kopia snapshot verify ``` ### Test restore to a temporary database (non-destructive) ```bash # Start a temporary Postgres container docker run --rm -d \ --name wikijs-restore-test \ -e POSTGRES_USER=wikijs \ -e POSTGRES_PASSWORD=testpassword \ -e POSTGRES_DB=wikijs_test \ postgres:16-alpine # Wait for Postgres to be ready sleep 5 # Restore dump into test container zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \ docker exec -i wikijs-restore-test psql -U wikijs -d wikijs_test # Verify tables exist docker exec wikijs-restore-test psql -U wikijs -d wikijs_test -c "\dt" # Expected output: List of tables (pages, users, pageHistory, assets, etc.) # Cleanup test container docker stop wikijs-restore-test ``` --- ## Recovery Procedures ### Scenario A — Restore to a New Wiki.js Instance (Any Host) This covers full disaster recovery to a fresh server, including Pocket Grimoire. **Requirements on the destination host:** - Docker and Docker Compose installed - A `docker-compose.yml` and `.env` ready (from backup or Pocket Grimoire stack) - Sufficient disk space **Step 1: Locate the backup** ```bash # On Netgrimoire, find the dump to restore ls -lh /vault/backups/wiki/ # Or restore from Kopia kopia snapshot list /vault/backups/wiki kopia restore SNAPSHOT_ID /tmp/wiki-restore/ ls /tmp/wiki-restore/ ``` **Step 2: Copy dump to the destination host** ```bash # From Netgrimoire, copy to the destination server scp /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz \ user@destination-host:/tmp/ # Or to Pocket Grimoire scp /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz \ user@pocket-grimoire.local:/tmp/ ``` **Step 3: Start the database container only** On the destination host, start just the database — do not start Wiki.js yet: ```bash cd /srv/pocket-grimoire/stacks/wikijs # Adjust path as needed # Start only the database container docker compose up -d db # Wait for healthy status docker compose ps # db should show: healthy ``` **Step 4: Restore the dump** ```bash # Restore the dump into the running database container zcat /tmp/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \ docker exec -i pocketgrimoire_db psql \ -U wikijs \ -d wikijs # Verify tables restored docker exec pocketgrimoire_db psql -U wikijs -d wikijs -c "\dt" ``` **Step 5: Start Wiki.js** ```bash docker compose up -d # Watch startup logs docker logs -f pocketgrimoire_wikijs # Wait for: "HTTP Server started successfully" ``` **Step 6: Verify** Open `http://pocket-grimoire.local:3000` and confirm: - Pages load correctly - Navigation structure is intact - User accounts are present (if you had multiple users) **Step 7: Re-sync Git content (if needed)** The database knows the page structure, but if the Git content repo isn't present on the new host, import it: ```bash # In Wiki.js admin panel: # Administration → Storage → Git # Click "Force Sync" or "Import Content" # Or copy the repo from VAULT SSD rsync -avP /vault/repos/wiki/ /srv/pocket-grimoire/repos/wiki/ ``` --- ### Scenario B — Restore on Existing Netgrimoire Instance Use this when the Wiki.js database is corrupted but the host is otherwise healthy. **Step 1: Stop Wiki.js (leave database running)** ```bash cd /opt/stacks/wikijs docker compose stop wikijs ``` **Step 2: Drop and recreate the database** ```bash docker exec -it wikijs_db psql -U postgres -c "DROP DATABASE wikijs;" docker exec -it wikijs_db psql -U postgres -c "CREATE DATABASE wikijs OWNER wikijs;" ``` **Step 3: Restore** ```bash zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \ docker exec -i wikijs_db psql -U wikijs -d wikijs ``` **Step 4: Restart Wiki.js** ```bash docker compose start wikijs docker logs -f wikijs ``` --- ### Scenario C — Restore Config Only If the stack config was lost but the database volume is intact: ```bash # Extract config from backup tar -xzf /vault/backups/wiki/wikijs-config-YYYYMMDD_HHMMSS.tar.gz \ -C /opt/stacks/ # Verify ls /opt/stacks/wikijs/ # Should show: docker-compose.yml .env # Restart stack cd /opt/stacks/wikijs docker compose up -d ``` --- ### Restore from Kopia (Offsite) When local vault files are unavailable, restore the backup directory from Kopia first: ```bash # List available snapshots kopia snapshot list /vault/backups/wiki # Restore snapshot to temp directory kopia restore SNAPSHOT_ID /tmp/wiki-restore/ # Then proceed with the appropriate scenario above # using files from /tmp/wiki-restore/ instead of /vault/backups/wiki/ ``` --- ## Pocket Grimoire Specifics When restoring to Pocket Grimoire, note the following differences from a full Netgrimoire instance: **Container names** differ — use `pocketgrimoire_db` instead of `wikijs_db`. **Stack path** is `/srv/pocket-grimoire/stacks/wikijs/` instead of `/opt/stacks/wikijs/`. **The database is already initialized** when Pocket Grimoire is first set up. Restoring a Netgrimoire dump overwrites it entirely, which is the intended behavior — Pocket Grimoire becomes a mirror of Netgrimoire's wiki state. **Git content repo** is located at `/srv/pocket-grimoire/repos/wiki/` and is populated via the sync script (`pocketgrimoire-sync.sh`). A database restore alone is sufficient if the Git repo is already in place. **Recommended restore workflow for Pocket Grimoire:** ```bash # 1. Copy dump from VAULT SSD (already available on Pocket Grimoire) ls /srv/vaultpg/backups/wiki/ # 2. Start db container only cd /srv/pocket-grimoire/stacks/wikijs && docker compose up -d db # 3. Restore zcat /srv/vaultpg/backups/wiki/wikijs-db-LATEST.sql.gz | \ docker exec -i pocketgrimoire_db psql -U wikijs -d wikijs # 4. Start full stack docker compose up -d ``` Because the VAULT SSD is always connected to Pocket Grimoire, no file transfer is needed — the dumps are already there. --- ## Monitoring & Alerts Add the following to your existing ntfy/monitoring setup to alert on backup failures. Wrap the backup script call in an error trap: ```bash # Add to wikijs-backup.sh after set -euo pipefail: NTFY_URL="https://ntfy.YOUR_DOMAIN/wikijs-backup" on_error() { curl -fsS -X POST "$NTFY_URL" \ -H "Title: Wiki.js backup FAILED ($(hostname -s))" \ -H "Priority: high" \ -H "Tags: rotating_light" \ -d "Backup failed at $(date -Is). Check /var/log/wikijs-backup.log" } trap on_error ERR ``` ### Check backup age manually ```bash # Find most recent dump ls -lt /vault/backups/wiki/wikijs-db-*.sql.gz | head -3 # Check Kopia last snapshot time kopia snapshot list /vault/backups/wiki | tail -5 ``` --- ## Quick Reference ```bash # Run backup manually sudo /usr/local/sbin/wikijs-backup.sh # Watch backup log tail -f /var/log/wikijs-backup.log # Check timer status systemctl status wikijs-backup.timer # List local dumps ls -lh /vault/backups/wiki/ # List Kopia snapshots kopia snapshot list /vault/backups/wiki # Restore dump (generic) zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \ docker exec -i CONTAINER_NAME psql -U wikijs -d wikijs # Test dump is readable zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | head -50 ``` --- ## Revision History | Version | Date | Notes | |---|---|---| | 1.0 | 2026-02-22 | Initial release — pg_dump + Kopia + Pocket Grimoire restore procedures |