# Self-hosting This guide explains how to run your own librenotes instance with Docker Compose. For day-2 ops (deploys, backups) see [operations.md](./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 ```sh git clone https://git.librete.ch/public/librenotes cd librenotes cp .env.netcup.example .env # edit values, see below docker compose -f compose.yaml -f compose.netcup.yaml pull docker compose -f compose.yaml -f compose.netcup.yaml up -d ``` The `compose.netcup.yaml` overlay is the production-style overlay shipped in this repo; it pulls a published image, drops host port binding (assumes a fronting reverse proxy), and joins an external `edge` docker network. Adapt to your topology if needed. 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. `git.librete.ch/public/librenotes:v0.1.0`. | Generate a JWT secret: ```sh openssl rand -hex 32 ``` ## Reverse proxy ### Caddy ```caddy notes.example.com { encode zstd gzip reverse_proxy localhost:8080 } ``` ### Nginx ```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](./operations.md#backups). ## 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: ```sh docker compose -f compose.yaml -f compose.netcup.yaml pull docker compose -f compose.yaml -f compose.netcup.yaml up -d ``` To pin a specific version, set `LIBRENOTES_IMAGE` to that tag in `.env`. Rolling back is the same command after editing `.env`.