Trilium Notes Tutorial: Deploy a Hierarchical Personal Knowledge Base via Docker

Learn how to host Trilium Notes on a VPS using Docker Compose. Build a powerful, customizable, and hierarchical note-taking database.

Trilium Notes Tutorial: Deploy a Hierarchical Personal Knowledge Base via Docker

Trilium Notes is an hierarchical personal knowledge base with a strong focus on build-your-own-customizations, scripting, and advanced developer workflows. Unlike flatter note-taking applications, Trilium uses a powerful SQLite-backed tree structure, permitting deep nesting, cloned notes (notes with multiple parents), attributes, and script execution.

This guide provides a production-grade deployment walkthrough for self-hosting Trilium Notes on a Virtual Private Server (VPS) using Docker Compose, secured behind an Nginx reverse proxy with automated Let's Encrypt SSL certificates.


1. Core Architecture and Database Engine

Before deploying, understanding how Trilium manages state is critical for designing backup and sync strategies.

Database Storage

Trilium stores all notes, revisions, attributes, and options in a single SQLite database named document.db, located inside its data directory (/home/node/trilium-data by default in Docker). SQLite provides ACID compliance and simplified backups, but because it relies on file-system locks, it cannot be run concurrently by multiple write-processes. Mounting this directory over network filesystems (like NFS) is strongly discouraged due to locking latency and corruption risks.

Hierarchical Note Tree & Attributes

Trilium represents notes as entities with parent-child relationships stored in the database. Notes can be cloned, meaning a single note can exist under multiple parents in the tree without duplicating its underlying content or ID. Additionally, Trilium features: - Labels: Key-value metadata attached to notes (e.g., cssClass=dark-theme, shareType=raw). - Relations: Directed associations between notes (e.g., sourceOfRelation -> targetNote). - Script Notes: JavaScript (backend or frontend) execution within the sandbox to automate tasks, generate dynamic templates, or render custom widgets.

Sync Protocol

Trilium implements a custom sync protocol over HTTP/HTTPS: 1. Sync Server (VPS): The central source of truth. 2. Sync Clients (Desktop/Local instances): Pull changes, resolve conflicts using last-write-wins (relying on millisecond-accurate timestamps), and push local mutations. 3. Changelog Tracking: A database table tracks incremental mutations, allowing clients to request delta updates rather than full database synchronization.


2. Docker Compose Configuration

Create a dedicated directory on your VPS to house the Docker Compose configuration and persistent storage volume:

mkdir -p ~/trilium-data
cd ~/trilium-data

Create a docker-compose.yml file. This configuration pins the stable version, mounts a persistent volume to preserve SQLite data, and exposes the container on a local port (8080) to be proxied by Nginx.

version: '3.8'

services:
  trilium:
    image: zadam/trilium:0.61-latest
    container_name: trilium-notes
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    environment:
      - TRILIUM_DATA_DIR=/home/node/trilium-data
      - USER_UID=1000
      - USER_GID=1000
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - ./data:/home/node/trilium-data

Key Parameters:

  • image: We pin the service to a minor version (0.61-latest) to avoid breaking database schema migrations that can occur with major releases.
  • ports: Binding to 127.0.0.1:8080 ensures the database container is inaccessible directly from the public internet, routing all external traffic through the reverse proxy.
  • volumes: Maps host path ./data to the container's /home/node/trilium-data directory, preserving SQLite state across container restarts and updates.

3. Reverse Proxy & SSL Configuration (Nginx)

To enable secure HTTPS synchronization and web client access, route traffic through Nginx.

Install Nginx and Certbot

Run the following commands on your Ubuntu/Debian host:

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Configure Nginx Server Block

Create a virtual host configuration file under /etc/nginx/sites-available/trilium.conf:

server {
    listen 80;
    server_name notes.example.com; # Replace with your domain

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSockets support for real-time synchronization
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Adjust maximum upload size for attachments
        client_max_body_size 100M;
    }
}

Enable the configuration and reload Nginx:

sudo ln -s /etc/nginx/sites-available/trilium.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Acquire Let's Encrypt SSL

Execute Certbot to provision and configure the SSL certificate:

sudo certbot --nginx -d notes.example.com

Certbot will automatically alter the Nginx configuration to enforce SSL redirection (port 80 to port 443) and manage TLS parameters.


4. Initialization and Desktop Client Sync

  1. Launch the Container: bash docker compose up -d
  2. Access Setup Wizard: Navigate to https://notes.example.com in your web browser. Select "I am a new user and want to create a new Trilium document", and configure your master password.
  3. Connecting a Desktop Client:
  4. Download the official Trilium Desktop application for your client platform.
  5. On first launch, select "I want to sync with my Trilium instance on a server".
  6. Input your server URL (https://notes.example.com) and master password to start the initial sync.

5. Maintenance: Automated Backups & Updates

Automated SQLite Backups

Because SQLite write locking can interfere with simple cp actions, Trilium includes a built-in backup schedule. By default, it generates hourly, daily, and weekly backups within the backup directory inside the data volume.

To create a secondary backup of the SQLite database safely without locking the server, copy the latest auto-backup from the volume directory:

#!/bin/bash
BACKUP_DIR="/home/user/backups"
DATA_DIR="/home/user/trilium-data/data"
TIMESTAMP=$(date +"%Y%m%d%H%M%S")

mkdir -p "$BACKUP_DIR"

# Copy the latest automated backup generated by Trilium
cp "$DATA_DIR/backup/backup-daily.db" "$BACKUP_DIR/trilium-backup-$TIMESTAMP.db"

# Prune backups older than 30 days
find "$BACKUP_DIR" -name "trilium-backup-*.db" -mtime +30 -delete

Container Updates

To pull the latest image and update the container:

cd ~/trilium-data
docker compose pull
docker compose up -d

Trilium will automatically apply database migrations on boot. Always ensure you have a backup copy of your document.db before upgrading.