Most note apps want to be a second brain. Memos wants to be a feed. You drop short markdown thoughts into a chronological timeline, tag them, and search later. It feels closer to a private Twitter or Mastodon than to Logseq or Obsidian, which is exactly why it works for the kind of notes you would otherwise lose in Slack DMs to yourself.
Memos is open source, written in Go, ships as a single binary, and the Docker image runs comfortably on a 256 MB VPS. SQLite is the default store, so there's no separate database container to manage. This guide gets you from a fresh server to a working public URL with HTTPS, your own admin account, registrations locked down, and the iOS or Android app pointed at it.
notes.example.com at the serverneosmemo/memos:stable behind Caddy in one docker-compose.ymlTotal time: about 15 minutes.
80 and 443 open to the internet for Let's EncryptMemos is genuinely tiny. On an idle server it sits around 40 MB of RAM with a few hundred notes. If you're already running other Docker workloads, this slots in alongside them.
Quick orientation, because the question always comes up:
Think of Memos as a personal microblog you can also publish from. Many people run all three side by side: Obsidian for long-form, Memos for stream-of-consciousness, and a dedicated bookmark app for links.
In your DNS provider, add an A record:
notes.example.com → YOUR_VPS_IPV4
Add an AAAA record if you use IPv6. Verify it resolves:
dig +short notes.example.com
DNS has to point at your VPS before Caddy can fetch a certificate.
On a fresh Ubuntu box:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Confirm:
docker --version
docker compose version
If you use UFW:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Caddy needs port 80 for the ACME HTTP challenge and 443 for HTTPS.
sudo mkdir -p /opt/memos
cd /opt/memos
sudo mkdir -p memos-data caddy-data caddy-config
Everything persistent lives under /opt/memos. If you back up this one directory, you can rebuild the stack on any host.
Memos is a single container. No database service to run, no Redis, no migrations to babysit. Create /opt/memos/docker-compose.yml:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
volumes:
- ./memos-data:/var/opt/memos
networks:
- memos-net
caddy:
image: caddy:2
container_name: memos-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- memos-net
networks:
memos-net:
A few notes:
5230 internally. We do not publish it on the host because Caddy reaches it on the Docker network../memos-data:/var/opt/memos is the entire data set: SQLite database, uploaded images, and any attached files.neosmemo/memos:stable is the recommended image tag for production. The latest tag tracks the dev branch and breaks more often.Create /opt/memos/Caddyfile:
notes.example.com {
encode zstd gzip
reverse_proxy memos:5230
}
Caddy will request a Let's Encrypt certificate for notes.example.com automatically on first boot. No extra config needed.
cd /opt/memos
sudo docker compose up -d
sudo docker compose logs -f
Watch the logs until Caddy reports the certificate was issued. Then open https://notes.example.com in a browser. You should see the Memos sign-up screen.
The first account you create on a fresh Memos install is automatically promoted to Host, which is the equivalent of root admin. Sign up with the username and password you want to keep.
Once you're in, you'll see the timeline view, an empty editor, and the side rail with tags and explore tabs. The keyboard shortcut to post is Ctrl+Enter (or Cmd+Enter on macOS).
In the Memos web UI:
To add other users later, the Host account can create them manually from the same Workspace settings panel under Members.
While you're in Workspace settings, also confirm:
Private, so new memos don't accidentally land on a public timeline.Memos has community iOS and Android clients that talk to your server over the same REST API the web UI uses.
https://notes.example.com as the host, then your username and password.For both apps, the URL must be the full https://... form. If the client refuses to log in, double-check that your domain is reachable in a browser first. The phone never sees a more lenient version of TLS than your laptop does.
Memos uses inline hashtags, like a microblog. Three things worth knowing on day one:
#tag anywhere in a memo creates that tag automatically. The sidebar lists tags by frequency./, for example #books/2026 and #books/wishlist. The sidebar groups them in a tree.tag:books), and date filters (from:2026-04-01 to:2026-04-30).Visibility per memo is set with the dropdown next to the post button:
A handy pattern is to tag work-in-progress items #inbox, then sweep the inbox view once a day and either delete, retag, or expand them.
The SQLite file in /opt/memos/memos-data is the single source of truth. Lose it and you've lost everything.
Create /usr/local/bin/memos-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/memos"
DATE="$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR"
docker exec memos sqlite3 /var/opt/memos/memos_prod.db ".backup '/var/opt/memos/memos.backup.db'"
tar -czf "$BACKUP_DIR/memos-$DATE.tar.gz" -C /opt/memos memos-data
find "$BACKUP_DIR" -name "memos-*.tar.gz" -mtime +14 -delete
The sqlite3 .backup command produces a consistent snapshot even while Memos is running, which is safer than tarring the live database file.
Make it executable and schedule a daily run:
sudo chmod +x /usr/local/bin/memos-backup.sh
echo "20 3 * * * root /usr/local/bin/memos-backup.sh" | \
sudo tee /etc/cron.d/memos-backup
For off-site safety, sync /var/backups/memos to S3, Backblaze B2, or another VPS using rclone on the same schedule.
To restore: stop the stack, replace memos-data/ with the contents of a backup, start the stack again.
Memos ships frequent point releases. The safe upgrade path:
cd /opt/memos
sudo docker compose pull
sudo docker compose up -d
Always take a fresh backup first. Across major releases the schema occasionally changes, and rolling back is much easier with a known-good snapshot than with a half-migrated database.
A personal microblog that no one else needs to read is a great candidate for VPN-only access. Pair this with our Tailscale guide so notes.example.com only resolves and answers inside your tailnet. The mobile clients work fine over WireGuard or Tailscale.
Caddy returns 502 Bad Gateway. The Memos container hasn't fully started, or it crashed on boot. Check sudo docker compose logs memos. Usually a permissions issue on the volume - run sudo chown -R 1000:1000 /opt/memos/memos-data and restart.
Login loop after upgrading Caddy. The browser cookie was set with Secure over a connection Caddy now serves slightly differently. Hard-refresh, then clear cookies for notes.example.com. If it persists, check that the Caddyfile still has only the apex notes.example.com { block and isn't redirecting to a different host.
Image uploads fail with permission errors. Memos writes uploads under /var/opt/memos/assets. Inside the container that path needs to be writable by UID 1000. Same fix as above: sudo chown -R 1000:1000 /opt/memos/memos-data.
API key returns 401 Unauthorized. Memos API tokens are bound to a specific user. If you regenerated one in the UI, the old value is dead immediately. Send the token as a Bearer header: Authorization: Bearer YOUR_TOKEN. Some scripts mistakenly send it as Token YOUR_TOKEN, which the server rejects.
Caddy fails to issue a certificate. DNS hasn't propagated, or 80/443 are blocked by your provider. Verify with dig +short notes.example.com and curl -I http://notes.example.com.
/api/v1/.... Generate a token in Settings → My Account → Access Tokens, then post a memo from anywhere with curl -H "Authorization: Bearer $TOKEN" -d '{"content":"hello"}' https://notes.example.com/api/v1/memos. Great for shortcuts on iOS, browser bookmarklets, or shell aliases.https://notes.example.com/u/USERNAME/rss.xml. Plug it into any reader to get a chronological feed of your public posts, or use it to syndicate to a static site.Self-hosted Memos is the kind of tool you forget about for weeks because it just runs. A few hundred notes in, the timeline plus tag search becomes the easiest place to write down anything that doesn't deserve a full markdown page.
Looking for a small VPS that fits a stack like this without breaking a sweat? Our Linux plans include fast NVMe storage, IPv6, and plenty of headroom for Docker workloads. See the options.