I wanted a Notion I could actually own — everything in one SQLite file on a box I control, no subscription, no cloud, and few enough features that one person can keep them all working. That's Notara: a self-hostable, fair-source Notion alternative with a block editor, inline databases, and real-time collaboration. The source is public, it's free to self-host, and there's nothing to be locked into.
It's built and maintained by one person (me), on purpose. The guiding rule is fewer features, done well — so the list below is deliberately short.
| Block editor | Paragraphs, headings, todos, code, toggles, callouts, images, PDFs and more |
| Inline databases | Table and board views with fields, relations and custom views |
| Real-time collaboration | Invite by email or link, see who's on the page, edit together |
| Full-text search | Across page titles and block content |
| Trash & restore | Soft-delete anything; restore in a click, auto-purge after your retention window |
| Import / Export | Notion Markdown and CSV in; export back out anytime |
| S3 backups | Optional scheduled backups to any S3-compatible bucket |
| Desktop app | Native Electron app for macOS — plus the web client |
| Own your data | One SQLite file per workspace, on infrastructure you control |
Notara is a single container with no external services — one SQLite file on a mounted volume is the whole database. There's no prebuilt image to trust: every method below builds from the source in this repo, so you run exactly what you can read.
It runs as one instance, on purpose. The rate limiter and presence state live in-process. Run a single container behind your reverse proxy and scale up (a bigger box), not out. A $5 VM is plenty to start.
The quickest way in:
git clone https://github.com/dnzzl/notara
cd notara
# set at least BETTER_AUTH_SECRET in the environment: section of docker-compose.yml
docker compose up -d --buildOpen http://localhost:3000 and create your account.
Both work the same — swap docker for podman if that's your world:
podman build -t notara .
podman run -d --name notara -p 3000:3000 \
-v notara-data:/data \
-e DATA_DIR=/data \
-e BETTER_AUTH_SECRET="$(openssl rand -base64 32)" \
notaraFor a rootless, boot-persistent install managed by systemd. Build the image first
(podman build -t notara .), store the auth secret, then drop a .container unit in the
Quadlet directory:
# ~/.config/containers/systemd/notara.container (rootless)
# /etc/containers/systemd/notara.container (system-wide)
[Unit]
Description=Notara
After=network-online.target
Wants=network-online.target
[Container]
Image=localhost/notara:latest
PublishPort=3000:3000
Volume=notara-data:/data
Environment=DATA_DIR=/data
Environment=BASE_URL=https://notes.example.com
Environment=TRUSTED_ORIGINS=https://notes.example.com
Secret=notara-auth-secret,type=env,target=BETTER_AUTH_SECRET
[Service]
Restart=always
[Install]
WantedBy=default.targetThen generate the secret, let systemd pick up the unit, and start it:
podman secret create notara-auth-secret <(openssl rand -base64 32)
systemctl --user daemon-reload
systemctl --user start notara
loginctl enable-linger "$USER" # keep it running after you log outQuadlet turns the .container file into a real notara.service — systemctl --user status notara, journalctl --user -u notara, and auto-restart all work as usual. Drop the
--user flags for a system-wide unit.
fly launch --no-deploy # detects the Dockerfile
fly volume create notara_data --size 1 # persistent SQLite storage
# in fly.toml: mount the volume at /data and set DATA_DIR=/data
fly secrets set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
fly deployNotara is scriptable end to end — the app is one way in, not the only one.
notara — the CLI. The scriptable client over the REST API. Install from JSR
(npx jsr add @notara/cli), then pipe your wiki into anything:
notara pages list --json | jq '.title'The REST API. A documented HTTP API at /api/v1 — read, write, search, manage. Built
for automation, CI, and agents. Interactive docs at GET /api/docs, OpenAPI at
GET /api/v1/openapi.json. Generate a key in the sidebar → API keys and pass it as
Authorization: Bearer ntr_…:
curl https://notes.example.com/api/v1/workspaces/<workspaceId>/pages \
-H "Authorization: Bearer ntr_your_key_here"The desktop app. Native macOS Electron build — lives in your dock, works offline, syncs to your server.
Set these in your docker-compose.yml → environment:, a Quadlet Environment=/secret,
a .env file, or your platform's env settings.
| Variable | Description |
|---|---|
BETTER_AUTH_SECRET |
Secret used to sign session tokens. Generate with openssl rand -base64 32. Rotating it invalidates all sessions. |
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port. (Fly injects this automatically.) |
DATA_DIR |
./.data |
Where SQLite databases and attachments live. Mount a persistent volume here. |
BASE_URL |
http://localhost:3000 |
Public URL of the instance — used in invite/reset emails. Must match what users type. |
TRUSTED_ORIGINS |
http://localhost:5173 |
Comma-separated origins allowed for CORS and session cookies. Set to your public URL in production. |
Email / SMTP (optional — enables password reset & email invites)
All SMTP_* variables are optional. Without them the app runs fine but password reset and email invitations are disabled.
| Variable | Default | Description |
|---|---|---|
SMTP_HOST |
— | e.g. smtp.resend.com, smtp.sendgrid.net, mail.example.com. |
SMTP_PORT |
587 |
587 for STARTTLS (recommended), 465 for implicit SSL. |
SMTP_SECURE |
false |
true only for port 465. |
SMTP_USER |
— | Usually your email address or an API key username. |
SMTP_PASS |
— | SMTP password or API key. |
SMTP_FROM |
Notara <no-reply@notara.app> |
The "From" address. Use a domain you control. |
Resend (recommended for new installs):
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=resend
SMTP_PASS=re_xxxxxxxxxxxxxxxxxxxx # your Resend API key
SMTP_FROM=Notara <no-reply@yourdomain.com>Postmark, Mailgun, Gmail App Passwords, and self-hosted (Postfix/Exim/Maddy) all work — use the same shape with that provider's host/credentials.
Google OAuth (optional — adds "Continue with Google")
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
OAuth 2.0 client ID from Google Cloud Console. |
GOOGLE_CLIENT_SECRET |
OAuth client secret. Add <BASE_URL>/api/auth/callback/google as an authorised redirect URI. |
S3 backups (optional)
Configure at runtime in ⚙ Settings → S3 backup. Works with AWS S3, Backblaze B2, Cloudflare R2, MinIO, and any S3-compatible service. Settings are saved to .data/settings.json.
PostHog analytics (optional, opt-in)
| Variable | Default | Description |
|---|---|---|
POSTHOG_KEY |
— | Server-side error reporting + product events. |
POSTHOG_HOST |
https://eu.i.posthog.com |
PostHog host. |
VITE_POSTHOG_KEY |
— | Frontend analytics key (build-time Vite var). |
VITE_POSTHOG_HOST |
https://eu.i.posthog.com |
Frontend PostHog host. |
PostHog only loads after the user accepts the consent banner (GDPR opt-in). The distinctId is the user's internal ID — never an email or other PII.
Admin panel (optional)
| Variable | Description |
|---|---|
ADMIN_EMAILS |
Comma-separated emails that can access /admin. Without it, the admin panel is disabled. |
Running behind a reverse proxy
Set BASE_URL and TRUSTED_ORIGINS to your public domain.
Caddy:
notes.example.com {
reverse_proxy localhost:3000
}
Nginx:
server {
listen 443 ssl;
server_name notes.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}.data/
├── platform.db # auth, users, workspaces
├── workspaces/
│ └── <workspace-id>.db # pages, blocks, databases (one file per workspace)
├── attachments/ # uploaded images and PDFs
└── settings.json # S3 backup config
Back up the entire .data/ directory to keep everything.
Prerequisites: Bun ≥ 1.1
git clone https://github.com/dnzzl/notara
cd notara
bun install
# Terminal 1 — backend
bun run dev:server
# Terminal 2 — frontend
bun run dev:appOpen http://localhost:5173. Create .env in the repo root with at least
BETTER_AUTH_SECRET=any-random-string-for-dev.
bun run build # production build
bun test # unit tests
bunx playwright test # E2E (requires built app)Notara is fair-source under the Functional Source License, FSL-1.1-ALv2.
- Free to self-host, run, modify, and redistribute — personal, internal business, or commercial.
- One restriction: you may not offer Notara (or a derivative) as a commercial hosted service that competes with it.
- Becomes fully open: each release automatically converts to the Apache License 2.0 two years after it ships.
No license key, no seat limit, no subscription. See LICENSE for the full terms.
Found a bug? Open an issue with the Bug report template — version/commit, deployment, browser + OS, expected vs actual, steps, logs (secrets redacted).
Bug fixes and docs PRs are welcome directly. New features need a feature issue that I've approved before you build — I'm one person, and I'd rather you didn't sink a weekend into a PR I can't take. Contributions ship under FSL-1.1-ALv2; the full policy is in CONTRIBUTING.md. Be kind; assume good intent.
Security vulnerabilities: email legrand.thomas5@hotmail.fr — never a public issue.

