Files
librenotes/docs/self-hosting.md
Michael Czechowski 61edef9483 Add user guide, self-hosting, API, and contributing docs
docs/user-guide.md — magic-link sign-in, note basics, wikilink
syntax, keyboard shortcuts, offline behaviour, and privacy
notes (sessionStorage for tokens, tenant-scoped localStorage).

docs/self-hosting.md — system requirements, Docker Compose
quick-start, the full LIBRENOTES_* env-var matrix (which are
required, which conditional), reverse proxy snippets for Caddy
and nginx, volume layout, the in-binary healthcheck, and
update/rollback procedure.

docs/api.md — every public endpoint: auth (login/verify),
notes CRUD, /api/whoami, /healthz. Status codes per endpoint,
the optimistic-locking ?base=<unix> contract for PUT/DELETE,
note-ID regex, and the rate-limit policy.

CONTRIBUTING.md — dev setup (Nix flake .#dev, plain Go, Docker),
package layout overview, coding standards (one-way dep flow,
tenant FS gateway requirement), branch naming, commit format,
and the PR process. Also points security reports at
security@librete.ch rather than public issues.

Closes #29.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:52:09 +02:00

3.7 KiB

Self-hosting

This guide explains how to run your own librenotes instance with Docker Compose. For day-2 ops (deploys, backups) see operations.md.

System requirements

  • Linux server with Docker Engine 24+ and the Compose plugin.
  • At least 256 MB of RAM and 1 GB of disk for the application plus whatever space you expect notes to consume (typically measured in MB even for heavy users).
  • A public DNS name pointing to the host. Magic-link sign-in will not work over plain IP because some email clients refuse to follow links to bare-IP URLs.
  • An SMTP relay (or a service like Postmark/SES) that the host can reach on port 587 or 465.
  • A reverse proxy (nginx, Caddy, or Traefik) terminating TLS in front of librenotes.

Quick start

git clone https://git.librete.ch/public/librenotes
cd librenotes
cp .env.example .env  # edit values, see below
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Visit https://your-domain/ and sign in.

Environment configuration

The runtime reads its configuration from environment variables. Place them in an .env file next to the Compose stack.

Variable Required Description
LIBRENOTES_BASE_URL yes Public origin used in magic links, e.g. https://notes.example.com. Must match what users will type.
LIBRENOTES_JWT_SECRET yes At least 32 bytes of random data. Sessions are invalidated when this changes.
LIBRENOTES_SMTP_HOST yes (prod) SMTP server hostname. Without it, magic links are logged to stdout (useful for dev only).
LIBRENOTES_SMTP_PORT no Default 587.
LIBRENOTES_SMTP_USER conditional SMTP credential.
LIBRENOTES_SMTP_PASS conditional SMTP credential.
LIBRENOTES_SMTP_FROM yes Envelope sender, e.g. no-reply@notes.example.com.
LIBRENOTES_DATA_DIR no Default /data inside the container.
LIBRENOTES_DB no Default /var/lib/librenotes/librenotes.db.
LIBRENOTES_IMAGE yes (prod) Image tag to pull, e.g. registry.librete.ch/librenotes:v0.1.0.

Generate a JWT secret:

openssl rand -hex 32

Reverse proxy

Caddy

notes.example.com {
    encode zstd gzip
    reverse_proxy localhost:8080
}

Nginx

server {
    listen 443 ssl http2;
    server_name notes.example.com;

    ssl_certificate     /etc/letsencrypt/live/notes.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem;

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

The application speaks plain HTTP and trusts the proxy for the public URL via LIBRENOTES_BASE_URL. Always terminate TLS at the proxy.

Volumes

The Compose stack declares two named volumes:

  • notes mounted at /data — per-tenant note files.
  • state mounted at /var/lib/librenotes — the SQLite database.

Both must be on persistent storage. Backing them up is covered in operations.md.

Health check

GET /healthz returns {"status":"ok"} when the binary is running. The Compose stacks invoke librenotes healthcheck from inside the container (since the runtime image has no shell tools to use curl/wget).

Updating

Pull the new image and restart:

docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

To pin a specific version, set LIBRENOTES_IMAGE to that tag in .env. Rolling back is the same command after editing .env.