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:
@@ -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"
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user