Skip to content

Services

Everything on the pages below is a Podman container defined as a Quadlet unit on one of the two uCore servers, deployed by the same Ansible run and covered by the same alerting. What differs is purpose, and that's how these pages are split: monitoring carries the observability stack, and kontti carries everything else — media, photos, personal applications, backups and local AI.

The grouping is deliberately by what a service is for rather than by where it runs, because the operational model underneath is identical in every case. Adding a service means adding a role and a unit file; there is no second deployment path to learn.

Page Covers
Monitoring Prometheus, Grafana, Loki, alerting
Media Server Plex, Immich, Music Assistant
Apps Homepage, Audiobookshelf, Mealie, Miniflux, oPodSync, Syncthing, Gitwatch
Backup Borgmatic, Borg, Synology DS920+
AI Ollama, Open WebUI, SearXNG, Qdrant, plex-sync

The local AI stack runs on kontti alongside these services; because it's a substantial topic of its own — local LLMs, private search, and semantic media search — it has its own AI section.

Sizing the memory limits

Roughly thirty application containers on kontti shared a 29 GiB host with no resource limits at all — limits existed only on the observability stack and one app. On a single-node setup that isn't a capacity question, it's a blast-radius one: one runaway process taking the host into OOM would pick its victims by kernel heuristic, and the plausible casualties include Borgmatic and the Pushgateway that carries the backup deadman switch. The services whose failure is hardest to notice are exactly the ones that would go with it.

Every MemoryMax was then sized against fourteen days of Prometheus data rather than guessed. Two things about that are worth recording, because both cut against how the numbers look at first glance:

  • container_memory_usage_bytes is the wrong metric for this. It counts reclaimable page cache, so a service that has merely touched a lot of files reads as though it needs tens of gigabytes — one container's "usage" suggested a 21 GB ceiling, which would have limited nothing. The pair that actually answers the question is container_memory_rss (what an OOM kill would be about) alongside working set (how much cache the workload genuinely leans on).
  • A measured peak is a floor, not a ceiling. For anything whose footprint scales with its input, the observation window only proves what happened to run in it. Ollama's peak was 349 MiB purely because no large model was loaded that fortnight; the real driver is an 8.4 GiB model file. The same applies to Immich's pgvector indexing and a full Syncthing rehash — each got a ceiling deliberately above anything measured.

Two omissions were deliberate. CPUQuota was left off everywhere: CPU contention is the scheduler sharing time and it degrades gracefully, while running out of memory kills processes — different failure modes deserve different treatment. And Borgmatic was left unlimited, because it's the service whose wrongly-killed run costs the most to recover and whose measured usage never came close to justifying a cap.

Verification was --check --diff against the host before applying: the run had to change exactly the edited unit files and their restarts, and nothing else.