operations.md lists the full secret/variable matrix the deploy workflow expects, the new compose.yaml + compose.netcup.yaml invocation, and a note on why both workflow jobs pin catthehacker/ubuntu:runner-latest. self-hosting.md updates the example LIBRENOTES_IMAGE value to the Gitea Packages path and switches the quick-start to compose.netcup.yaml.
4.0 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.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:
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:
notesmounted at/data— per-tenant note files.statemounted 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 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.