Skip to content

Get started

Requirements

Lunedo 0.1.0
Other versions
0.1.0 Nov 1, 2026

Opens the documentation of that release on GitHub.

Last updated

What Lunedo needs to run. The setup script checks the host part itself and says what is missing.

Requirement Value
Operating system Linux. Lunedo is not supported on macOS or Windows hosts, and the setup script refuses to run on macOS.
Container runtime Docker Engine 24 or newer with Docker Compose v2.
Architecture The image ghcr.io/lunedo/lunedo is published for linux/amd64 and linux/arm64.
CPU and RAM Depend on the size of the organization; see Measured requirements.
Disk space The setup script needs at least 4 GB free to install. What the data and backups need later is listed under Measured requirements.
Port One free host port for Lunedo, 3000 by default.
Clock Synchronized, for example with NTP. The system check warns when Lunedo and the database differ by more than 5 seconds.
Requirement Value
Server PostgreSQL 16, 17 or 18. The image contains pg_dump, pg_restore and psql for these three major versions and picks the one that matches the server for backups. The Compose file of the setup script uses postgres:17-alpine.
Shared memory 1 GB of /dev/shm for the PostgreSQL container (shm_size: 1gb in the Compose file of the setup script). Parallel queries on large databases fail without it, because Docker gives a container only 64 MB. It is used only while queries need it and counts towards the memory of PostgreSQL.
Connection settings Lunedo turns off just-in-time compilation (jit) for its own connections by sending -c jit=off as a startup option. Nothing changes on the server.
Extension pg_trgm, for search. A migration creates it with CREATE EXTENSION IF NOT EXISTS "pg_trgm". It is a trusted extension, so a database user with the CREATE privilege on the database can create it. On a managed server that does not allow this, create it beforehand as an administrator of the server.
Rights The user in LUNEDO_DATABASE_URL owns the database or may create tables, types and indexes in it. Lunedo applies its migrations at every start.
Optional right CREATEDB for the monthly test restore of backups, which restores into a temporary database on the same server, for example ALTER ROLE "lunedo" CREATEDB;. Without it, a test restore only verifies the backup. See Backups.
Free space Room for one more copy of the database while a test restore runs.
Storage An SSD for the database from size medium on; for large, an NVMe SSD is recommended. Saving a change waits until PostgreSQL has written it to disk, so the database storage should not be shared with write-heavy workloads such as CI runners, other databases or backups during working hours: on the reference machine, another workload writing to the same SSD made page opens take seconds and requests fail. On shared storage, use SSDs with power-loss protection, which confirm writes from their cache.

A container runs the worker, the collaboration server and one API process per CPU, at most 8 (LUNEDO_API_PROCESSES sets another number). Requests are spread over the API processes, so the API uses more than one CPU without a proxy in front of several containers.

Each process has its own pool of database connections. Without LUNEDO_DATABASE_POOL_SIZE, the worker and the collaboration server use twice the number of CPUs of the container plus 2, at least 6 and at most 24. A single API process uses the same number; with several API processes, it is divided among them, at least 8 each. With LUNEDO_DATABASE_POOL_SIZE, every process, each API process included, uses that number. Every process also holds one connection for notifications; the worker holds up to four more for the job queue and every API process one.

Add up for every container that uses the same database:

  • worker: pool size + 5
  • collaboration server: pool size + 1
  • every API process: pool size + 2 + search slots

The sum, plus a few connections for backups and psql, must stay below max_connections of PostgreSQL. With the defaults, one container on up to 4 CPUs stays below the PostgreSQL default of 100, for example 78 connections on 4 CPUs; on 8 CPUs it needs 154. The PostgreSQL container of the setup script sets max_connections to 200. Raise max_connections before you raise the pool size or the number of API processes, or run more containers.

Searches run in PostgreSQL in a lane of their own, so that many broad searches cannot slow down opening pages:

  • At most LUNEDO_SEARCH_SLOTS searches run at the same time, counted for all API processes and containers that use the same database: by default one less than the CPUs of the container, at least 1 and at most 4. When PostgreSQL runs on another machine, set it to one less than that machine’s CPUs.
  • A search waits for a free slot for at most LUNEDO_SEARCH_WAIT_SECONDS (2 by default) without holding a database connection. After that the API answers 429 with Retry-After, and the app says that search is busy and tries once more.
  • Searches use connections of their own (lunedo-search in pg_stat_activity, one per slot in every API process), without parallel query, and each statement may run at most LUNEDO_SEARCH_TIMEOUT_SECONDS (5 by default).
  • Searches in knowledge base portals, also by visitors who are not signed in, use the same lane. When no slot becomes free in time, the portal answers 503 and asks to search again, and its start page leaves out the frequent searches.

A search with more than 1,000 matches, counted as matching sections of pages and files rather than as results, counts and ranks only the 1,000 most recently changed of them; the app shows the number with a plus sign and says that it shows the most recently changed results. On large instances a broad search therefore prefers recently changed pages. Add more words to narrow it to fewer matches and find older ones.

The metrics lunedo_search_slot_wait_seconds, lunedo_search_slots_in_use and lunedo_search_rejected_total show how full the lane is; see Monitoring.

Every connection keeps the statements Lunedo uses repeatedly prepared, at most 150 per connection with their plans, so PostgreSQL does not plan them again on every call. Lunedo needs connections that keep their session: directly to PostgreSQL, or through a pooler in session mode such as PgBouncer with pool_mode = session. A pooler in transaction mode is not supported, because Lunedo uses notifications (LISTEN) and releases prepared statements with DEALLOCATE.

Limits that protect the memory of a process count per API process: diagram sanitizing and uploads, Word and PDF files created at once and password checks.

Migrations only go forward. When several processes start at the same time, a PostgreSQL advisory lock lets one of them migrate while the others wait. Readiness (/health/ready) answers 503 while the schema is behind the code. When migrations are pending, the first process takes a backup before it migrates; see Backups before updates.

Service When it is needed
Redis or Valkey Only when more than one collaboration server runs. Set LUNEDO_REDIS_URL on every collaboration node; see Several nodes. Any Redis-compatible server works.
Render container Only for PDF files created on the server. The image ghcr.io/lunedo/render runs headless Chromium; see Rendering.
S3-compatible storage Only when attachments or backups go to object storage. See File storage and Backups in S3.
Mail server or Microsoft Graph For invitations, password resets and e-mail notifications. See E-mail.

One image holds every part of Lunedo. The first argument of the container chooses what it runs:

Role Port Runs
all 3000, 3001, 3002 The API, the collaboration server and the worker in one container. The default, and what the Compose files use.
api 3000 The API and the web client. It also forwards the collaboration WebSocket on /collab to LUNEDO_COLLAB_UPSTREAM.
collab 3001 The collaboration server for real-time editing.
worker 3002 Background jobs: backups, mail, search indexing, imports, rendering and the update check.

In all, only the API port needs to be published; the API forwards /collab to the collaboration server in the same container. Each process answers /health/live, /health/ready and /metrics on its own port; see Monitoring.

Every other argument runs a command instead of a server, such as backup, keys, audit, search, maintenance, version or release-notes. See Command line.

The container runs as the user node with UID 1000 and GID 1000.

Path in the container Content
/var/lib/lunedo The data volume: files (the file store, LUNEDO_FILES_DIR), secrets (generated secrets, LUNEDO_SECRETS_DIR), backups (LUNEDO_BACKUP_DIR) and work (temporary files of backups, LUNEDO_WORK_DIR).
/data/import The import folder for large exports (LUNEDO_IMPORT_DIR). Mount it read-only; see Imports.

Directories that you mount must belong to UID 1000, or the system check reports that the file store is not writable.

This section is generated from docs/data/requirements.json, packages/server/src/backup/configuration.ts by pnpm docs:generate. Do not edit it by hand; change the sources and run the command again.

Measured on a reference machine with 4 vCPU (12th Gen Intel(R) Core(TM) i5-12500), 7.8 GiB of RAM and 272.5 GB of disk (ZFS on one consumer NVMe SSD), running Ubuntu 24.04.5 LTS with kernel 6.8.0-147-generic, Docker 29.9.2, Docker Compose 5.6.1 and PostgreSQL 17.11. Each size ran on a fresh installation from the setup script, without TLS and without a reverse proxy, with synthetic content and the default limits. Size, backups and start times were measured on 2026-11-01 with commit 8c70161e. The load came from one computer, which sent the requests of each person from a client address of its own, in load runs of 15 minutes.

The load runs follow one of two scenarios. Both are assumptions about how people use a wiki, not measured use. In both, five of the people active at the same time edit the same page together.

Scenario Wait between two actions Open a page Search Edit and publish Notifications and For you
typical 30 to 90 seconds, 60 on average 70 % 15 % 2 % 13 %
peak 7 to 36 seconds, 18 on average 70 % 15 % 10 % 5 %

The targets apply to typical; peak shows how much room is left. A size measured only with peak is judged by peak, and its hardware comes from peak. Response times of the first minutes of a run, while the people start and the load generator opens its connections, are not counted where the table says so; errors always are.

Size Scenario Load runs Measured on Commit Not counted at the start
small peak 3 2026-10-29 8c70161e nothing
medium typical 2 2026-11-01 adb35241 4 min
medium peak 2 2026-11-01 adb35241 4 min
large typical 2 2026-10-31 21e978c3 4 min
large peak 3 2026-10-31 21e978c3 4 min
Size People Pages Spaces Attachments Active at the same time
small 25 2,002 10 1.9 GB 5
medium 250 20,048 60 19.5 GB 50
large 1,000 99,594 200 48.9 GB 200

Minimum is what the reference machine used at the peak of the load runs, rounded up: CPU from the busy time of the whole machine, but at least 2 vCPU, because fewer were never measured; RAM from the Lunedo and PostgreSQL containers. Add what the operating system needs. Recommended RAM is the peak × 1.5, and recommended CPU keeps the response times below the targets with room for peaks: at least the peak × 1.5 and at least what the peak scenario used, never more than the reference machine has. Disk is the image, the database, the file store and backups, with 30 % headroom; with backups on the same host, it counts the 20 backups that the default retention keeps.

Size Minimum CPU Minimum RAM Minimum disk Recommended CPU Recommended RAM Recommended disk, backups elsewhere Recommended disk, backups on this host
small 2 vCPU 2 GiB 6 GB 3 vCPU 3 GiB 7 GB 56 GB
medium 2 vCPU 5 GiB 42 GB 2 vCPU 7 GiB 55 GB 541 GB
large 2 vCPU 5 GiB 107 GB 3 vCPU 7 GiB 139 GB 1,371 GB

Median of the load runs; p95 is the time within which 95 % of the requests finished. Readable is the time until the page is shown in the browser.

Size Scenario Open a page, API Open a page, readable Search Save For you and notifications Errors
small peak 160 ms / 256 ms 353 ms / 855 ms 86 ms / 136 ms 54 ms / 85 ms 45 ms / 69 ms 0
medium typical 35 ms / 59 ms 223 ms / 261 ms 65 ms / 95 ms 27 ms / 38 ms 124 ms / 142 ms 0
medium peak 35 ms / 60 ms 223 ms / 246 ms 66 ms / 97 ms 24 ms / 42 ms 127 ms / 152 ms 0
large typical 38 ms / 67 ms 223 ms / 249 ms 180 ms / 229 ms 22 ms / 59 ms 21 ms / 38 ms 0
large peak 43 ms / 105 ms 227 ms / 333 ms 199 ms / 480 ms 24 ms / 57 ms 25 ms / 66 ms 0

Each cell shows p50 / p95.

Size Scenario Target Limit Measured (worst run) Met
small peak Open a page, API (p95) below 500 ms 263 ms yes
small peak Open a page, readable (p95) below 1.50 s 879 ms yes
small peak Search (p95) below 800 ms 148 ms yes
small peak Save (p95) below 500 ms 101 ms yes
small peak Errors 0 0 yes
medium typical Open a page, API (p95) below 500 ms 60 ms yes
medium typical Open a page, readable (p95) below 1.50 s 272 ms yes
medium typical Search (p95) below 800 ms 95 ms yes
medium typical Save (p95) below 500 ms 39 ms yes
medium typical Errors 0 0 yes
medium peak Open a page, API (p95) below 500 ms 60 ms yes
medium peak Open a page, readable (p95) below 1.50 s 248 ms yes
medium peak Search (p95) below 800 ms 101 ms yes
medium peak Save (p95) below 500 ms 43 ms yes
medium peak Errors 0 0 yes
large typical Open a page, API (p95) below 500 ms 67 ms yes
large typical Open a page, readable (p95) below 1.50 s 257 ms yes
large typical Search (p95) below 800 ms 231 ms yes
large typical Save (p95) below 500 ms 81 ms yes
large typical Errors 0 0 yes
large peak Open a page, API (p95) below 500 ms 106 ms yes
large peak Open a page, readable (p95) below 1.50 s 334 ms yes
large peak Search (p95) below 800 ms 538 ms yes
large peak Save (p95) below 500 ms 62 ms yes
large peak Errors 0 0 yes
Size Database File store Image One full backup Backup takes Restore takes Start until ready RAM when idle New page found in search (p95) Job queue under load (peak)
small 150 MB 2.0 GB 1.1 GB 2.0 GB 45.5 s 22.8 s 9.30 s 741 MiB 700 ms 16
medium 1.3 GB 19.6 GB 1.1 GB 19.7 GB 7 min 25 s 4 min 52 s 9.30 s 826 MiB 1.00 s 16
large 6.4 GB 49.2 GB 1.1 GB 49.9 GB 20 min 8 s 17 min 4 s 10.0 s 885 MiB 2.40 s 16

How fast the content of each size was written through the public API, as a migration script would do it: pages with their versions, attachments, comments and permissions, by several clients at once.

Size Clients at once Pages per minute Versions per minute Requests per second Upload Busy CPU
small 4 240 1,189 47 4.1 MB/s 80 %
medium 4 263 1,314 52 4.4 MB/s 80 %
medium 8 276 1,388 55 4.6 MB/s 92 %
large 4 228 1,142 43 2.0 MB/s 83 %