Upstream docs: https://usememos.com/docs
Everything not listed in this document should behave the same as upstream Memos. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable.
Memos is an open-source, self-hosted note-taking service built for quick capture. It stores notes as Markdown, runs as a single lightweight Go binary with an embedded SQLite database, and exposes REST and gRPC APIs. Upstream: https://github.com/usememos/memos (MIT).
- Image and Container Runtime
- Volume and Data Layout
- Installation and First-Run Flow
- Configuration Management
- Network Access and Interfaces
- Actions (StartOS UI)
- Backups and Restore
- Health Checks
- Dependencies
- Limitations and Differences
- What Is Unchanged from Upstream
- Contributing
- Quick Reference for AI Consumers
One unmodified upstream image, run as a single StartOS daemon:
| Daemon | Image | Architectures |
|---|---|---|
memos |
neosmemo/memos:0.30.0 |
x86_64, aarch64 |
The image has a real ENTRYPOINT:
['/usr/local/memos/entrypoint.sh', '/usr/local/memos/memos']
Consequences baked into this package:
- The entrypoint script
entrypoint.shstarts as root,chown -R 10001:10001 /var/opt/memos, thenexec su-exec 10001:10001 memos— so the app process ends up non-root (UID/GID10001= thenonrootuser). The StartOS-owned volume at/var/opt/memosis auto-chowned at every boot and the app writes succeed as nonroot. useEntrypoint()(no override) preserves that ENTRYPOINT+CMD. Because the image has a real entrypoint (not a CMD-only image), the "useEntrypoint-on-a-CMD-only-image" risk does NOT apply here.su-execis an exec tool that replaces the process (likegosu), NOT a PID-1 init supervisor (s6/tini/dumb-init/supervisord), sorunAsInitis intentionally left at its default.MEMOS_UID/MEMOS_GIDare left at their default10001— the entrypoint chowns the mounted volume as root before dropping privileges. Do NOT override them to 0 (that is the rootless-Docker case the entrypoint'sMEMOS_ENTRYPOINT_SWITCHEDguard protects against, and would make memos run as root permanently).
One volume:
| Volume | Mount point | Contents |
|---|---|---|
main |
/var/opt/memos |
SQLite DB (memos_prod.db) + uploaded assets |
store.json lives at the root of the main volume. It holds the optional
MEMOS_INSTANCE_URL pin (see Quick Reference), seeded to '' at install by
init/seedFiles.ts and NOT regenerated on restore.
- No secrets are generated. Memos uses SQLite with no password; the only
persisted package state is
instanceUrl: ''(auto-derive). - No admin-credential action exists. Memos has no CLI/gRPC subcommand to provision an admin. Sign-up is open by default and the first account created via the web UI becomes the HOST (admin). Close public sign-up afterward inside Memos's own admin Settings.
MEMOS_INSTANCE_URLis derived automatically from the liveuiinterface address (preferring publicly-reachable hosts). If no address is available, Memos runs privately. Anoptionaltask (init/watchInstanceUrl.ts) points RSS/webhook users at the Set Instance URL action.- Memos 0.30.0 applies its own SQLite migrations on startup; the package does not add a custom database migration.
| StartOS-Managed | Upstream / not yet exposed |
|---|---|
MEMOS_PORT (fixed to 5230) |
External PostgreSQL/MySQL backend (MEMOS_DRIVER=postgres|mysql + MEMOS_DSN) |
MEMOS_DATA (fixed to /var/opt/memos) |
SMTP/email config |
MEMOS_DRIVER (fixed to sqlite) |
AI/LLM provider configuration |
MEMOS_INSTANCE_URL (auto-derived or pinned via the action) |
Multi-user / SSO specifics |
MEMOS_LOG_LEVEL (fixed to info) |
| Interface | Internal port | Protocol | Purpose |
|---|---|---|---|
ui |
5230 |
http |
The web UI (also serves the REST/gRPC API). Single exposed interface. |
No sidecars, no peer/SMTP/DB ports. Reachable via LAN IP, .local, .onion,
and custom StartOS domains like any UI interface.
- Set Instance URL (
set-instance-url)- Purpose: pin (or unpin via Auto) the origin used for
MEMOS_INSTANCE_URL. - Visibility: always enabled; also surfaced as an
optionaltask. - Inputs: a
dynamicSelectof theuiinterface's current non-local hostnames (plus an Auto option). - Outputs: the chosen host. The service restarts to apply it.
- When to use: use a pinned stable external origin for RSS feeds, webhooks, or public anonymous access. Auto may derive a changing StartOS address; if no address is available, Memos remains private.
- Purpose: pin (or unpin via Auto) the origin used for
sdk.Backups.ofVolumes('main') captures a whole-volume rsync of the main
volume:
| Backed up | How |
|---|---|
SQLite DB + uploaded assets (main volume) |
whole-volume rsync (includes store.json) |
The package declares a whole-volume backup of main, including the SQLite
database, uploaded assets, and store.json. Restoring is intended to bring
back notes, accounts, and uploaded assets; the service should then start
cleanly.
| Daemon | Probe | Messages |
|---|---|---|
memos |
checkWebUrl http://127.0.0.1:5230/ (displayed), 30 s grace |
"The web interface is ready" / "The web interface is not ready" |
checkWebUrl (not just port-listening) catches "port bound but app still
booting". 30 s grace is generous for a Go binary first boot.
None. Memos uses an embedded SQLite database — no external DB or sidecars.
- No admin-credential action. The first web sign-up becomes the HOST (admin). There is no CLI/API to provision an admin user upstream. Close public sign-up afterward in Memos's own Settings.
MEMOS_INSTANCE_URLauto-derivation. StartOS fronts the service with a reverse proxy reachable at several addresses; the package derivesMEMOS_INSTANCE_URLfrom theuiinterface's current public address (preferring clearnet/Tor, falling back to LAN). If no address is available, Memos remains private. For RSS/webhooks, pin the Instance URL to your registered external domain.- SQLite only. This package uses the embedded SQLite backend. External PostgreSQL/MySQL is out of scope for v1.
- Backup size — uploaded assets under
/var/opt/memoscan grow with use. v1 backs up the whole volume (ofVolumes); switch that volume toaddSync(incremental rsync) with anexcludefor transient caches if it balloons.
- Instances without
MEMOS_INSTANCE_URLare private; anonymous API access is restricted and RSS feeds are unavailable. Use a pinned stable URL when public access or RSS is required. - Saved time filters using
now()must use the 0.30.0 timestamp syntax. - Shared-memo API clients must use the new shared-memo route and resource name.
- MCP clients must use
/mcpand the new service-prefixed tool names. - Existing instance tag settings are copied to users by the upstream SQLite migration on startup.
- The web UI, REST/gRPC APIs, Markdown notes, tags, resources (attachments), and RSS follow the upstream docs, subject to the 0.30.0 changes above.
- The image's bundled entrypoint script (chown + su-exec) and non-root runtime model are unchanged.
- SQLite is the default DB backend (upstream default).
See AGENTS.md for the agent workflow, the SDK pin rationale,
and how to inspect a running install. The remaining verification checklist
lives in TODO.md.
package_id: memos
architectures: [x86_64, aarch64]
image: neosmemo/memos:0.30.0
volumes:
main: /var/opt/memos
ports:
ui: 5230
dependencies: []
startos_managed_env_vars:
- MEMOS_PORT # 5230
- MEMOS_DATA # /var/opt/memos
- MEMOS_DRIVER # sqlite
- MEMOS_INSTANCE_URL # auto-derived from ui host, or pinned via Set Instance URL
- MEMOS_LOG_LEVEL # info
actions:
- set-instance-url
store_json:
instanceUrl: pinned MEMOS_INSTANCE_URL origin (empty = auto-derive; no address = private)
sdk: @start9labs/start-sdk@2.0.9
os_version: 0.4.0-beta.10 (SDK-stamped; runtime-verified on host 0.4.0.1)