Context
The server runs 11 Docker containers on a 1.9GB DigitalOcean droplet (currently 72.5% memory). The Overseer dashboard shows host-level CPU/memory/disk via psutil, but there's no way to see which container is the memory hog. This adds per-container memory/CPU breakdown.
Approach
- Query Docker Engine API via Unix socket using
httpx (already a dependency — no new packages)
- Add as a
sub_component of backend alongside existing cpu/memory/disk metrics
- Display in a new "Containers" tab in the Server/Backend modal, sorted by memory usage descending
- Graceful degradation when Docker socket isn't available (dev machines)
Files to Modify
1. docker-compose.yml — Mount Docker socket for webserver
Add /var/run/docker.sock:/var/run/docker.sock:ro to webserver volumes
2. docker-compose.dev.yml — Same for dev
Add socket mount alongside existing code mount
3. app/core/config.py — Add settings
DOCKER_SOCKET_PATH: str = "/var/run/docker.sock"
DOCKER_STATS_CACHE_SECONDS: int = 10
DOCKER_STATS_ENABLED: bool = True
4. app/services/system/health_docker.py — NEW: Docker stats health check
check_docker_stats() -> ComponentStatus — main entry point
- Uses
httpx.AsyncHTTPTransport(uds=socket_path) to hit Docker API
GET /containers/json to list containers, then parallel GET /containers/{id}/stats?stream=false
- Parses memory (usage/limit/percent), CPU percent, PIDs, network I/O
- Returns
ComponentStatus with per-container sub_components
- Friendly name mapping:
sector-7g-worker-homer-1 → Homer
- 10-second cache (stats calls take ~1-2s)
- Returns INFO status (not UNHEALTHY) when socket missing
5. app/services/system/health.py — Wire into cached metrics pipeline
- Add
"containers" to SYSTEM_METRICS set (line 37)
- Add
check_docker_stats to system_metric_checks dict in _get_cached_system_metrics() (line 390)
6. app/components/frontend/dashboard/modals/containers_tab.py — NEW: Containers tab UI
ContainersTab(ft.Container) — Flet component
- Summary metric cards: container count, total memory, peak CPU, top consumer
- Expandable table sorted by memory descending: status dot, name, memory MB, limit, mem%, CPU%, PIDs
- Expanded rows show: image, container ID, network RX/TX
- Uses existing controls:
ExpandableDataTable, MetricCard, PrimaryText, SecondaryText
- Follows
OverviewTab/RoutesTab/LifecycleTab pattern from backend_modal.py
7. app/components/frontend/dashboard/modals/backend_modal.py — Add tab
- Import
ContainersTab
- Add
ft.Tab(text="Containers", content=ContainersTab(backend_component)) at line 1143
8. app/components/frontend/dashboard/cards/server_card.py — Optional card hint
- Add "Top Container" metric showing the highest memory consumer name + MB
Verification
make serve locally — Containers tab shows in Server modal (may show "unavailable" without Docker socket)
- Deploy to server — tab shows all 11 containers sorted by memory, expandable for details
uv run pytest — existing tests still pass
- Test graceful degradation: without socket mount, tab shows info message instead of error
Context
The server runs 11 Docker containers on a 1.9GB DigitalOcean droplet (currently 72.5% memory). The Overseer dashboard shows host-level CPU/memory/disk via psutil, but there's no way to see which container is the memory hog. This adds per-container memory/CPU breakdown.
Approach
httpx(already a dependency — no new packages)sub_componentof backend alongside existing cpu/memory/disk metricsFiles to Modify
1.
docker-compose.yml— Mount Docker socket for webserverAdd
/var/run/docker.sock:/var/run/docker.sock:roto webserver volumes2.
docker-compose.dev.yml— Same for devAdd socket mount alongside existing code mount
3.
app/core/config.py— Add settingsDOCKER_SOCKET_PATH: str = "/var/run/docker.sock"DOCKER_STATS_CACHE_SECONDS: int = 10DOCKER_STATS_ENABLED: bool = True4.
app/services/system/health_docker.py— NEW: Docker stats health checkcheck_docker_stats() -> ComponentStatus— main entry pointhttpx.AsyncHTTPTransport(uds=socket_path)to hit Docker APIGET /containers/jsonto list containers, then parallelGET /containers/{id}/stats?stream=falseComponentStatuswith per-containersub_componentssector-7g-worker-homer-1→Homer5.
app/services/system/health.py— Wire into cached metrics pipeline"containers"toSYSTEM_METRICSset (line 37)check_docker_statstosystem_metric_checksdict in_get_cached_system_metrics()(line 390)6.
app/components/frontend/dashboard/modals/containers_tab.py— NEW: Containers tab UIContainersTab(ft.Container)— Flet componentExpandableDataTable,MetricCard,PrimaryText,SecondaryTextOverviewTab/RoutesTab/LifecycleTabpattern frombackend_modal.py7.
app/components/frontend/dashboard/modals/backend_modal.py— Add tabContainersTabft.Tab(text="Containers", content=ContainersTab(backend_component))at line 11438.
app/components/frontend/dashboard/cards/server_card.py— Optional card hintVerification
make servelocally — Containers tab shows in Server modal (may show "unavailable" without Docker socket)uv run pytest— existing tests still pass