The status banner added in9d28e12only rendered on the stack detail page, but Update is most often clicked from the stacks list — so in practice the status was invisible. Render it on the stacks list and dashboard too. Actions on different stacks run concurrently from the list, so busy state and status are now keyed by stack id instead of a single value: previously the first action to finish cleared every row's spinner, and each new action overwrote the previous one's status. StacksTable takes an isBusy(id) predicate in place of the single busyId prop. Also bumps the version so the newly version-tagged CI images (f8bfc91) actually differ from the running release — self-update compares tags against APP_VERSION, so shipping without a bump shows no update. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pMmFFkdfxkoYjcEcpZTa5
StackPilot
A self-hosted Docker Compose manager for power users and homelab enthusiasts — as intuitive as Dockge, as capable as Portainer for Compose workflows.
Status: Phase 1 (Core) + Phase 2 (Volumes & GPU) + Phase 3 (Quality of Life) + Phase 4 (Operations) + Phase 5 (Multi-host) + Phase 6 (Backup destinations) + Phase 7 (Scheduled backups) + Phase 8 (Remote-stack backups)
- Phase 9 (Networks) + Phase 10 (iGPU passthrough) + Phase 11 (Remote UX & network attach) + Phase 12 (File browser) + Phase 13 (Multi-host networks & images) + Phase 14 (Multi-host file browser) + Phase 15 (Dashboard stack resource usage) + Phase 16 (Volumes page, multi-host) + Phase 17 (Multi-host dashboard) + Phase 18 (Image prune) + Phase 19 (Compose validate & diff) + Phase 20 (Container management) + Phase 21 (Container terminal) + Phase 22 (Auto-update) + Phase 23 (Secrets & configs) + Phase 24 (Design System v2) complete.
What works today (Phase 1)
- File-first stacks — every stack is a plain
compose.yaml(+ optional.env) on disk. The DB only stores metadata; nothing is locked in. - Auth — JWT access/refresh tokens, bcrypt hashing, admin/user roles, and a first-launch setup wizard that creates the initial admin account.
- Stack lifecycle — create, edit, clone, delete, and
up / down / start / stop / restart / pull / updateviadocker compose. - Live status — running / partial / stopped / error / updating, computed from Docker container labels.
- Real-time logs — streamed over WebSocket, color-coded per service.
- Live deploy console — deploying from the editor streams
compose upoutput (image pulls, container creation) over a WebSocket in real time instead of a blind spinner; the deploy keeps running server-side if the modal is closed. Works for remote stacks too — the central app proxies the agent's deploy stream through to the browser. Compose runs with--progress json, so the console shows a real progress bar (download bytes per layer, weighted by layer size, then container create/start) plus a per-image bar; the raw output is kept below it and updates one line per layer instead of scrolling past. - Monaco editor — YAML editing with an
.envtab and adocker run→ compose converter. - Dashboard — system resource bar, stack grid with quick actions, and a recent-activity audit feed.
- Auto-discovery — stacks created outside the UI (any folder under the stacks dir containing a compose file) are picked up automatically.
- Dark / light theme.
Phase 2 — Volumes & GPU
- Volume Wizard in the editor (right-hand helper panel): Bind / Named / NFS /
SMB-CIFS / tmpfs, with a sandboxed host path browser for bind mounts and a
live YAML preview. NFS/SMB
driver_optsare generated for you. - GPU assignment per service: auto-detects NVIDIA (
nvidia-smi) and AMD/Intel (/dev/dri+ sysfs), injects the right YAML (NVIDIAdeploy.reservations, or/dev/dripassthrough + render/video groups +LIBVA_DRIVER_NAME=iHDfor Intel). - Device passthrough: lists host USB / serial-TTY / DRI nodes, add per service,
plus a guarded
privilegedtoggle. - All wizard edits are merged into the compose YAML server-side (robust, validated) and returned to the editor for review before saving.
GPU/device detection needs host visibility. The bundled compose bind-mounts
/dev:/dev:ro; NVIDIA additionally requires the NVIDIA container runtime on the host.
Phase 3 — Quality of Life
- Env editor: table mode with sensitive-value masking (
PASS/SECRET/KEY/… auto-detected) + raw mode, plus PUID/PGID/TZ quick-insert. - Image update checker: background task compares the local manifest digest with the registry (Docker Hub / ghcr / lscr / private v2 with token auth); update badges on the Images page + an "updates available" banner on the dashboard.
- Port conflict detector: pre-deploy check against host-bound ports
(
/proc/net/tcp[6]) and running container bindings, with a confirm dialog. - Resource limits: CPU/memory sliders in the editor →
deploy.resources.limits. - Template library: each template is a ready-to-run stack folder in git
(
backend/templates/<slug>/—compose.yaml+.env.example+template.json). Bundled set: Jellyfin, Vaultwarden, Uptime-Kuma, Paperless-NGX, Gitea. "Pull" copies the whole folder into a new stack (.env.example→.env) which you then edit and deploy. Save any stack back as a custom template (stored under${DATA_DIR}/templates/). Add your own by dropping a folder into the templates dir. - Healthcheck status surfaced per container in the stack overview.
Phase 4 — Operations
- Self-update (0.32.0): the top-bar version badge checks the registry for a
newer StackPilot release on page load (
GET /api/system/update, anonymous v2 token flow, 10 min cache) and shows an amber update pill. One click (POST /api/system/update, admin) spawns a detached helper container that runsdocker compose pull && up -don StackPilot's own compose project (resolved from its container labels) — the helper survives the backend being recreated; the UI polls/api/healthand reloads when the new version answers. Installs not managed by compose get a clear "update manually" error instead. - Backup & restore: per-stack
.tar.gzbackups covering the whole stack — the stack folder, every bind-mounted data directory (./config,/mnt/appdata/…) and every named volume. Bind sources and volumes are read through a throwaway helper container, i.e. by host path, so data that StackPilot itself cannot see is captured too (that is the case wheneverSTACKS_HOST_DIRdiffers from the container'sSTACKS_DIR— compose then creates the data directories at the container path on the host, and a naive backup would only find the compose file). The Backup dialog shows the full inventory with sizes and lets you pick what goes in; NFS/CIFS-backed volumes are unchecked by default because they live on a NAS and restoring one would overwrite the share. Restore puts bind folders back at their host paths and preserves permissions, ownership and symlinks (PUID/PGID-based images such as the *arr suite need this), with optional rename and overwrite/conflict detection. - Notification webhooks: ntfy, Discord, Slack, Gotify, or generic JSON, each
subscribed to chosen events (image update available, stack start/stop/error,
pull failed). Managed in Settings → Notifications; env
NOTIFY_WEBHOOKSstill supported for generic endpoints. - Settings page: tune the update-check interval, manage webhooks, and manage users (create/disable/delete, promote/demote, with last-admin safeguards).
- Audit log page: searchable, paginated view of all recorded actions.
- Mobile-responsive layout: off-canvas sidebar + adaptive spacing.
Phase 5 — Multi-host
- Remote agents: deploy
stackpilot-agent(same image, different CMD) on any host — it needs only the Docker socket and a sharedAGENT_TOKEN, and exposes a slim, token-guarded stack/system API (no UI, no DB). - Central management: add hosts under Settings → Remote hosts (name, agent URL, token) with a live connectivity dot. The Stacks page groups stacks by host ("This host" + one section per agent); remote stacks have their own detail view with full lifecycle (start/stop/restart/pull/update/down), live logs, and compose/.env editing — all proxied to the agent.
Phase 6 — Backup destinations
- Off-box backups: define SFTP, S3-compatible (MinIO, Backblaze B2,
AWS S3, …) or NFS share (0.33.0) destinations under Settings → Backup
destinations (with a Test button; secrets are masked in API responses).
NFS needs no privileges in the StackPilot container: the Docker daemon mounts
the export as a named volume (
stackpilot-nfs-dest-<id>, recreated when the config changes) and file I/O runs through throwaway helper containers. - Push & restore: the stack Backup dialog can push straight to a destination instead of downloading; the Restore dialog can browse a destination's backups and restore (volumes included) directly from it. Remote backups can also be deleted from the UI.
Phase 7 — Scheduled backups
- Recurring backups: schedule a stack to back up to a destination hourly, daily, or weekly (UTC) under Settings → Scheduled backups. A background scheduler runs due jobs every minute and records last/next run + status.
- Retention: keep the newest N backups per stack on the destination; older ones are pruned automatically.
- Run now for an on-demand run, plus a
backup_failednotification event wired into the webhook system.
Phase 8 — Remote-stack backups
- Back up agent stacks: the agent exposes its own backup/restore endpoints, and the main app streams a remote stack's backup through to a destination (credentials stay central — agents never see them). Remote stack detail has a Backup button; each host section on the Stacks page has a Restore button.
- Schedule remote stacks: a backup schedule can target a remote host; backups
are namespaced per host (
backup-<host>-<stack>-…) so retention never prunes across hosts sharing a destination.
Phase 9 — Networks
- Network management: the Networks page lists Docker networks (driver, scope, subnet, attached containers / in-use, owning stack), with create (bridge / macvlan / ipvlan / overlay, optional subnet+gateway, internal/attachable), delete (default networks protected; in-use guarded by Docker), and prune unused.
- Stack delete: local stacks can now be deleted from the UI (stack detail and the stack card), with a confirm dialog and an optional "keep files on disk".
Phase 10 — iGPU passthrough
- Render/video group detection: for a passed-through Intel/AMD iGPU, StackPilot
reads the host group ownership of the
/dev/drinodes (render node →renderGID, pairedcardnode →videoGID) and injects them as numericgroup_addentries (e.g.group_add: ["991", "44"]). Group names rarely resolve inside images, so the numeric GID is what actually grants access. The GPU selector shows the detected GIDs; removal cleans them (andLIBVA_DRIVER_NAME).
Phase 11 — Remote UX & network attach
- Live remote logs: remote-stack logs now stream over a WebSocket proxied
through the central app to the agent (
/ws/agent-logs/{agent}/{stack}), instead of polling — same live viewer as local stacks. - Deploy to a remote host from the UI: the New Stack editor and the template dialog gained a host selector. Pick an online agent and the stack is created (and optionally started) on that host; you land on its remote detail page.
- Network attach/detach: each network row on the Networks page expands to an
inspect view listing connected containers, with admin controls to disconnect a
container or connect any container on the host (
POST /api/networks/{id}/connect//disconnect).
Phase 24 — Design System v2 (analytics-style UI)
- New shell: the sidebar is gone — a fixed 60px top bar carries a pill navigation (active route = dark pill), the logo mark, a version badge, a remote-host online indicator, theme toggle and an avatar menu. Narrow screens get an off-canvas drawer.
- Design tokens (
frontend/src/styles/tokens.css): one CSS-variable set for surfaces, borders, text tiers, brand colours, radii and type scales, with class-based dark-mode overrides. Existing Tailwind aliases (bg/card/accent) are remapped onto the tokens so all pages reskin consistently. Typeface: Schibsted Grotesk (bundled, offline-friendly). - Stack Health funnel — the dashboard centrepiece. Five stages
(
discovered → running → healthy → updated → auto-managed) fromGET /api/dashboard/funnel(30 s server cache,?refresh=trueto bust; the last stage — API keymonitored— counts stacks with an enabled auto-update policy). SVG waterfall with alternating gradient / diagonal-hatch bars, value chips and hover conversion/drop-off tooltips. - Summary widgets from
GET /api/dashboard/summary: compose-containers card with per-host breakdown (agents report a compose-only count) and an "Insights" chip (healthy-rate %), a 30-day uptime line chart (sampled every 5 min intoDATA_DIR/uptime.jsonl, charted as daily averages), and an ops contribution grid from audit-log activity with the peak weekday. A 30/7-day range selector slices both series client-side. - Explore prompt bar under the funnel: typed queries or
/running,/stopped,/attentiontags deep-link to the Stacks page, which now honours?q=and?filter=(plus a new status-filter select).
Phase 23 — Secrets & configs (compose file-based)
- A Secrets tab on the stack detail page manages per-stack Docker secrets and configs: create one by name + content, list them (name, kind, size — content is never returned by the API), and delete. Content is write-only: once saved it is cleared from the form and cannot be read back.
- Files are stored inside the stack's own directory (
<stack_dir>/.secrets/<name>/.configs/<name>, dir0700/ file0600) and referenced from the compose file with a relativefile:path, so the Docker daemon reads them with noHOST_ROOT_PREFIXdependency — exactly as if dropped next tocompose.yaml. - Attach/detach wires a stored secret/config into a chosen service: secrets
appear at
/run/secrets/<name>, configs mount at a target path you specify. The compose file is rewritten in place (top-levelsecrets:/configs:defs are pruned when no service still uses them); redeploy the stack to apply. - Admin-only (secrets are sensitive); every write/delete/attach/detach is
audited (
secret.*). Works for remote stacks too — the agent stores the files on its own host (/agent/stacks/{id}/secrets/*, proxied via/api/agents/{id}/stacks/{id}/secrets/*). Names are validated against path traversal (single component, no.., no leading dot); content capped at 1 MiB.
Phase 22 — Auto-update (Watchtower-style)
- A per-stack Auto-update policy (on the stack Overview tab): when the background image-update check finds a newer registry digest for one of the stack's images, the stack is either pulled + redeployed or merely flagged ("notify only"), with a Check now button for an on-demand run.
- Runs inside the existing image-update-check cycle (reuses the freshly-computed digest cache, no extra registry calls). Only running stacks are auto-redeployed — a stopped stack is never silently started ("skipped").
- New
stack_auto_updatednotification event. Works for remote stacks too (policy stored centrally; the agent answers/agent/stacks/{id}/updatesand performs the redeploy). Last run + status (updated / up-to-date / update-available / skipped / error) are shown inline.
Phase 21 — Container terminal (web exec)
- An interactive terminal into any running, compose-managed container,
opened from the terminal button on its container card (stack Overview tab).
Streams an exec session over WebSocket into xterm.js — pick
/bin/sh,/bin/bash, or/bin/ash; full TTY with resize. - Admin-only (exec is root-equivalent): a non-admin token is rejected at the
WebSocket handshake (
4403). Only containers with thecom.docker.compose.projectlabel can be reached. - Works for remote stacks too: the same terminal proxies through
/ws/agent-exec/{agent_id}/{container_id}to the agent's new/agent/ws/exec/{container_id}(bidirectional — keystrokes in, output out).
Phase 20 — Container management
- The stack Overview tab now renders each service as an expandable
container card instead of a static row. Expanding it fetches a curated
single-container inspect view (image, state + exit code, restart count,
started-at, networks, mounts, and environment) via
GET /api/containers/{id}. - Admins get per-container start / stop / restart buttons directly on the
card (
POST /api/containers/{id}/{action}), so a single misbehaving service can be bounced without touching the rest of the stack. - Works for remote stacks too — the same card is used on the remote stack
detail page, proxied through
/api/agents/{id}/containers/*to the agent's new/agent/containers/*endpoints. - Only containers carrying the
com.docker.compose.projectlabel are exposed, so this never becomes a generic "control any container on the host" backdoor.
Phase 19 — Compose validate & diff
- The editor can validate a compose file (
docker compose config) before deploying and show a diff against the currently deployed definition, so you can see exactly what a re-deploy will change.
Phase 18 — Image prune
- Prune images (dangling, or all unused) from the Images page, on the local host and on each agent.
Phase 17 — Multi-host dashboard
- The dashboard now shows, per host (local + each registered agent, online
dot / offline notice), a resource overview bar (CPU cores, memory
used/total, disk used/total, Docker volumes total, containers, Docker version)
and a stacks-with-usage table with CPU/memory meters and inline
start/stop/restart. The volumes total reuses the cached
/volumes/sizeslookup (docker system df), polled gently (~60s). - New agent endpoint
/agent/stacks/stats(proxied at/api/agents/{id}/stacks/stats);/agent/systemnow also reportscpu_cores,mem_total,mem_used, anddisk_total/disk_used(disk of the host volume backing the stacks dir) so the remote resource bar and meters have a host reference.
Phase 16 — Volumes page (multi-host)
- New Volumes page (sidebar) with per-host sections (local + each online agent, like Networks/Images). Lists Docker volumes with driver, owning stack, in-use containers and mountpoint.
- Admin actions: delete a volume (with an in-use warning + force option) and
Prune unused; an Only unused filter. New agent endpoints
/agent/volumes(list/delete/prune), proxied at/api/agents/{id}/volumes/*. - Volume sizes are loaded on demand via a Compute sizes button (runs
docker system df, which walks volume contents and can take a few seconds); results are cached ~60s. EndpointGET /api/volumes/sizes(+ per-agent). - The Volume Wizard in the stack editor (bind/named/NFS/SMB/tmpfs YAML generation) is unchanged — the new page is for managing/cleaning up volumes.
Phase 15 — Dashboard stack resource usage
- The dashboard now lists stacks in a table (status, services) with live
CPU and memory usage per stack, sampled from
docker statsand aggregated by compose project. - When a stack has
deploy.resources.limitsassigned, the bar fills toward that limit and shows usage vs the limit (e.g.0.42 / 1 cores,310 MB / 512 MB); otherwise it shows absolute usage against the host total. Inline start/stop/ restart actions per row for admins. New endpointGET /api/stacks/stats.
Phase 14 — Multi-host file browser
- The Files page now has a host switcher. When agents are registered, a Host dropdown at the top switches the whole browser between the local host and any online agent; switching resets the path and clipboard.
- All file operations (browse, view/edit, create, rename, copy/move, delete,
upload files & folders, download) work against the selected agent, sandboxed
by that agent's
ALLOWED_BROWSE_ROOTS/HOST_ROOT_PREFIX. - New agent endpoints
/agent/files/*, proxied at/api/agents/{id}/files/*.
Phase 13 — Multi-host networks & images
- Networks and Images are now per-host. Both pages render a section for the local host plus one for every registered agent (online dot included), exactly like the Stacks page. Each agent section talks to that host's Docker daemon.
- Remote network management: list, inspect, create, delete, prune, and connect/disconnect containers on an agent host — including a Prune unused button, which resolves the common "all predefined address pools have been fully subnetted" deploy error without SSH.
- Remote images: list image tags (with using-stacks) and run on-demand update checks per host.
- New agent endpoints
/agent/networks/*and/agent/images/*, proxied through the central app at/api/agents/{id}/networks/*and/api/agents/{id}/images/*.
Phase 12 — File browser
- Files page (sidebar): a full host filesystem browser with breadcrumb navigation, clickable browse-root chips, an Up control, and a show/hide hidden-files toggle. Listings show size, permissions and modified time.
- View & edit: clicking a text file opens it in a Monaco editor (with syntax highlighting picked from the extension). Binary and oversized files are detected and offered as a download instead. Admins can edit and Save.
- Manage (admin): create folders/files, rename, delete (recursive for folders), upload files or whole folders (the directory tree is recreated server-side), and download any file. Copy/cut & paste moves files and folders between directories (clipboard bar + per-row copy/cut, with an overwrite prompt on conflict). Every mutation is audit-logged.
- Sandboxed: all access is confined to
ALLOWED_BROWSE_ROOTS; path traversal and deleting a browse root are refused. To reach the real host filesystem, mount it into the backend and setHOST_ROOT_PREFIX(see the commented/:/host_rootvolume indocker-compose.yml). Endpoints live under/api/files/*(list,read,write,mkdir,touch,rename,copy,move,upload— with optionalrel_pathfor folder uploads —,download,DELETE).
Deploying an agent on another host
cd agent
cp .env.example .env # set a strong AGENT_TOKEN
docker compose up -d # exposes the agent on :5010
Then in the central UI: Settings → Remote hosts → Add host with
http://<that-host>:5010 and the same AGENT_TOKEN.
Architecture
frontend (React + Vite + Tailwind, served by nginx)
│ proxies /api and /ws
▼
backend (FastAPI + docker-py + SQLite)
│ docker-py + `docker compose` CLI
▼
Docker Engine (via /var/run/docker.sock — never exposed to the browser)
Quick start
cd stackpilot
cp .env.example .env
# edit .env and set a strong SECRET_KEY: openssl rand -base64 48
docker compose up -d --build
Open http://localhost:5009 and complete the first-launch setup wizard to create your admin account.
Configuration
All backend settings are environment variables (see backend/config.py). The
most important ones:
| Variable | Default | Purpose |
|---|---|---|
SECRET_KEY |
(auto, dev only) | JWT signing key — set this in prod |
STACKS_DIR |
/opt/stacks |
Where stack folders live (in-container) |
DATA_DIR |
/data |
SQLite DB + app data |
CORS_ORIGINS |
localhost | Allowed API origins (comma separated) |
The host path for stacks is set via STACKS_HOST_DIR in .env, and it should
be the same path as STACKS_DIR (/opt/stacks by default). Compose runs
inside the backend container, so a stack's relative bind mounts (./config) are
resolved against the container path and the daemon creates those directories at
that path on the host. Point STACKS_HOST_DIR somewhere else and every stack's
data lives at /opt/stacks/<stack>/… on the host while StackPilot looks at a
different folder — the file browser and editor then show only the compose file.
Backups cover the data either way (they read bind sources by host path through a
helper container) and the Backup dialog warns when the two paths diverge.
Local development
Backend:
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
SECRET_KEY=dev STACKS_DIR=../data/stacks DATA_DIR=../data uvicorn main:app --reload --port 5008
Frontend (proxies to the backend on :5008):
cd frontend
npm install
npm run dev # http://localhost:5173
API surface (Phase 1)
POST /api/auth/setup | login | refresh GET /api/auth/me | needs-setup
GET /api/stacks POST /api/stacks
GET /api/stacks/{id} PUT /api/stacks/{id} DELETE /api/stacks/{id}
POST /api/stacks/{id}/{start|stop|restart|pull|update|down|clone}
GET /api/stacks/{id}/logs GET /api/stacks/{id}/export
POST /api/stacks/convert (docker run → compose)
GET /api/system/info | gpus | devices GET /api/audit
GET /api/system/update POST /api/system/update (self-update)
WS /ws/logs/{stack_id}[/{service}] WS /ws/events
Phase 2 endpoints
GET /api/volumes | /orphaned DELETE /api/volumes/{name}
POST /api/volumes/prune POST /api/volumes/generate-yaml
GET /api/host/paths?path=&show_hidden= (sandboxed browser)
POST /api/editor/services | add-volume | set-gpu | add-device | remove-device | set-privileged
Phase 3 endpoints
GET /api/images | /updates POST /api/images/check
POST /api/ports/conflicts POST /api/editor/set-resources
GET /api/templates | /{id} POST /api/templates/{id}/instantiate
POST /api/templates | /from-stack DELETE /api/templates/custom/{slug}
Phase 4 endpoints
GET /api/stacks/{id}/backup?include_volumes=&stop_first= POST /api/stacks/restore
GET /api/settings PUT /api/settings
GET /api/settings/webhooks POST /api/settings/webhooks
PUT /api/settings/webhooks/{id} DELETE /api/settings/webhooks/{id}
POST /api/settings/webhooks/{id}/test
GET /api/auth/users POST /api/auth/users
PATCH /api/auth/users/{id} DELETE /api/auth/users/{id}
Phase 5 endpoints
GET /api/agents POST /api/agents
PUT /api/agents/{id} DELETE /api/agents/{id}
POST /api/agents/{id}/ping GET /api/agents/{id}/system
GET /api/agents/{id}/stacks | /{sid} GET /api/agents/{id}/stacks/{sid}/logs
POST /api/agents/{id}/stacks PUT /api/agents/{id}/stacks/{sid}
DELETE /api/agents/{id}/stacks/{sid} POST /api/agents/{id}/stacks/{sid}/{action}
agent (on the remote host, Bearer AGENT_TOKEN):
GET /agent/ping | /system | /stacks | /stacks/{id} | /stacks/{id}/logs
GET /agent/stacks/{id}/backup POST /agent/stacks/restore
POST /agent/stacks | /stacks/{id}/{action} PUT/DELETE /agent/stacks/{id}
Phase 6 endpoints
GET /api/backups/destinations POST /api/backups/destinations
PUT /api/backups/destinations/{id} DELETE /api/backups/destinations/{id}
POST /api/backups/destinations/{id}/test GET /api/backups/destinations/{id}/backups
DELETE /api/backups/destinations/{id}/backups/{name}
POST /api/stacks/{id}/backup/push POST /api/stacks/restore-from
Phase 7 endpoints
GET /api/backups/schedules POST /api/backups/schedules
PUT /api/backups/schedules/{id} DELETE /api/backups/schedules/{id}
POST /api/backups/schedules/{id}/run
Phase 8 endpoints (remote-stack backups)
GET /api/agents/{id}/stacks/{sid}/backup POST /api/agents/{id}/stacks/{sid}/backup/push
POST /api/agents/{id}/stacks/restore POST /api/agents/{id}/stacks/restore-from
backup schedules accept an optional agent_id to target a remote host.
Phase 9 endpoints
GET /api/networks | /{id} POST /api/networks
DELETE /api/networks/{id} POST /api/networks/prune
DELETE /api/stacks/{id}?delete_files= (stack delete, now surfaced in the UI)
Phase 11 endpoints
WS /ws/agent-logs/{agent_id}/{stack_id} (live remote logs, proxied to the agent)
GET /api/networks/{id}/containers POST /api/networks/{id}/connect | /disconnect
POST /api/agents/{id}/stacks (create a stack on a remote host — now in the UI)
POST /api/templates/{id}/instantiate {agent_id} (instantiate a template onto a remote host)
Phase 24 endpoints
GET /api/dashboard/funnel[?refresh=true] (stack-health funnel, 30s TTL cache)
GET /api/dashboard/summary (containers, uptime series, ops activity)
Security notes
- The Docker socket is only ever touched by the backend process; it is never proxied to the browser.
- Login is rate-limited (10/min/IP).
- Compose files are backed up to
*.bakbefore every overwrite. - Generated YAML never includes the obsolete
version:field and uses Compose v2 (docker compose) syntax.