Deploy
One-click cloud buttons, Docker, Compose, source builds, and backups.
Prerequisites
- Docker deploys: Docker Engine on any Linux box, NAS, Raspberry Pi, or VPS. No repository clone needed — the image is on GitHub Container Registry.
- Source deploys:
git, Node.js 22+, pnpm. - One-click cloud deploys: just an account with the provider.
One-click cloud deploys
NemoMemo is a stateful app (SQLite + uploads on disk), so it needs a host with a persistent volume — which is why you won't see Vercel/Netlify buttons here; those platforms are for static and serverless apps and would lose your data.
- Render — the button reads this repo's
render.yaml: one Docker web service with a 1 GB persistent disk mounted at/app/dataand a health check, preconfigured. Persistent disks require Render's paid Starter instance (about $7/month). Sign in, click Apply, done. - Koyeb — the button prefills a Dockerfile build from this repo. After the first
deploy, attach a volume to the service mounted at
/app/datain the Koyeb dashboard (Volumes → attach) so your data survives redeploys.
Both providers inject a PORT environment variable, which NemoMemo respects
automatically.
Docker
docker run -d --name nemomemo \
-p 5230:5230 \
-v nemomemo-data:/app/data \
ghcr.io/davidallmon/nemomemo:latestEverything lives in the /app/data volume: nemomemo.db (SQLite) and uploads/.
Images are published for amd64 and arm64 (Raspberry Pi 4/5 works). Tags: latest
tracks main; version tags appear on releases.
Upgrading:
docker pull ghcr.io/davidallmon/nemomemo:latest
docker rm -f nemomemo && docker run -d --name nemomemo \
-p 5230:5230 -v nemomemo-data:/app/data ghcr.io/davidallmon/nemomemo:latestDocker Compose
services:
nemomemo:
image: ghcr.io/davidallmon/nemomemo:latest
ports:
- "5230:5230"
volumes:
- nemomemo-data:/app/data
restart: unless-stopped
volumes:
nemomemo-data:Build from source
git clone https://github.com/DavidAllmon/nemomemo.git
cd nemomemo
pnpm install --filter '!@nemomemo/site'
pnpm build
NEMOMEMO_WEB_DIST=web/dist node server/dist/index.jsOr build the image yourself: docker build -t nemomemo . (the image contains only the
app — never the marketing/docs site).
Configuration
| Env var | Default | Purpose |
|---|---|---|
NEMOMEMO_PORT (or PORT) | 5230 | HTTP port |
NEMOMEMO_DATA | ./data | Data directory |
DORY_TTL_SECONDS | 86400 | How long Dory remembers |
NEMOMEMO_WEB_DIST | — | Path to the built SPA (source installs) |
NEMOMEMO_SMTP_HOST | — | SMTP server for outbound email (optional — see below) |
NEMOMEMO_SMTP_PORT | 587 | SMTP port (465 switches to implicit TLS) |
NEMOMEMO_SMTP_USER | — | SMTP login |
NEMOMEMO_SMTP_PASS | — | SMTP password / API key |
NEMOMEMO_SMTP_FROM | — | From address, e.g. "NemoMemo <[email protected]>" |
Email (optional, recommended)
Set all five NEMOMEMO_SMTP_* variables and your reef can send email — any SMTP
provider works (Brevo and Postmark have friendly free tiers, or your own relay):
NEMOMEMO_SMTP_HOST=smtp-relay.example.com
NEMOMEMO_SMTP_PORT=587
NEMOMEMO_SMTP_USER=[email protected]
NEMOMEMO_SMTP_PASS=your-smtp-key
NEMOMEMO_SMTP_FROM="NemoMemo <[email protected]>"With email on, your reef gets: welcome + verification emails for new members, self-serve password reset ("Forgot your password?" on the sign-in page), member invites (add someone by email and they pick their own password), and security heads-ups when a password or email changes.
Without it, everything still works: accounts still sign up with an email address (it's part of every account since v1.8), but nothing sends — members who forget a password ask their reefkeeper, who can set a new one in Settings → Members.
Exposing it to the internet
Put NemoMemo behind HTTPS — either a reverse proxy (Caddy, nginx, Traefik) terminating
TLS in front of port 5230, or a Cloudflare Tunnel pointing at
http://localhost:5230 (no open ports needed; great for homelabs).
Backups
Your whole reef is two things inside the data volume: nemomemo.db (SQLite) and
uploads/. Back up both. (On NemoMemo Cloud we do this for you nightly — self-hosting
means this part is yours.)
The easy way: the button
As the reefkeeper (admin), open Settings → Backups → Download backup. You get one zip containing a consistent database snapshot (safe even while people are writing) and every uploaded file. Do that on a schedule you'll actually keep, store the zip somewhere that isn't the server, and you're covered. Everything below is for automating it.
The readable way: Markdown export
Any memo can be saved as a plain .md file from its ⋯ → Copy → Download as .md
menu — created/updated times, visibility, and tags ride along as YAML frontmatter,
so it reads correctly in any Markdown app. Power users can grab everything at once:
while signed in, GET /api/v1/memos/export/markdown streams a zip of all your own
memos — one dated .md file each, comments in their own folder, plus an
attachments/ folder with inline links rewritten to relative paths. Either way it's
a copy for humans and other tools, not a restore format — the backup zip above is
the one the Restore button accepts.
A safe snapshot (Docker)
Don't copy nemomemo.db while the app is running — a plain cp can catch it
mid-write. SQLite's .backup takes a consistent snapshot even while live:
mkdir -p ~/nemomemo-backups
docker exec <container> sh -c \
'apk add --no-cache sqlite >/dev/null 2>&1 || true; sqlite3 /app/data/nemomemo.db ".backup /app/data/backup.db"'
docker cp <container>:/app/data/backup.db ~/nemomemo-backups/nemomemo-$(date +%F).db
docker cp <container>:/app/data/uploads ~/nemomemo-backups/uploads-$(date +%F)
docker exec <container> rm /app/data/backup.db(Running from source instead? Just point sqlite3 and cp at your data directory
directly.)
Make it nightly
Put those commands in a script and add a cron line on the host:
17 3 * * * /home/you/backup-nemomemo.sh >> /home/you/nemomemo-backups/backup.log 2>&1Off the machine (recommended)
A backup on the same disk dies with the disk. restic — the same tool NemoMemo Cloud's own backups use — encrypts your backup folder and pushes it to almost anywhere: another machine over SFTP, or any S3-compatible bucket.
Getting a free bucket (either of these comfortably fits a reef in its free tier):
- Cloudflare R2 (pricing: 10 GB +
free egress): in the Cloudflare dashboard go to R2 → Create bucket (pick any
name, e.g.
my-reef-backups). Then R2 → Manage R2 API Tokens → Account API Tokens → Create: permissions Object Read & Write, apply to your bucket only, TTL Forever. The confirmation screen shows an Access Key ID, Secret Access Key, and your account's S3 endpoint URL — copy all three (the secret is shown only once). - Backblaze B2 (pricing: first 10 GB always free): create a bucket, then Application Keys → Add a New Application Key scoped to that bucket; the key ID/key are your S3 credentials and the endpoint is shown on the bucket page.
Then wire restic to it:
export AWS_ACCESS_KEY_ID=<access key id>
export AWS_SECRET_ACCESS_KEY=<secret access key>
export RESTIC_REPOSITORY=s3:https://<endpoint>/<bucket>
export RESTIC_PASSWORD=<pick a strong passphrase and KEEP IT SAFE>
restic init # once
restic backup ~/nemomemo-backups # each night (add to your cron script)
restic forget --keep-daily 14 --keep-weekly 8 --prune # retentionWithout the restic passphrase a backup can never be restored — store it (and the keys) somewhere that isn't this server, like a password manager.
Restoring
The easy way: as the reefkeeper, Settings → Backups → Restore from backup — upload a backup zip, and the reef verifies it, sets the current data aside as a safety copy, swaps the backup in, and restarts itself. Done.
By hand (if the app won't start, or you prefer the shell): stop the container, put the files back, start it again:
docker stop <container>
docker cp ~/nemomemo-backups/nemomemo-2026-08-22.db <container>:/app/data/nemomemo.db
docker cp ~/nemomemo-backups/uploads-2026-08-22/. <container>:/app/data/uploads
docker start <container>Backups only matter if restore is realistic — do one practice restore before you depend on it. Just keep swimming (with copies). 🐟