- infrastructure/auto-deploy.md: Plato pipeline added (Shape B, port 9799, hook id 8); ALLOWED_HOST_LIST gains 192.168.8.190; per-pipeline note for the LXC 126 specifics (origin URL, no ProtectHome); Changelog updated - containers/126-plato.md: Auto-deploy section + Changelog entry
5.4 KiB
126 — plato
Docker host for Plato — a cross-linked notes workspace (SvelteKit SPA embedded into a Go HTTP server, SQLite-backed). LAN+mesh only, no public ingress.
At a glance
- Hostname:
plato - IP:
192.168.8.190 - Privilege: privileged
- Resources: 2 cores / 2 GiB RAM / 8 GiB rootfs / 1 GiB swap
- Mounts: host
/mnt/library/documents/plato↔ container/opt/plato/data - Public hostname:
plato.hubris.network→ caddy (121) →192.168.8.190:8080
Stack
Single-container deploy. The repo's Dockerfile is a three-stage build (Node → Go → distroless/static-debian12:nonroot, ~23 MiB final image). The container exposes :8080 and writes its SQLite db to /data.
- Checkout:
/opt/plato/app(clone ofhttp://192.168.8.121:3000/dtoro/Plato.git, using the cached gitea PAT in/root/.git-credentials— same pattern as caddy (121)). - Data: host
/mnt/library/documents/plato(owned65532:65532to match the distroless nonroot UID) bind-mounted into the LXC at/opt/plato/data, then bound into the container at/datavia adocker-compose.override.yml:services: plato: volumes: !override - /opt/plato/data:/data restart: unless-stopped .envat/opt/plato/app/.env(optional, untracked) — LLM provider keys (OPENROUTER_API_KEY,ANTHROPIC_API_KEY, etc.) andPLANTUML_BASE_URLoverride. Absent by default; LLM features stay greyed out, PlantUML defaults to the public service.- Run / update: push to
dtoro/Plato(auto-deploys, see below) orcd /opt/plato/app && git pull && docker compose up -d --buildfor a manual rebuild.
Auto-deploy
Push to dtoro/Plato main triggers a rebuild — same Shape B pattern as Artifacto / mule-image. Webhook receiver at /opt/plato-deploy/, systemd unit plato-deploy-webhook.service, port 9799, gitea hook id 8.
- Receiver:
http://192.168.8.190:9799/deploy, signed payload (HMAC-SHA256, secret in/etc/plato-deploy/secret). - Logs:
journalctl -u plato-deploy-webhook -f. - Health:
curl http://127.0.0.1:9799/health. - Manual deploy:
/opt/plato-deploy/deploy.sh. - Gitea's
app.iniALLOWED_HOST_LISTwas extended with192.168.8.190to allow this delivery.
Fresh-DB bootstrap workaround
The schema constant in backend/internal/views/store.go (as of commit e0542c0) creates the views table without project_id, then immediately runs CREATE UNIQUE INDEX … ON views(project_id, lower(title)). On a fresh DB this fails (no such column) and Plato crash-loops with open views store: SQL logic error: no such column: project_id. ensureProjectIDColumn() adds the column on subsequent migrations, but the schema apply happens first.
Until the upstream fix lands, pre-seed the DB before first start:
docker compose stop
rm -f /opt/plato/data/plato.db
python3 - <<'PY'
import sqlite3
c = sqlite3.connect('/opt/plato/data/plato.db')
c.executescript("""
CREATE TABLE views (
id TEXT PRIMARY KEY,
type TEXT NOT NULL DEFAULT 'document',
title TEXT NOT NULL,
aliases TEXT NOT NULL DEFAULT '[]',
content TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
project_id TEXT NOT NULL DEFAULT ''
);
CREATE UNIQUE INDEX views_project_title_lower ON views(project_id, lower(title));
CREATE INDEX views_project_id ON views(project_id);
""")
c.commit()
PY
chown 65532:65532 /opt/plato/data/plato.db
docker compose up -d
Once the column exists, every subsequent boot's IF NOT EXISTS clauses no-op. Once Plato is fixed upstream (remove the CREATE UNIQUE INDEX line from the boot schema constant — ensureTitleIndexPerProject() already re-creates it after the migration), this preseed becomes unnecessary.
Why privileged
Matches the docker-host convention used by 120 mule-images and 122 arriman. Distroless nonroot's UID 65532 on the host bind mount maps directly through; unprivileged would shift the UID by the idmap offset and the container couldn't write /data without extra plumbing.
Caddy
plato.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.190:8080
}
No Authentik forward-auth — Plato has no auth model yet; access control is "be on the LAN or the mesh".
DNS
dnsmasq entry on 124-authentik:
address=/plato.hubris.network/192.168.8.175
Related
Changelog
2026-05-13 — auto-deploy wired
Shape B pipeline added (/opt/plato-deploy/, port 9799, gitea hook id 8). ALLOWED_HOST_LIST in gitea app.ini extended with 192.168.8.190. See auto-deploy.
2026-05-13 — container created, Plato deployed
LXC 126 stood up on Debian 12 standard, privileged, docker-ce installed. Plato cloned from dtoro/Plato, built and started. Caddy site and dnsmasq split-horizon entry added. Recycled the IP/ID slot freed earlier the same day by the decommissioned Seafile experiment (LXC 125). Hit the fresh-DB bootstrap bug on first boot; worked around by pre-seeding the SQLite schema.