memos: deploy to ana-docker

Stock neosmemo/memos:stable, port 5230, SQLite at
/opt/docker/conf/memos/data/. Joins traefik-net and ships homepage
labels (group=Notes) so it auto-appears on the dashboard via docker
discovery — no edit to configs/homepage/services.yaml needed.

First-run bootstrap is via the UI: visit http://10.250.50.70:5230
and create the Host account through the sign-up form.

Playbook idiom note: docker compose pull lines need the literal
block scalar (|) when the grep pattern contains colons — bare-string
shell value made YAML parse the colon as a mapping separator and
elway choked on first try.
This commit is contained in:
2026-04-29 13:27:12 -07:00
parent d27bb9209b
commit 6df5549161
4 changed files with 193 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
# Deploy memos (https://github.com/usememos/memos) — single-container
# self-hosted note server — to ana-docker.
#
# First-boot creates a fresh SQLite DB at /var/opt/memos/memos_prod.db
# inside the container (bind-mounted out to ${MEMOS_DATA_DIR} on the
# host). Visit http://10.250.50.70:5230 immediately after deploy to
# create the Host account.
#
# Usage:
# scripts/elway ana-docker --playbook playbooks/deploy-memos.yaml
#
# Idempotent — safe to rerun.
vars:
compose_dir: /opt/docker/compose/memos
data_dir: /opt/docker/conf/memos/data
host_port: "5230"
steps:
# ── host-side dirs (sudo only on first run) ─────────────────────────
- name: Ensure compose dir exists
shell: mkdir -p {{ compose_dir }}
sudo: true
creates: "{{ compose_dir }}"
- name: Chown compose dir to lkraven
shell: chown -R lkraven:lkraven {{ compose_dir }}
sudo: true
when: "[ \"$(stat -c %U {{ compose_dir }})\" != \"lkraven\" ]"
- name: Ensure data dir exists
shell: mkdir -p {{ data_dir }}
sudo: true
creates: "{{ data_dir }}"
- name: Chown data dir to lkraven
shell: chown -R lkraven:lkraven {{ data_dir }}
sudo: true
when: "[ \"$(stat -c %U {{ data_dir }})\" != \"lkraven\" ]"
# ── deploy compose + env ────────────────────────────────────────────
- name: Upload compose.yaml
upload:
src: stacks/memos/compose.yaml
dest: "{{ compose_dir }}/compose.yaml"
mode: "0644"
- name: Seed .env from template (only if absent)
upload:
src: stacks/memos/.env.example
dest: "{{ compose_dir }}/.env"
mode: "0644"
when: "[ ! -f {{ compose_dir }}/.env ]"
# ── bring up + wait ─────────────────────────────────────────────────
- name: docker compose pull (gets latest stable tag)
shell: |
set -o pipefail
cd {{ compose_dir }} && docker compose pull 2>&1 \
| grep -vE '^(Status|Pulling) |Downloaded'
- name: docker compose up -d
shell: cd {{ compose_dir }} && docker compose up -d
- name: Wait for / to respond
shell: |
for i in $(seq 1 30); do
curl -sf -o /dev/null --max-time 3 http://localhost:{{ host_port }}/ && exit 0
sleep 2
done
exit 1
changed_when: "false"
verify:
- name: / returns 200
shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/
changed_when: "false"
- name: Container is running
shell: docker inspect memos --format '{{.State.Status}}' | grep -q running
changed_when: "false"
- name: Container is on traefik-net (so homepage docker discovery sees it)
shell: docker inspect memos --format '{{json .NetworkSettings.Networks}}' | grep -q traefik-net
changed_when: "false"
+22
View File
@@ -0,0 +1,22 @@
# memos stack tunables. Copy to `.env` on ana-docker before deploying.
# Image tag — `stable` tracks upstream's stable channel. Pin a specific
# version (e.g. `0.24.0`) before any production cutover so a stable
# bump doesn't surprise you on the next pull.
MEMOS_TAG=stable
# ── network ──────────────────────────────────────────────────────────
# Host port (container listens on 5230 internally).
# Reservations on ana-docker (sample): 5001 Dockge, 7878 task-board,
# 8080 Miniflux, 8181 news-digest. 5230 is memos' canonical port and
# was free at deploy time.
MEMOS_PORT=5230
MEMOS_BIND=0.0.0.0
# ── runtime ──────────────────────────────────────────────────────────
MEMOS_TZ=America/Los_Angeles
# ── persistent storage on the host ───────────────────────────────────
# SQLite DB + uploaded resource files. Backed up via the host's restic
# profile (memos_prod.db is small; resources/ can grow with attachments).
MEMOS_DATA_DIR=/opt/docker/conf/memos/data
+38
View File
@@ -0,0 +1,38 @@
# memos
[memos](https://github.com/usememos/memos) — lightweight self-hosted
note / memo server. Single Go binary, SQLite by default, MIT license.
| | |
|---|---|
| host | `ana-docker` |
| port | `5230` |
| image | `neosmemo/memos:stable` |
| data | `/opt/docker/conf/memos/data/` (SQLite + uploaded resources) |
| homepage group | `Notes` |
## Deploy
```bash
scripts/elway ana-docker --playbook playbooks/deploy-memos.yaml
```
First run: visit <http://10.250.50.70:5230>, create the Host account
through the sign-up form. Subsequent users register through the same
form (or you disable open sign-up via Settings → Workspace).
## Tunables
See `.env.example` — copy to `.env` on the host (lives at
`/opt/docker/compose/memos/.env`, gitignored). Knobs are intentionally
minimal: image tag, host port, TZ, data dir. Memos itself reads its
config from the SQLite DB (workspace settings via the UI).
## Backups
Data dir is `/opt/docker/conf/memos/data/`. The host's restic profile
covers `/opt/docker/conf/` so the SQLite DB + uploaded resources go
along for free. SQLite snapshot is consistent under restic's COW reads
since memos uses WAL mode + fsync; for a guaranteed-quiescent backup
take a `docker exec memos sqlite3 /var/opt/memos/memos_prod.db ".backup /var/opt/memos/snapshot.db"`
in the restic pre-backup hook.
+45
View File
@@ -0,0 +1,45 @@
# memos — lightweight self-hosted note / memo server.
# Upstream: https://github.com/usememos/memos (MIT, Go binary).
#
# Single container, SQLite by default (file lives in the bind-mounted
# data dir below). Auth bootstraps via the UI on first run — first
# user created becomes Host. No env vars required for a basic install.
#
# All tunables live in .env — edit that, not this file.
services:
memos:
image: neosmemo/memos:${MEMOS_TAG:-stable}
container_name: memos
restart: unless-stopped
ports:
- "${MEMOS_BIND:-0.0.0.0}:${MEMOS_PORT}:5230"
volumes:
# SQLite DB + uploaded resource files. Bind mount on the host
# rather than a named volume so backups + manual inspection are
# straightforward.
- ${MEMOS_DATA_DIR}:/var/opt/memos
environment:
- TZ=${MEMOS_TZ:-America/Los_Angeles}
healthcheck:
# Image is alpine-based and ships busybox wget. --spider keeps it
# cheap (HEAD request) and 127.0.0.1 dodges the IPv6-first
# localhost trap that bit news-digest + chatterbox earlier.
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5230/ || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
networks:
- tnet
labels:
- homepage.group=Notes
- homepage.name=Memos
- homepage.icon=mdi-note-text-outline
- homepage.description=Self-hosted note + memo server (ana-docker)
- homepage.href=http://10.250.50.70:${MEMOS_PORT}
networks:
tnet:
name: traefik-net
external: true