Anso c47b8eb8e9 perf(frontend): overlap stack list and status hydration requests (#1820)
* perf(frontend): overlap stack list and status hydration requests

Fire /stacks and /stacks/statuses concurrently while committing the list
first, so list_visible stays progressive and statuses overlap the list
fetch instead of serializing behind it.

The visible list now renders as soon as it commits (isLoading clears at
list commit, not at status completion), and an atomic HydrationEvidence
record derived at render time fails closed on node switches, list
changes, and partial status payloads: lifecycle actions across the
sidebar menu, editor toolbar, mobile editor, bulk bar, and deferred
dialogs recheck a ref-backed readiness predicate immediately before any
mutation.

Status payloads are validated (bulk object format with typed fields,
legacy string maps with recognized values); the legacy per-stack
fallback targets the captured node and tracks coverage, so a total
fallback failure escalates to a hard error instead of masquerading as
ok. Pending, error, stale, and incomplete hydration states render
distinctly in rows, filter chips, and the mobile masthead.

Measured on the SEN-531 workload contract (local node, 6 stacks, Dev
Mode ON): hydration gap 52.5ms median to 0ms, full hydration ~146ms to
~84-100ms (~42% improvement). 2590 frontend tests, visual regression
12/12, backend tsc clean.

* fix(frontend): recheck hydration readiness in deferred lifecycle executors

Deferred continuations (pre-deploy advisory proceed, external-network
continue, update-readiness proceed, service-update proceed) dispatched
against the node captured when the dialog opened, without rechecking
readiness at the actual mutation boundary. A dialog opened on node A
could therefore mutate node A after the operator switched to node B.

Each executor now rechecks the ref-backed readiness predicate before
starting any operation, releasing the pending deploy guard and surfacing
a toast when blocked. Plain Save stays independent of status readiness
(mobile editor and diff-preview plain-save mode are no longer gated);
only Save & Deploy / Save & Reapply requires authoritative runtime
evidence.

Adds two-node delayed-response tests proving late prior-node list and
status results cannot replace the current node's files, statuses,
evidence, or loading ownership, plus deferred readiness-loss regression
tests for the update and service-update dialogs.

* fix(frontend): bind deferred executor readiness to the captured node

The previous readiness rechecks validated the CURRENT node, but deferred
continuations (pre-deploy advisory proceed, external-network continue,
update-readiness proceed, service-update proceed) still dispatched with
the node captured when the dialog opened. A dialog opened on node A could
mutate node A after the operator switched to node B and B finished
hydrating.

The executor-boundary predicate now also requires the captured operation
node to equal the current active node (effect-updated ref), so a
fully-hydrated node switch still blocks the stale continuation.

Adds ready-after-switch regression tests for the deploy continuation,
stack update, and service update (switch to node B, hydrate, invoke the
stored proceed, assert no operation session starts), plus focused plain
Save vs Save & Deploy tests for the mobile editor and the diff preview.
Restores the unrelated frontend lockfile metadata drift.

* fix(frontend): guard external-network creation and reactive retry by node ownership

The missing-external-networks dialog's createAndContinue posted network
creation requests to the captured node before any ownership check, and
the reactive 409 retry callback started a deploy directly without the
executor guard. A dialog opened on node A could create Docker networks
or deploy on node A after the operator switched to node B.

Both paths now recheck hydrationReadyForNode before any mutation: the
proactive dialog closes with the pending guard released and a toast when
blocked, and the reactive retry refuses to start an operation session.

Adds ready-after-switch regression tests for both flows asserting no
/system/networks POST and no operation session after a switch to a fully
hydrated node B.

* fix(frontend): bind policy bypass, delete, and take-down confirmations to their opening node

Three deferred confirmation paths remained unsafe across node switches:
the policy-bypass retry validated current readiness but dispatched to the
policy block's captured node; Delete and Take Down dialogs stored only a
stack name and derived the operation node live at confirmation time, so
a dialog opened on node A would mutate node B's same-named stack after a
switch.

Delete and Take Down targets now carry the opening node id in the overlay
state (deleteTarget / takeDownTarget), and their confirm handlers verify
hydrationReadyForNode against that id before mutating. The policy-bypass
retry uses the same predicate against the block's captured node.

The reactive 409 regression test now reaches the reactive path: the
proactive preflight reports no missing networks, the deploy POST returns
the 409, and the reactive refetch opens the dialog, after which an
ownership change during network creation blocks the retried deploy.

Also restores the unrelated frontend lockfile drift again.

* fix(frontend): validate legacy fallback payloads and target stale refreshes at the current node

The legacy per-stack fallback counted any successfully decoded JSON body
as authoritative coverage, including a 200 non-array body or an array
with malformed entries, which could authorize lifecycle actions on
non-authoritative evidence. The fallback now requires a container array
whose entries satisfy the minimal container shape; malformed responses
fail closed and contribute zero coverage.

A refreshStacks callback captured on node A and invoked after the
operator switched to node B mixed a live list request (localStorage
target) with node A statuses and evidence. refreshStacks now reads the
current node from its render-synchronous ref, and the list request is
targeted explicitly so both requests always share one authoritative
node.

Adds malformed-fallback regressions (non-array, malformed-array,
partial-invalid) and a stale-callback-after-switch test asserting both
requests target the current node and its state stays authoritative.
2026-08-12 11:54:29 -04:00

Sencho

Self-hosted Docker Compose management for one machine or a fleet.

Docs · Website · Discussions · Sponsor · Buy Me a Coffee

Latest release Docker Pulls CI CodeQL License Last commit Open issues Website Docs


Sencho dashboard

Note

Sencho is used in production for day-to-day Docker Compose and fleet management. As a pre-1.0 project it still evolves quickly, so review the known limitations and validate against your own setup before deploying it on critical infrastructure.


What Sencho is

Sencho is a Docker Compose control plane for DevOps engineers, platform teams, system administrators and homelab users who run services on Compose and need a real operational surface: a graphical interface that does not give up file-on-disk workflows, and the ability to manage more than one machine without SSH gymnastics or a VPN.

It runs as a single container on your hardware and provides a UI for common Compose operations: deploying, editing files, watching logs, restarting containers, browsing volumes, and recovering from failures. Your compose files stay on the host filesystem and remain the source of truth.

Multi-node was part of the architecture from the start, not bolted on later: every Sencho instance is the same autonomous node, whether it runs alone or as one of many in a fleet. To manage another machine, you install a second Sencho on it and connect them with a long-lived API token; the primary dashboard then acts as an authenticated HTTP and WebSocket proxy across your fleet. Use TLS, a VPN, or a private network for any untrusted link. Each node still uses its local Docker socket (see Quick start), but Sencho does not require SSH and does not expose a remote Docker socket on the network. For nodes behind NAT or strict firewalls, the Pilot Agent establishes a single outbound WebSocket tunnel to the primary, so the remote host opens no inbound port at all.

Sencho is free, open-source software under AGPLv3. Everything below is included in the Community tier with unlimited nodes and users.


Capabilities

Stacks

  • Full Compose lifecycle: create, deploy, restart, stop, take down, pull
  • Atomic deployments with automatic rollback on failure
  • Monaco editor with diff preview before save and one-click rollback to any prior deploy
  • Health-gated updates that hold a rollout until health checks pass, with stalled-update detection and in-app recovery
  • Git-sourced stacks pulled and synced from any repository, with ordered multi-file Compose
  • File explorer for compose, env, and supporting files, with move and rename across directories
  • Drift detection that compares running containers against the effective Compose model and flags exactly what changed
  • Environment and secrets guardrails that inventory every variable a stack uses and flag missing or duplicate values, without ever exposing a value
  • Storage portability checks that show whether a stack's mounts can move cleanly to another node before you move it
  • Compose Doctor preflight checks that catch compose problems before deploy
  • Stack labels for grouping and bulk operations
  • App Store with LinuxServer.io templates by default, or any custom Portainer-compatible registry

Observability

  • Aggregated log search and stream across every container in the fleet
  • Live container stats, health checks, and image-update notifications on a configurable cadence, with links from each image to its registry and source
  • Threshold alerts for CPU, memory, and network
  • Read-only audit log of every action, with a 14-day recent-activity window
  • Network topology view of containers, networks, and nodes
  • Documentation-drift flags when a stack dossier diverges from the running stack

Fleet

  • Multi-node management via authenticated HTTP and WebSocket proxy
  • Fleet view with grid and topology layouts
  • Fleet snapshots of compose and env across the fleet
  • Fleet Federation: cordon nodes and pin Blueprints to specific hosts
  • Fleet Actions: bulk label operations, fleet-wide stop-by-label, and fleet-wide prune
  • Fleet Dossier: export the whole fleet as a single browsable Markdown archive
  • Docker Label Audit across every node, for labels that drive external automation
  • Remote updates: pull the latest image and recreate any node in the fleet from the Fleet view, no SSH session required
  • Node labels and grouping
  • Pilot Agent for nodes behind NAT or strict firewalls
  • Node compatibility checks before deploying

Automation

Security

Operations


Before you install

Sencho talks to Docker through the host's /var/run/docker.sock. Mounting this socket grants Sencho the same privilege as sudo docker on the host. This is the same model used by Portainer, Dockge, Komodo, and other Compose dashboards. If your threat model requires stricter isolation, see running with a non-root container user and front Sencho with a reverse proxy that enforces authentication.

Quick start

Sencho runs in a single container.

services:
  sencho:
    image: saelix/sencho:latest
    container_name: sencho
    restart: unless-stopped
    ports:
      - "1852:1852"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/app/data
      # 1:1 Compose Path Rule: the host path MUST match the container path
      - /opt/docker:/opt/docker
    environment:
      - COMPOSE_DIR=/opt/docker
      - DATA_DIR=/app/data
docker compose up -d

Open http://your-server:1852 and create your admin account.

Always front Sencho with a TLS-terminating reverse proxy in production. See the self-hosting guide for hardening, environment variables, and reverse-proxy examples.

Run with docker run instead
docker run -d --name sencho \
  -p 1852:1852 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v sencho_data:/app/data \
  # 1:1 Compose Path Rule: the host path MUST match the container path
  -v /opt/docker:/opt/docker \
  -e COMPOSE_DIR=/opt/docker \
  saelix/sencho:latest

For the full walkthrough, see the quickstart guide.


Adding remote nodes

To manage a second machine, install Sencho on it the same way, then add it from the primary dashboard with its URL and a long-lived API token. The primary proxies authenticated HTTP and WebSocket requests to the remote instance. The remote node does not run SSH for Sencho, does not expose its Docker socket on the network, and does not run a separate agent process. The local Sencho on each node manages its own Docker through the standard socket mount described in Quick start. Nodes behind NAT or strict firewalls can opt into the Pilot Agent for outbound-only connectivity.

See the multi-node guide for the full token-bearer flow.


Screenshots

Stacks Editor
Fleet Logs
Security overview Blueprints and drift
Scheduled Operations Compose Doctor

Telemetry and data handling

Sencho does not emit telemetry, analytics, or crash reports, and makes no outbound calls to Sencho-controlled endpoints. Stack metadata, container inventory, and user activity never leave your instance.


Admiral

Admiral is Studio Saelix's paid business assurance plan on top of everything in Community: Hardened Build, Recovery Vault (managed off-site snapshots), priority support, and governance depth (LDAP / Active Directory, full audit log export and anomaly detection, and related organizational controls). Built-in RBAC (five roles and scoped assignments) is included on Community. AWS ECR registry credentials currently require Admiral as well; that access rule is temporary availability, not the reason Admiral exists. See sencho.io/pricing for current plan details.


Documentation, community, and license


Contributors

S
Description
Self-hosted Docker Compose management platform. Great for homelabs, small DevOps teams, and platform engineers.
Readme AGPL-3.0 194 MiB
Languages
TypeScript 99.2%
JavaScript 0.4%
CSS 0.3%
Dockerfile 0.1%