-## How alerts work
-
-Sencho's monitoring service samples container metrics every minute and evaluates all defined alert rules. When a rule's condition holds true for the configured duration, a notification is dispatched. A cooldown period prevents the same alert from firing repeatedly.
-
-## Setting up a notification agent
-
-At least one agent must be enabled before alerts can be delivered. Go to **Settings → Notifications**.
+Three channel types are available, each configured with a webhook URL and an enable/disable toggle:
### Discord
-1. In Discord, go to your server's **Settings → Integrations → Webhooks**
+1. In Discord, go to your server's **Settings > Integrations > Webhooks**
2. Click **New Webhook**, choose a channel, and copy the webhook URL
-3. In Sencho, open **Settings → Notifications → Discord**, paste the URL, enable the toggle, and click **Save**
-4. Click **Test** to send a test message
+3. In Sencho, open **Settings > Notifications > Discord**, paste the URL, enable the toggle, and click **Save**
+4. Click **Test** to send a test message and verify the connection
### Slack
1. In Slack, go to **api.slack.com/apps**, create an app, and add the **Incoming Webhooks** feature
2. Activate it and copy the generated webhook URL for your chosen channel
-3. In Sencho, open **Settings → Notifications → Slack**, paste the URL, enable the toggle, and click **Save**
+3. In Sencho, open **Settings > Notifications > Slack**, paste the URL, enable the toggle, and click **Save**
### Generic Webhook
-Any HTTP endpoint that accepts a POST with a JSON body can receive Sencho alerts. Go to **Settings → Notifications → Webhook**, enter the URL, and click **Save**.
+Any HTTP endpoint that accepts a POST with a JSON body can receive Sencho alerts. Go to **Settings > Notifications > Webhook**, enter the URL, enable the toggle, and click **Save**.
The payload format is:
+
```json
{
"level": "warning",
@@ -45,76 +44,94 @@ The payload format is:
## Creating stack alerts
-Stack alerts are configured per-stack. Right-click a stack in the sidebar (or click the **⋮** button) and select **Alerts**.
+Stack alerts are configured per stack. Right-click a stack in the sidebar (or click the three-dot menu) and select **Alerts**. A panel slides open showing existing rules and a form to create new ones.
-
+
-The alerts panel slides open, showing existing rules for the stack and a form to create new ones.
+The panel includes:
-
-
-
+- **Notification status banner** at the top, showing whether channels are configured. If no channels are enabled, a warning explains that rules will be evaluated but no external notifications will be sent.
+- **Existing Rules** section listing all active rules for this stack, each showing the metric, condition, duration, and cooldown. Hover over a rule to reveal the delete button.
+- **Add New Rule** form with the fields described below.
### Alert fields
| Field | Description |
|-------|-------------|
-| **Metric** | The container metric to watch |
-| **Operator** | Comparison operator: `>`, `>=`, `<`, `<=`, `==` |
-| **Threshold** | The value to compare against |
-| **Duration (minutes)** | How long the condition must hold before firing |
-| **Cooldown (minutes)** | Minimum time between repeated notifications for this rule |
+| **Metric** | The container metric to watch (see table below) |
+| **Operator** | Comparison: Greater than, Greater or equal, Less than, Less or equal, Equals |
+| **Threshold** | The numerical value to compare against |
+| **Duration (mins)** | How long the condition must hold before firing (default: 5) |
+| **Cooldown (mins)** | Minimum time between repeated notifications for this rule (default: 60) |
### Available metrics
-| Metric | Unit | Description |
-|--------|------|-------------|
-| `cpu_percent` | % | CPU usage relative to total host cores |
-| `memory_mb` | MB | RSS memory used by the container |
-| `memory_percent` | % | Memory used as a fraction of the host total |
-| `net_rx` | bytes/s | Inbound network rate |
-| `net_tx` | bytes/s | Outbound network rate |
-| `restart_count` | count | Number of times the container has restarted |
+| Metric | Description |
+|--------|-------------|
+| CPU Usage (%) | CPU usage relative to total host cores |
+| Memory Usage (%) | Memory used as a fraction of the host total |
+| Memory Usage (MB) | RSS memory used by the container |
+| Network In (MB) | Inbound network throughput |
+| Network Out (MB) | Outbound network throughput |
+| Restart Count | Number of times the container has restarted |
### Example: alert on high CPU
-To alert when any container in a stack uses more than 80% CPU for over 5 consecutive minutes, with a 30-minute cooldown:
+To alert when any container in a stack uses more than 80% CPU for over 5 consecutive minutes, with a 60-minute cooldown:
| Field | Value |
|-------|-------|
-| Metric | `cpu_percent` |
-| Operator | `>` |
-| Threshold | `80` |
-| Duration | `5` |
-| Cooldown | `30` |
+| Metric | CPU Usage (%) |
+| Operator | Greater than |
+| Threshold | 80 |
+| Duration | 5 |
+| Cooldown | 60 |
## Notification history
-All dispatched notifications appear in the **notification bell** (top-right of the nav bar). Click it to see recent alerts with their level, message, and timestamp. Mark all as read or clear individual entries from there.
+All dispatched notifications appear in the **notification bell** in the top-right corner of the navigation bar. A red dot pulses on the bell when unread notifications exist.
+
+
+
+
+
+Click the bell to open the notification popover, which shows:
+
+- **Level badge** for each entry (ERROR in red, WARNING in amber, INFO in default)
+- **Node name** badge for notifications from remote nodes
+- **Timestamp** of when the alert was triggered
+- **Alert message** describing what was breached
+
+The popover header includes:
+
+| Action | What it does |
+|--------|--------------|
+| **Mark all as read** | Marks all notifications as read (removes the red dot) |
+| **Clear all** | Deletes all notifications from the list |
+
+You can also dismiss individual notifications by hovering over them and clicking the dismiss button.
-
+Key details:
-### How it works
+- **Alert rules are stored on each node independently.** Rules created while a remote node is selected are saved on that remote instance, not your primary instance.
+- **Monitoring runs locally on each node.** Each Sencho instance evaluates its own alert rules against its own container metrics.
+- **Notifications are sent by the node that detects the breach.** Make sure notification channels are configured on each remote node where you want to receive alerts, since channel settings are per-instance.
-1. **Alert rules are stored on each node independently.** When you create an alert while a remote node is selected, the rule is saved on that remote Sencho instance's database - not your primary instance.
-2. **Monitoring runs locally on each node.** The remote Sencho instance evaluates its own alert rules against its own container metrics every minute.
-3. **Notifications are sent by the node that detects the breach.** Make sure notification agents (Discord, Slack, Webhook) are configured on each remote node where you want to receive alerts - notification settings are per-instance.
+The alert panel shows a blue info banner when you are configuring alerts on a remote node, including which channels are active on that node.
### Setup checklist for remote alerts
-1. On the **remote** Sencho instance, go to **Settings → Notifications** and configure at least one agent
+1. On the **remote** Sencho instance, go to **Settings > Notifications** and configure at least one channel
2. From your **primary** instance, switch to the remote node
3. Right-click a stack and select **Alerts** to create rules
4. The remote instance handles monitoring and notification delivery independently
diff --git a/docs/features/api-tokens.mdx b/docs/features/api-tokens.mdx
index 59171287..fd9bb60f 100644
--- a/docs/features/api-tokens.mdx
+++ b/docs/features/api-tokens.mdx
@@ -7,7 +7,7 @@ description: Generate scoped API tokens for CI/CD pipelines, scripts, and automa
API Tokens require a Sencho **Admiral** license. Skipper and Community Edition do not include this feature.
-API tokens let you authenticate external tools - CI/CD pipelines, deployment scripts, monitoring integrations - without sharing user credentials. Each token is scoped to a specific permission level so you can follow the principle of least privilege.
+API tokens let you authenticate external tools (CI/CD pipelines, deployment scripts, monitoring integrations) without sharing user credentials. Each token is scoped to a specific permission level so you can follow the principle of least privilege.
## Permission scopes
@@ -15,9 +15,9 @@ Every token is created with one of three permission levels:
| Scope | Allowed actions |
|-------|----------------|
-| **Read Only** | `GET` requests only - view stacks, containers, metrics, and settings |
-| **Deploy Only** | Everything in Read Only, plus stack operations: deploy, down, restart, stop, start, update |
-| **Full Admin** | Full stack and container management - all read and write operations on stacks, containers, images, and system metrics |
+| **Read Only** | `GET` requests only: view stacks, containers, metrics, and settings |
+| **Deploy Only** | Everything in Read Only, plus stack lifecycle operations: deploy, down, restart, stop, start, update |
+| **Full Admin** | All read and write operations except the universal restrictions listed below |
Choose the narrowest scope that fits your use case. A CI pipeline that only deploys stacks should use **Deploy Only**, not Full Admin.
@@ -41,17 +41,29 @@ These restrictions ensure that API tokens cannot escalate privileges or modify t
1. Open **Settings Hub** and navigate to the **API Tokens** tab (visible to Admiral admins only).
2. Click **Create Token**.
-3. Enter a descriptive name (e.g., "GitHub Actions deploy"), select a permission scope, and optionally choose an expiration period (30, 60, 90 days, or 1 year). Tokens without an expiration must be revoked manually.
-4. Click **Create**. The raw token is displayed **once** - copy it immediately.
+3. Fill in the creation form:
+ - **Name**: A descriptive label (e.g., "GitHub Actions deploy")
+ - **Permission Scope**: Select Read Only, Deploy Only, or Full Admin
+ - **Expiration**: Choose 30 days, 60 days, 90 days, 1 year, or no expiration. Tokens without an expiration must be revoked manually.
+4. Click **Create**. A green banner appears with the raw token value. Copy it immediately using the copy button.
-
+
@@ -11,44 +11,58 @@ The **App Store** tab lets you browse a curated catalogue of Docker Compose temp
## Browsing templates
-Templates are loaded from a remote registry (configurable in **Settings → App Store**). The default registry provides 190+ self-hosted application templates.
+Templates are loaded from a remote registry (configurable in **Settings > App Store**). The default registry is LinuxServer.io. A live count of available apps is shown next to the category pills.
-**Search:** Type in the search bar to filter templates by name or description in real-time.
+**Search:** Type in the search bar to filter templates by name, description, or category in real-time.
**Categories:** Click any category pill to narrow the list:
-`Automation` · `Books` · `Development` · `Documentation` · `Downloaders` · `Media` · `Monitoring` · `Networking` · `Other` · `Product`
+`All` · `Automation` · `Books` · `Development` · `Documentation` · `Downloaders` · `Media` · `Monitoring` · `Networking` · `Other` · `Productivity` · `Security` · `Utilities`
Each template card shows:
-- Application logo
+- Application logo (or a placeholder icon if unavailable)
- Name and short description
-- Category tags
-- Links to the GitHub repo and documentation (where available)
+- Up to three category badges (click a badge to filter by that category)
## Deploying a template
-Click any template card to open the **deployment sheet** on the right side of the screen. Fill in the fields and click **Deploy**.
+Click any template card to open the **deployment sheet** on the right side of the screen.
+
+
+
+
+
+The sheet header displays the app logo, title, and a truncated description with a **Read more** / **Read less** toggle. Below the description you may see:
+
+- **Architecture badges** (e.g. `amd64`, `arm64`) when the template declares supported platforms
+- **GitHub stars** count
+- **Source** link to the GitHub repository
+- **Docs** link to the official documentation
+
+When deploying to a remote node, a badge at the top of the sheet shows the target node name.
### Stack name
-Pre-filled with the template name in lowercase. You can change it - the same [naming rules](/features/stack-management#creating-a-stack) apply (lowercase, hyphens, no spaces).
+Pre-filled with the template name in lowercase. You can change it; the same [naming rules](/features/stack-management#creating-a-stack) apply (lowercase, hyphens, no spaces).
-### Environment variables
+### Ports
-Each template declares the variables it needs. Sencho pre-fills sensible defaults where available (e.g. `PUID=1000`, `PGID=1000`, `TZ` from your browser locale).
-
-Edit any value before deploying. You can also click **Custom Env** to add arbitrary key-value pairs not defined by the template.
+For each exposed port, the sheet shows an editable **host port** field (left side) and the fixed **container port** (right side). Edit the host port to avoid conflicts with other services on the same machine.
### Volumes
-For each container mount point, enter the **host path** where that data should be stored. Suggested defaults (e.g. `./config`, `./data`) are shown as placeholders.
+For each container mount point, enter the **host path** where that data should be stored. Suggested defaults (e.g. `./config`, `./data`) are pre-filled.
+
-Click any row to expand it and see full request details including the API path, IP address, node ID, and entry ID.
+Click any row to expand it and see the full request details inline.
-## Filtering & search
+
+
+
+
+The header displays the total number of matching entries and provides **Refresh** and **Export** controls.
+
+Results are paginated at 50 entries per page. Navigation controls appear at the bottom of the table when there are multiple pages.
+
+## Filtering and search
The audit log provides several ways to find specific entries:
-- **Full-text search** — Search across action summaries, API paths, and usernames
-- **Method filter** — Filter by HTTP method (POST, PUT, DELETE, PATCH)
-- **Date range** — Set a start and/or end date to narrow results to a specific time window
+- **Full-text search** - Search across action summaries, API paths, and usernames
+- **Method filter** - Filter by HTTP method (POST, PUT, DELETE, PATCH) using the dropdown
+- **Date range** - Set a start and/or end date to narrow results to a specific time window
All filters work together and are applied server-side with pagination.
## Export
-Export the currently filtered audit log dataset as **CSV** or **JSON** using the **Export** dropdown in the header. The export respects all active filters — so you can narrow down to a date range or specific user before exporting.
+Export the currently filtered audit log dataset as **CSV** or **JSON** using the **Export** dropdown in the header. The export respects all active filters, so you can narrow down to a date range or specific user before exporting.
Exports are capped at 10,000 entries per download.
@@ -75,7 +90,7 @@ The **Auditor** role provides read-only access to the audit log without granting
Auditors **cannot** modify settings, manage users, deploy stacks, or perform any other administrative actions. This role is ideal for compliance officers, security reviewers, or team leads who need visibility into system activity without operational access.
-To create an Auditor user, go to **Settings → Users** and select the **Auditor** role when creating a new user.
+To create an Auditor user, go to **Settings > Users** and select the **Auditor** role when creating a new user.
## Configurable data retention
@@ -83,10 +98,14 @@ Audit log entries are automatically cleaned up based on your configured retentio
To change the retention period:
-1. Go to **Settings → Developer → Data Retention**
-2. Set the **Audit Log Retention** value (1–365 days)
+1. Go to **Settings > Developer > Data Retention**
+2. Set the **Audit Log Retention** value (1-365 days)
3. Click **Save Developer Settings**
+
+
+
+
Cleanup runs automatically as part of Sencho's periodic maintenance cycle.
## Security at rest
diff --git a/docs/features/auto-update-policies.mdx b/docs/features/auto-update-policies.mdx
index 6d9c9e82..ff9df405 100644
--- a/docs/features/auto-update-policies.mdx
+++ b/docs/features/auto-update-policies.mdx
@@ -9,9 +9,9 @@ description: "Automatically check for and apply container image updates on a sch
## Overview
-Auto-Update Policies let you define schedules for Sencho to automatically check your container images for updates and apply them when new versions are available. Think of it as a built-in Watchtower — but integrated directly into your Sencho dashboard with full visibility into what was updated and when.
+Auto-Update Policies let you define schedules for Sencho to automatically check your container images for updates and apply them when new versions are available. Think of it as a built-in Watchtower, integrated directly into your Sencho dashboard with full visibility into what was updated and when.
-Each policy targets a specific stack and runs on a cron schedule. When triggered, Sencho:
+Each policy targets a specific stack (or all stacks on a node) and runs on a cron schedule. When triggered, Sencho:
1. Inspects every container in the target stack
2. Compares local image digests against the remote registry
@@ -22,7 +22,7 @@ Each policy targets a specific stack and runs on a cron schedule. When triggered
-## Creating a Policy
+## Creating a policy
Navigate to **Auto-Update** in the sidebar and click **New Policy**.
@@ -36,10 +36,13 @@ Fill in the following fields:
|-------|-------------|
| **Name** | A descriptive name for the policy (e.g., "Nightly media stack update") |
| **Node** | The node where the target stack runs |
-| **Stack** | The stack to monitor and update |
-| **Schedule** | A cron expression or preset defining how often to check |
+| **Stack** | The stack to monitor and update, or **All Stacks** to update every stack on the selected node |
+| **Check Frequency** | A cron preset or custom cron expression defining how often to check |
+| **Enabled** | Toggle to enable or disable the policy on creation |
-### Schedule Presets
+The Stack selector becomes available after choosing a node. Selecting **All Stacks** will check and update every stack on that node during each run.
+
+### Schedule presets
For convenience, Sencho offers common schedule presets:
@@ -52,68 +55,83 @@ For convenience, Sencho offers common schedule presets:
| Weekly (Sunday 3 AM) | `0 3 * * 0` | Minimal disruption for stable stacks |
| Custom | User-defined | Any valid cron expression |
-## Filtering by Node
+When using a preset, a human-readable description of the schedule is shown below the selector. Custom cron expressions are validated and described in real time.
-In a multi-node environment, you can filter the policy list to show only policies targeting a specific node. Click the **calendar icon** on any node row in **Settings → Nodes** to jump directly to a filtered view. A filter bar at the top shows the active node, with a **Clear filter** button to return to the full list.
+## Filtering by node
-## Managing Policies
+In a multi-node environment, you can filter the policy list to show only policies targeting a specific node. Click the **calendar icon** on any node row in **Settings > Nodes** to jump directly to a filtered view. A filter badge at the top shows the active node, with a **Clear filter** button to return to the full list.
-Each policy in the list shows:
+## Managing policies
-- **Name** and target stack
-- **Schedule** in human-readable form (e.g., "Every 6 hours")
-- **Status** — enabled or disabled
-- **Last run** — when it last executed and whether it succeeded
-- **Next run** — when it will execute next
+The policy list is displayed as a table with the following columns:
-### Available Actions
+| Column | Description |
+|--------|-------------|
+| **Name** | The policy name |
+| **Stack** | Target stack name, or "All Stacks" for wildcard policies |
+| **Schedule** | Human-readable description with the raw cron expression below |
+| **Status** | Last run result: **Success** (green), **Failed** (red), or "Never run" |
+| **Last Run** | Timestamp of the most recent execution |
+| **Next Run** | When the policy will next execute |
+| **Enabled** | Toggle switch to enable or disable the policy |
+| **Actions** | Action buttons (see below) |
-- **Toggle** — Enable or disable a policy without deleting it
-- **Run Now** — Trigger an immediate check-and-update cycle
-- **Edit** — Modify the policy name, target, or schedule
-- **Delete** — Permanently remove the policy
+### Available actions
-## Run History
+Each policy row has four action buttons:
-Click the clock icon on any policy to view its run history. Each entry shows:
+- **Run Now** (play icon) - Trigger an immediate check-and-update cycle without waiting for the next scheduled run
+- **Execution History** (clock icon) - Open the run history panel for this policy
+- **Edit** (pencil icon) - Modify the policy name, target, or schedule
+- **Delete** (trash icon) - Permanently remove the policy and all its execution history after confirmation
-- **Timestamp** — When the run started
-- **Status** — Success or failure
-- **Output** — Detailed log of what was checked and whether updates were applied
+## Run history
-This gives you full auditability over what changed and when.
+Click the clock icon on any policy to open the run history panel. The history is displayed as a table with the following columns:
-## How It Works
+| Column | Description |
+|--------|-------------|
+| **Time** | When the run started |
+| **Source** | Whether the run was triggered by the **Scheduler** or **Manual** (via Run Now) |
+| **Status** | Success, Failed, or Running |
+| **Duration** | How long the run took (in seconds) |
+| **Details** | Output summary or error message |
+
+Run history is paginated at 20 entries per page. You can export the full history as CSV using the download button in the panel header.
+
+## How it works
Under the hood, Auto-Update Policies are built on the same scheduling engine as [Scheduled Operations](/features/scheduled-operations). The key difference is that auto-update policies:
- Are available to **Skipper** tier (Scheduled Operations requires Admiral)
-- Always target a **stack** (not individual containers)
+- Can target a single **stack** or **all stacks** on a node
- Perform a **check-then-update** flow rather than a blind restart
-### The Check-Then-Update Flow
+### The check-then-update flow
-1. **Enumerate images** — Sencho lists all unique images used by containers in the target stack
-2. **Check digests** — For each image, Sencho compares the local `RepoDigests` against the remote registry manifest digest
-3. **Conditional update** — Only if at least one image has a newer version does Sencho run `docker compose up -d` to pull and recreate
-4. **Clear indicators** — After a successful update, the blue update indicator dot is automatically cleared
+1. **Enumerate images** - Sencho lists all unique pullable images used by containers in the target stack
+2. **Check digests** - For each image, Sencho compares the local digest against the remote registry manifest
+3. **Conditional update** - Only if at least one image has a newer version does Sencho run `docker compose up -d` to pull and recreate
+4. **Clear indicators** - After a successful update, the blue update indicator dot is automatically cleared
+5. **Notify** - A notification is dispatched informing you which stack was updated and which images changed
-If no updates are found, the run completes with a "No updates available" message and no containers are restarted.
+If no updates are found, the run completes with an "all images up to date" message and no containers are restarted.
-## Relationship to Image Update Detection
+## Relationship to image update detection
Sencho has two complementary features for keeping your images current:
| Feature | Purpose | Tier |
|---------|---------|------|
-| **Image Update Detection** | Passive — shows a blue dot on stacks with available updates | All tiers |
-| **Auto-Update Policies** | Active — automatically applies updates on a schedule | Skipper+ |
+| **Image Update Detection** | Passive: shows a blue dot on stacks with available updates | All tiers |
+| **Auto-Update Policies** | Active: automatically applies updates on a schedule | Skipper+ |
Image Update Detection runs in the background every 6 hours and highlights stacks that have newer images available. Auto-Update Policies take this a step further by automatically applying those updates based on your defined schedule.
-## Best Practices
+## Best practices
-- **Start with longer intervals** — Use "Daily at 3 AM" or "Weekly" for production stacks. Reserve shorter intervals for dev/staging environments.
-- **Pin critical images** — If a stack uses `image: postgres:16.2` (pinned tag), auto-update will only detect updates to that exact tag. Use floating tags like `postgres:16` if you want minor version updates.
-- **Monitor run history** — Check run history periodically to ensure updates are applying cleanly. Failed runs may indicate registry authentication issues or compose file problems.
-- **Combine with notifications** — Sencho sends alert notifications when auto-updates are applied, so you stay informed even when updates happen automatically.
+- **Start with longer intervals** - Use "Daily at 3 AM" or "Weekly" for production stacks. Reserve shorter intervals for dev/staging environments.
+- **Pin critical images** - If a stack uses `image: postgres:16.2` (pinned tag), auto-update will only detect updates to that exact tag. Use floating tags like `postgres:16` if you want minor version updates.
+- **Monitor run history** - Check run history periodically to ensure updates are applying cleanly. Failed runs may indicate registry authentication issues or compose file problems.
+- **Use "All Stacks" carefully** - Wildcard policies update every stack on the node. This is convenient for dev environments but may be too aggressive for production.
+- **Combine with notifications** - Sencho sends alert notifications when auto-updates are applied, so you stay informed even when updates happen automatically.
diff --git a/docs/features/dashboard.mdx b/docs/features/dashboard.mdx
index d26f64ce..49e2047f 100644
--- a/docs/features/dashboard.mdx
+++ b/docs/features/dashboard.mdx
@@ -1,60 +1,70 @@
---
title: Dashboard
-description: Real-time system stats, historical metrics, and a quick-start converter for your host machine.
+description: Real-time system stats, stack health, historical metrics, and recent alerts for your host machine.
---
-The **Home** tab is the first thing you see after logging in. It shows a live snapshot of your host's health alongside container activity across all stacks.
+The **Home** tab is the first thing you see after logging in. It provides a live overview of your node's health, resource usage, stack status, and recent alert activity.
-
+
-## Container stats
+## Health status bar
-The top row summarises container state at a glance:
+The top bar provides an at-a-glance health assessment for the active node. Sencho evaluates CPU, RAM, disk usage, exited containers, and unread error alerts to derive one of three states:
+
+| Status | Meaning |
+|--------|---------|
+| **Healthy** | All systems nominal. No resources above warning thresholds, no unread errors. |
+| **Degraded** | At least one resource is above 80%, or there are unread error alerts. |
+| **Critical** | At least one resource is above 90%, or there are exited containers with unread errors. |
+
+The bar also shows the active node name, the number of running containers, the current alert count, and a relative timestamp for the last data refresh.
+
+## Resource gauges
+
+Five compact cards display real-time host and container metrics:
| Card | What it shows |
|------|---------------|
-| **Active Containers** | Running containers - broken down as `N managed · N external` |
-| **Exited Containers** | Stopped or crashed containers |
-| **Docker Network** | Current inbound/outbound network throughput across all containers |
+| **CPU** | Current CPU usage percentage, core count, and a color-coded gauge bar |
+| **Memory** | RAM usage percentage, used/total in GB, and a gauge bar |
+| **Disk** | Disk usage percentage, used/total for the primary mount, and a gauge bar |
+| **Containers** | Active container count (hover to see managed vs. external breakdown) and exited count |
+| **Network** | Current RX (receive) and TX (transmit) throughput in bytes/second |
-**Managed** means the container belongs to a stack in your `COMPOSE_DIR`. **External** means it exists on the Docker host but was started outside Sencho (e.g. by another Compose project or `docker run`).
+Gauge bars turn yellow at 80% usage and red at 90%.
-## System stats
+## Stack health table
-The second row shows host-level resource usage, polled every few seconds:
+A table listing every stack in your `COMPOSE_DIR` with live status and resource usage:
-| Card | What it shows |
-|------|---------------|
-| **Host CPU** | Current CPU usage percentage and core count |
-| **Host RAM** | Used / total memory in GB and percentage |
-| **Host Disk** | Used / total disk space for the primary mount point |
+| Column | Description |
+|--------|-------------|
+| **Stack** | Stack name (derived from the directory name) |
+| **Status** | `UP` (running) or `DN` (exited) |
+| **CPU** | Aggregate CPU usage across all containers in the stack, normalized over host cores |
+| **Memory** | Total memory allocated by the stack's containers |
-When any value exceeds configured thresholds (set in **Settings → System Limits**), the card changes colour as a visual warning.
+Click any row to navigate directly to that stack's editor. Stacks are sorted with running stacks first, then alphabetically. If you have more than 8 stacks, the table paginates automatically.
## Historical metrics charts
-Two area charts display time-series data sampled at one-minute intervals, retained for up to 24 hours (configurable in **Settings → Developer**):
+Two area charts display time-series data sampled at one-minute intervals, retained for up to 24 hours:
-- **Normalized CPU Usage** - total CPU percentage across all managed containers, normalised over all host cores
-- **Normalized RAM Usage** - total memory allocated by managed containers, in GB
+- **CPU Usage** - normalized total CPU percentage across all managed containers over host cores
+- **RAM Usage** - total memory allocated by managed containers, in GB
-The x-axis shows time labels. Hover over a data point to see the exact value at that moment.
+Hover over a data point to see the exact value at that moment.
+
## Compose file editor
@@ -17,11 +17,13 @@ Click **Edit** to enter edit mode. Your changes are unsaved until you explicitly
### Save options
-| Button | What it does |
+The primary action button is **Save & Deploy**, which writes the file to disk and immediately runs `docker compose up -d`. A dropdown arrow next to it reveals additional options:
+
+| Action | What it does |
|--------|--------------|
-| **Save** | Writes the file to disk. Does not restart any containers. |
-| **Save & Deploy** | Writes the file, then immediately runs `docker compose up -d`. Use this to apply compose changes in one step. |
-| **Discard** | Reverts the editor to the last saved version. Unsaved changes are lost. |
+| **Save & Deploy** | Writes the file, then runs `docker compose up -d` to apply changes immediately. |
+| **Save Only** | Writes the file to disk without restarting any containers. |
+| **Discard Changes** | Reverts the editor to the last saved version. Unsaved changes are lost. |
## Environment file editor
@@ -35,34 +37,39 @@ The `.env` editor has the same save/discard controls as the compose editor. Chan
## Container panel
-The left panel lists all containers that belong to the selected stack. Each container shows:
+The left column lists all containers that belong to the selected stack under the **CONTAINERS** heading. Each container shows:
-- **Status badge** - `running`, `exited`, `starting`, or `unhealthy`
-- **Live stats** - CPU %, RAM usage, and network I/O updated every 1–2 seconds
-- **Port mappings** - host:container port pairs (if any)
+- **Status badge** - `running`, `exited`, `starting`, or `unhealthy` (hover for detailed status text)
+- **Live stats** - CPU %, RAM usage, and network I/O displayed inline, updated every few seconds
### Container actions
-Each container row has action buttons that appear on hover:
+Each container row has action buttons on the right side:
-| Button | Icon | What it does |
-|--------|------|--------------|
-| **Open App** | External link | Opens the container's web UI in a new tab (uses the first mapped port) |
-| **View Live Logs** | Scroll text | Opens a live log stream modal for that container (available when running) |
-| **Open Bash Terminal** | Terminal | Opens an interactive bash session inside the container (admin only, available when running) |
+| Button | What it does |
+|--------|--------------|
+| **Open App** | Opens the container's web UI in a new tab (uses the detected web port). Only shown when a port is mapped. |
+| **View Live Logs** | Opens a live log stream modal for that container. Only available when the container is running. |
+| **Open Bash Terminal** | Opens an interactive bash session inside the container. Admin only, only available when running. |
-
+
-### Log viewer
+## Embedded terminal
-The log viewer streams output from a single container in real-time using Server-Sent Events. Logs auto-scroll to the bottom as new lines arrive. Close the modal to stop the stream.
+Below the container list, an embedded terminal streams the combined output from the stack's containers. This provides a persistent view of container logs without opening a separate modal.
+
+The terminal supports search (find text within the log output) and export (download the current log buffer).
### Container terminal (exec)
-The terminal modal gives you an interactive bash shell inside the running container - equivalent to `docker exec -it
+
During creation, Sencho connects to each node in parallel:
-- **Local nodes** - reads files directly from the compose directory
-- **Remote nodes** - fetches files via the Distributed API proxy using the node's API token
+- **Local nodes** are read directly from the compose directory
+- **Remote nodes** are fetched via the Distributed API proxy using the node's API token
-If a remote node is offline or unreachable, it is **skipped gracefully**. The snapshot is still created with data from all reachable nodes, and skipped nodes are recorded with the reason for the failure.
+If a remote node is offline or unreachable, it is **skipped gracefully**. The snapshot still captures data from all reachable nodes, and skipped nodes are recorded with the reason for the failure.
+
+### Scheduled snapshots
+
+Fleet snapshots can also be created automatically on a recurring schedule. Navigate to the **Schedules** view, create a new scheduled operation, and select **Fleet Snapshot** as the action type. Scheduled snapshots appear in the snapshot list with a "Scheduled snapshot" prefix in their description.
## Browsing snapshots
+The snapshot list shows each snapshot in a table with the following columns:
+
+- **Date** - when the snapshot was taken
+- **Description** - your optional label, or a prefix like "Scheduled snapshot" for automated ones
+- **Scope** - how many nodes and stacks were captured (e.g. "2 nodes, 22 stacks")
+- **Warnings** - a warning icon with a count if any nodes were skipped, or "None"
+- **Actions** - **View** to open the detail view, and a delete button for admins
+
-
+
-The snapshot list shows:
-- **Date** - when the snapshot was taken
-- **Description** - your optional label
-- **Scope** - how many nodes and stacks were captured
-- **Warnings** - an indicator if any nodes were skipped
+## Snapshot detail view
-Click **View** to open the detail view, which presents a collapsible tree:
+Click **View** on any snapshot to open the detail view. The header card displays the snapshot's title (or "Untitled Snapshot"), who created it, when, and badge counts for nodes and stacks captured.
-```
-Node A (local)
- ├── traefik/
- │ ├── compose.yaml
- │ └── .env
- └── postgres/
- └── compose.yaml
-Node B (remote)
- └── grafana/
- ├── compose.yaml
- └── .env
-```
+Below the header, each node appears as a collapsible card. Expand a node to see its stacks, then expand a stack to see individual files. Each file has a **Preview** button that renders the file contents inline.
-Expand any file to preview its contents inline.
+
+
+
+
+If any nodes were unreachable during snapshot creation, a warning banner appears at the top of the detail view listing each skipped node and the reason it was skipped.
## Restoring from a snapshot
Admins can restore individual stacks from any snapshot:
1. Open a snapshot's detail view
-2. Find the stack you want to restore
-3. Click **Restore**
-4. Optionally check **Redeploy stack after restore** to immediately apply the restored configuration
-5. Confirm the action
+2. Expand the node and stack you want to restore
+3. Click **Restore** below the file list
+4. A confirmation dialog appears. Optionally check **Redeploy stack after restore** to immediately apply the restored configuration.
+5. Click **Restore** to confirm
+
+
+
+
Sencho writes the snapshot's files back to the target node:
-- **Local nodes** - files are written directly, and the current files are backed up first (creating a rollback point via the atomic deployment system)
-- **Remote nodes** - files are pushed via the Distributed API proxy
+- **Local nodes** have files written directly, with the current files backed up first (creating a rollback point via the atomic deployment system)
+- **Remote nodes** receive files via the Distributed API proxy
+
+## Page layout
+
+The Fleet page is divided into two tabs:
+
+- **Overview** - the main monitoring view described on this page
+- **Snapshots** - fleet-wide backup snapshots (covered in [Fleet Backups](/features/fleet-backups))
+
+The header shows **Fleet Overview** with a subtitle summarising the current state (e.g. "2 of 2 nodes online, 31 containers, 21 stacks"). Two buttons sit in the top-right corner:
+
+| Button | What it does |
+|--------|--------------|
+| **Check Updates** | Opens the Node Updates modal to view and apply Sencho version updates across your fleet (Pro) |
+| **Refresh** | Re-fetches data from all nodes. Shows a spinner while loading |
+
## Community features
Every Sencho installation gets the full fleet monitoring grid at no cost.
@@ -21,13 +35,18 @@ Each node appears as a card showing:
|------|-------------|
| **Status badge** | Online (green) or Offline (grayed out) |
| **Type badge** | `local` or `remote` |
+| **Version badge** | The node's Sencho version (e.g. `v0.38.0`). Remote nodes running older versions that cannot report their version show no version badge |
+| **Update available badge** | Orange pill shown when a newer Sencho version is available for this node |
+| **Critical badge** | Red pill shown when CPU or disk usage exceeds 90% |
| **Running containers** | Count of containers in `running` state |
| **Stopped containers** | Count of containers in `exited` state |
| **Stacks** | Total number of Compose stacks on the node |
-| **CPU usage** | Current percentage with colour-coded bar (green → amber → red) |
+| **CPU usage** | Current percentage with colour-coded bar (green to amber to red) |
| **RAM usage** | Used / total with percentage bar |
| **Disk usage** | Used / total with percentage bar |
+Nodes with an available Sencho update also show an **Update to vX.Y.Z** button at the bottom of the card.
+
Offline nodes are visually dimmed and show a "Node unreachable" placeholder instead of stats.
### Manual refresh
@@ -55,7 +74,7 @@ Four cards appear above the node grid, aggregating data across all online nodes:
### Auto-refresh
-Fleet data automatically refreshes every 30 seconds. A subtle indicator at the bottom of the page confirms this is active.
+Fleet data automatically refreshes every 30 seconds. A subtle indicator at the bottom of the page confirms this is active. When a node update is in progress, the refresh rate increases to every 5 seconds so you can watch status changes in near real-time.
### Search
@@ -82,36 +101,57 @@ Filter pills let you narrow the grid:
| Filter | Options |
|--------|---------|
-| **Status** | All · Online · Offline |
-| **Type** | All Types · Local · Remote |
+| **Status** | All, Online, Offline |
+| **Type** | All Types, Local, Remote |
| **Critical Only** | Show only nodes with CPU or disk above 90% |
+| **Tags** | Filter by stack labels assigned to nodes |
A "Clear filters" button appears when filters hide all nodes.
### Stack drill-down
-Click **Stack details** on any online node card to expand the stack list. Each stack shows a count of its containers.
+Click **Stack details** on any online node card to expand the stack list. The button shows the total stack count (e.g. "18 stacks"). Each stack in the list shows a container count badge.
Click a stack name to expand it further and see individual containers with:
- Container name
- State badge (running, exited, restarting)
-- Uptime (e.g. "Up 43 hours")
+- Image name (e.g. `linuxserver/plex:latest`)
+- Uptime (e.g. "Up 4 days")
-Hover over any container row to reveal an **Open in editor** button that navigates you directly to that node's stack editor.
+Hover over any container row to reveal an **Open in editor** button that navigates you directly to that stack's editor on the corresponding node.
-
+
### Critical node detection
Nodes with CPU or disk usage above 90% automatically receive a red **Critical** badge. Combined with the **Critical Only** filter, this lets you quickly triage overloaded servers.
+### Node Updates
+
+Click **Check Updates** in the header to open the Node Updates modal. This lets you manage Sencho version updates across your entire fleet from one place.
+
+
+
+
+
+The modal shows:
+
+- **Summary cards** at the top: counts of nodes that are Up to date, have updates Available, are currently Updating, or have Failed
+- **Gateway version** label showing your primary instance's version
+- **Filter** search box to find specific nodes
+- **Node table** with columns: Node name, Type, Current version, Latest version, and Status (either an "Up to date" badge or an "Update" button)
+- **Recheck** button to re-scan for available updates
+- **Update All** button to trigger updates on all remote nodes that have a pending update
+
+When you click **Update** on a remote node, Sencho sends the update command and the node restarts with the new version. If you update the local node, a reconnection overlay appears while your primary instance restarts.
+
---
## How fleet data is fetched
-Fleet View queries all registered nodes in parallel. Each node responds independently - one slow or offline node does not block the others. Local node data comes from the Docker socket and system stats directly. Remote node data is fetched over the Distributed API proxy using each node's Bearer token.
+Fleet View queries all registered nodes in parallel. Each node responds independently; one slow or offline node does not block the others. Local node data comes from the Docker socket and system stats directly. Remote node data is fetched over the Distributed API proxy using each node's Bearer token.
@@ -11,48 +11,60 @@ The **Logs** tab aggregates output from all running containers into a single scr
## Log format
-Each line shows:
+Each line displays:
-- **Timestamp** - when the log line was emitted
-- **Stack name** - the Compose stack the container belongs to (colour-coded)
-- **Container name** - the specific container
-- **Level** - `INFO`, `WARN`, or `ERROR` (where detectable)
-- **Message** - the raw log output
+- **Timestamp** - when the log line was emitted (local time, 12-hour format)
+- **Container name** - the specific container that produced the line, shown in brackets
+- **Level** - `INFO`, `WARN`, or `ERROR` (where detectable), colour-coded for quick scanning
+- **Message** - the raw log output, tinted red for `STDERR` lines
+
+`WARN` lines appear in amber, `ERROR` lines in red, and `INFO` lines in green so you can spot problems at a glance.
## Streaming modes
-Sencho supports two modes for fetching logs, switchable via the **Developer mode** toggle:
+Sencho supports two modes for fetching logs, controlled by the **Developer Mode** toggle in **Settings → Developer**:
| Mode | How it works | Best for |
|------|-------------|----------|
-| **Standard** (default) | Polls all containers every N seconds (configurable in Settings → Developer) | General use; lower overhead |
-| **Developer mode** | Server-Sent Events stream; logs arrive as they are emitted | Debugging; watching a specific event in real-time |
+| **Standard** (default) | Polls all containers on a configurable interval | General use; lower overhead |
+| **Developer mode** | Server-Sent Events (SSE) stream; logs arrive as they are emitted | Debugging; watching events in real time |
-The default polling interval is 5 seconds. You can change it to 1, 3, 5, or 10 seconds in **Settings → Developer → Global Logs Refresh Rate**.
+When Developer Mode is active, a pulsing green **● LIVE** indicator appears in the toolbar to confirm the SSE stream is connected.
+
+The default polling interval is 5 seconds. You can change it to 1, 3, 5, or 10 seconds in **Settings → Developer → Standard Log Polling Rate**. This setting is disabled while Developer Mode is active because SSE streaming replaces polling entirely.
## Filtering logs
-Use the controls above the log panel to narrow what you see:
+Use the controls in the toolbar above the log panel to narrow what you see:
| Control | What it does |
|---------|-------------|
-| **Stack filter** | Multi-select dropdown - show only logs from chosen stacks |
-| **Stream** | `ALL` / `STDOUT` / `STDERR` - filter by output stream |
-| **Search** | Full-text filter on the message field (case-insensitive) |
+| **Search** | Full-text filter across the message, container name, and stack name (case-insensitive) |
+| **Stacks** | Checkbox dropdown; select one or more stacks to show only their containers' logs. Displays "All" when nothing is selected. |
+| **All Streams** | Filter by output stream: `All Streams`, `STDOUT`, or `STDERR` |
-Filters combine - you can show only `STDERR` from a specific stack while searching for a keyword.
+Filters combine. For example, you can show only `STDERR` from a specific stack while searching for a keyword.
## Controls
| Button | What it does |
|--------|-------------|
-| **Clear** | Clears the current log buffer in the UI (does not delete logs from Docker) |
-| **Auto-scroll** toggle | When enabled, the view scrolls to the bottom as new lines arrive |
+| **Clear** (trash icon) | Clears the current log buffer in the UI. Does not delete logs from Docker. |
+| **Download** (download icon) | Exports the currently filtered logs as a `.txt` file. The filename includes a timestamp for easy identification. |
-## Capacity limits
+## Auto-scroll
-To avoid browser memory issues, the log view keeps a maximum of **2,000 entries** in memory at a time. Older entries are dropped as new ones arrive. For deeper investigation, use the [per-container log viewer](/features/editor#log-viewer) in the Editor tab, or `docker compose logs` directly from the [Host Console](/features/host-console).
+The log view automatically scrolls to the bottom as new lines arrive. If you scroll up to inspect older entries, auto-scroll pauses so you are not pulled away. Scrolling back to the bottom re-enables it.
+
+## Display limits
+
+To keep the browser responsive, the log viewer enforces two capacity limits:
+
+- **Memory buffer** - a maximum of 2,000 log entries are held in memory at a time. Older entries are dropped as new ones arrive.
+- **Rendered rows** - only the most recent 300 entries from the filtered set are rendered as DOM nodes. If more entries match, a notice appears at the top of the log area showing how many entries exist versus how many are displayed.
+
+For deeper investigation, use the [per-container log viewer](/features/editor#log-viewer) in the Editor tab, or `docker compose logs` directly from the [Host Console](/features/host-console).
+
## What it is
-The host console spawns a real PTY (pseudo-terminal) process on the Sencho host using `node-pty`. Your keystrokes are sent over a WebSocket and the terminal output is streamed back in real-time. The terminal emulator is [xterm.js](https://xtermjs.org/), the same engine used by VS Code's built-in terminal.
+The Host Console gives you a real terminal session on the Sencho host, streamed to your browser over a WebSocket. It supports:
-Features:
- Full colour and cursor support
-- Tab completion (via the host shell)
-- Scrollback buffer (10,000 lines)
+- Tab completion (provided by the host shell)
- Automatic terminal resizing when you resize the browser window
- Copy and paste
+- Cursor blinking
## Opening the console
-Click **Console** in the top navigation bar. The session starts immediately in the stack's working directory if a stack is selected, otherwise in the `COMPOSE_DIR` root.
+Click **Console** in the top navigation bar. The session starts immediately in the `COMPOSE_DIR` root. If a stack is selected in the sidebar, the terminal opens directly inside that stack's directory instead.
-The header shows the connection status (**Connected** in green) and which node the console is attached to.
+The header bar shows:
-Click **Close Console** to end the session and terminate the shell process on the host.
+- **Host Console** label with a terminal icon
+- The active **node name** (e.g. "Local" or a remote node)
+- The **stack name** in parentheses, if one is selected
+- A green **Connected** badge while the WebSocket session is active
+- A **Close Console** button on the right to end the session
+
+Clicking **Close Console** terminates the shell process on the host and disconnects the WebSocket.
## Shell type
-The shell depends on the host OS:
-- **Linux/macOS hosts:** `bash` or `sh`
-- **Windows hosts (Docker Desktop):** PowerShell (as shown in the screenshot - the console opens inside the Sencho container's Windows environment)
+The shell depends on the host operating system:
+
+- **Linux / macOS hosts:** `bash` (falls back to `sh` if bash is not available)
+- **Windows hosts:** PowerShell
+
+In a typical Docker deployment, the Sencho container runs Linux, so the console opens a `bash` shell regardless of the Docker host's OS. The PowerShell prompt shown in the screenshot above reflects a native Windows development environment.
## Availability
-The Host Console is available to **admin** users on the **Admiral** tier. Access is enforced at every layer:
+The Host Console is available to **admin** users on the **Admiral** tier. If your instance is on the Community or Skipper tier, the Console tab does not appear in the navigation bar at all. Attempting to access the console endpoint directly without the correct license is rejected.
-- **UI:** The Console tab only appears in the navigation bar for Admiral-licensed admin users.
-- **API:** The `POST /api/system/console-token` endpoint returns `403 Forbidden` without an Admiral license.
-- **WebSocket:** The `/api/system/host-console` upgrade handler rejects connections without an Admiral license.
+## Security considerations
-### What Community and Skipper users see
+The Host Console has stricter access requirements than other features:
-If you are on the Community or Skipper tier, navigating to the Console view shows an upgrade prompt with a link to the [pricing page](https://sencho.io/pricing). The Console tab is hidden from the navigation bar entirely.
-
-## Security model
-
-The host console has a stricter authentication requirement than other features:
-
-- Requires a valid **browser session** (httpOnly cookie). Node-proxy tokens used for multi-node communication are explicitly blocked.
-- Each session is issued a short-lived **console token** (60-second TTL) before the WebSocket is established.
-- Access is restricted to the **Admiral** license tier, ensuring only teams with appropriate licensing can use interactive shell access.
-- Sensitive environment variables (names containing SECRET, PASSWORD, TOKEN, KEY, or CREDENTIAL, plus `DATABASE_URL`) are automatically stripped from the console environment so they cannot be read via `env` or `printenv`.
-- Because the console gives full shell access to the host, you should secure your Sencho instance with HTTPS and a strong password if it is exposed to a network.
+- **Browser sessions only.** API tokens used for multi-node communication cannot open a console session.
+- **Admiral license required.** The license check is enforced at every layer, not just in the UI.
+- **Sensitive environment variables are stripped.** Environment variables whose names suggest secrets (passwords, tokens, keys, credentials) and certain well-known database connection strings are automatically removed from the console environment. Running `env` or `printenv` inside the console will not reveal them.
+
## Activating your license
@@ -45,7 +61,7 @@ Clicking an upgrade button opens the Lemon Squeezy checkout in a new tab. After
2. Paste your key into the **License Key** field at the bottom of the page.
3. Click **Activate**.
-Sencho validates the key against Lemon Squeezy and activates Pro features immediately.
+Sencho validates the key against Lemon Squeezy and unlocks your tier's features immediately.
## License validation
@@ -53,11 +69,18 @@ Active licenses are re-validated every **72 hours** against the Lemon Squeezy AP
## Managing your subscription
-To manage your subscription (update payment method, view invoices, cancel, or switch plans), visit the [Lemon Squeezy customer portal](https://app.lemonsqueezy.com/my-orders) or use the link in your purchase confirmation email.
+Once your license is active, you can manage your subscription in two ways:
+
+- **From the License page:** Go to **Settings > License** and click **Manage Subscription**. This opens the Lemon Squeezy billing portal where you can update your payment method, view invoices, cancel, or switch plans.
+- **From the profile menu:** Click your profile icon in the top bar and select **Billing** for quick access to the same portal.
+
+
+
+
## Multi-node license enforcement
-If you manage multiple servers through Sencho's [multi-node feature](/features/multi-node), you only need a license on your **primary instance**. All remote nodes automatically inherit the primary's license tier for proxied requests — no per-node activation required.
+If you manage multiple servers through Sencho's [multi-node feature](/features/multi-node), you only need a license on your **primary instance**. All remote nodes automatically inherit the primary's license tier for proxied requests. No per-node activation is required.
For details on how this works, see [License enforcement across nodes](/features/multi-node#license-enforcement-across-nodes).
diff --git a/docs/features/multi-node.mdx b/docs/features/multi-node.mdx
index b85141ff..55f54474 100644
--- a/docs/features/multi-node.mdx
+++ b/docs/features/multi-node.mdx
@@ -3,7 +3,7 @@ title: Multi-Node Management
description: Connect multiple Sencho instances and manage all your servers from a single dashboard.
---
-Sencho's multi-node feature lets you manage Docker Compose stacks on multiple servers - all from the same browser tab. Each server runs its own Sencho instance, and your primary instance acts as a transparent proxy to the others.
+Sencho's multi-node feature lets you manage Docker Compose stacks on multiple servers, all from the same browser tab. Each server runs its own Sencho instance, and your primary instance acts as a transparent proxy to the others.
@@ -15,16 +15,16 @@ There is no central server. Each Sencho instance manages its own host independen
## The local node
-Your primary Sencho installation is always listed as **Local**. It is the default node, marked with a star, and cannot be deleted. All operations on the local node run directly against the host's Docker socket.
+Your primary Sencho installation is always listed as **Local**. It is the default node, marked with a star icon, and cannot be deleted. All operations on the local node run directly against the host's Docker socket.
## Adding a remote node
### Step 1: Generate a token on the remote machine
-On the **remote** Sencho instance (the server you want to add), open **Settings → Nodes** and click **Generate Token**. Copy the generated token - you'll only see it once.
+On the **remote** Sencho instance (the server you want to add), open **Settings → Nodes**. You will see a **Generate Node Token** section at the top of the page. Click **Generate Token**, then copy the token that appears. You will only see it once.
+
| Field | Description |
|-------|-------------|
-| **Name** | A display name (e.g. `prod-server`, `media-box`) |
-| **Type** | Select **Remote** |
-| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.20:3001`) |
+| **Name** | A display name (e.g. `Production VPS`, `media-box`) |
+| **Type** | Select **Remote** (or **Local** for an additional local Docker socket) |
+| **Sencho API URL** | The full HTTP/HTTPS URL of the remote instance (e.g. `http://192.168.1.50:3000`) |
| **API Token** | The token you generated in Step 1 |
+| **Compose Directory** | The root directory where compose stack folders live on the remote node (defaults to `/app/compose`) |
-Click **Create**. Sencho immediately tests the connection and shows the result.
+Click **Add Node**. Sencho immediately tests the connection and shows the result.
### Step 3: Verify connectivity
@@ -51,28 +52,16 @@ A successful connection shows the remote node as **Online** with a green badge.
- The API URL is correct (include the port if non-standard)
- The token was copied correctly without extra whitespace
-Click the **wifi icon** (test connection) on any node row at any time to re-check status.
-
-## The 1:1 path rule for remote nodes
-
-The Compose directory path matters on remote nodes too. When you register a remote node, its `COMPOSE_DIR` is whatever that remote instance was configured with. Make sure the remote Sencho's `COMPOSE_DIR` follows the [1:1 path rule](/getting-started/configuration#compose-directory-the-11-path-rule) on that remote host.
+Click the **wifi icon** (Test Connection) on any node row at any time to re-check status. A successful test shows a **Connection Details** panel below the table with information about the remote instance, including its OS, architecture, container count, and Sencho version.
## Switching between nodes
-The **node switcher** dropdown in the top-left of the sidebar shows the currently active node. Click it to switch to any registered node. All views - dashboard stats, stack list, editor, resources, logs - immediately reflect the selected node.
+The **node switcher** dropdown in the top-left of the sidebar shows the currently active node and its Sencho version. Click it to switch to any registered node. All views (dashboard stats, stack list, editor, resources, logs) immediately reflect the selected node.
-Node status indicators:
-
-| Indicator | Meaning |
-|-----------|---------|
-| Green dot | Node is reachable and responding |
-| Red dot | Node is unreachable |
-| Gray dot | Status not yet checked |
-
## Per-node scheduling and update indicators
The Nodes table surfaces scheduling and update status at a glance for each node:
@@ -86,7 +75,7 @@ The Nodes table surfaces scheduling and update status at a glance for each node:
| **Schedules** | Number of active scheduled tasks targeting this node, plus a relative countdown to the next run (e.g. "next 2h") |
| **Updates** | Whether auto-update policies are enabled ("Auto" badge), and how many stacks have pending image updates (pulsing blue dot with count) |
-Click the **calendar icon** on any node row to jump directly to the Schedules view filtered to that node. From there you can create, edit, or manage scheduled tasks scoped to the selected node. The filter bar shows which node you're viewing, with a **Clear filter** button to return to the full list.
+Click the **calendar icon** on any node row to jump directly to the Schedules view filtered to that node. From there you can create, edit, or manage scheduled tasks scoped to the selected node. The filter bar shows which node you are viewing, with a **Clear filter** button to return to the full list.
When a node is deleted, all scheduled tasks and update status data associated with it are automatically cleaned up.
@@ -96,17 +85,17 @@ When you have a paid license (Skipper or Admiral) on your primary instance, all
### How it works
-Your primary Sencho instance asserts its license tier to remote nodes on every proxied request. Remote nodes trust this assertion because it arrives alongside a valid node proxy token — the same token you configured when adding the node. No additional configuration is required.
+Your primary Sencho instance asserts its license tier to remote nodes on every proxied request. Remote nodes trust this assertion because it arrives alongside the valid node token you configured when adding the node. No additional configuration is required.
This means:
-- **Paid primary → remote nodes**: Pro and Admiral features work on all remote nodes, governed by the primary instance's license.
-- **Community primary → remote nodes**: Pro-gated features are blocked on remote nodes, even if a remote node has its own paid license. The primary's tier is authoritative for proxied requests.
+- **Paid primary → remote nodes**: Skipper and Admiral features work on all remote nodes, governed by the primary instance's license.
+- **Community primary → remote nodes**: Paid features are blocked on remote nodes, even if a remote node has its own paid license. The primary's tier is authoritative for proxied requests.
- **Direct access to a node**: If you access a remote Sencho instance directly (not through the primary), it uses its own local license tier as usual.
### Why this matters
-Without this trust chain, remote nodes would default to the Community tier and block Pro/Admiral features — even though the primary instance has a valid paid license. Distributed license enforcement eliminates this gap so your fleet behaves consistently regardless of which node you're operating on.
+Without this trust chain, remote nodes would default to the Community tier and block paid features, even though the primary instance has a valid license. Distributed license enforcement eliminates this gap so your fleet behaves consistently regardless of which node you are operating on.
+
## How routing works
When Sencho dispatches an alert (container crash, threshold breach, scheduled task failure), the routing engine:
-1. **Checks routing rules** — sorted by priority (lowest number first). If the alert's stack matches a rule's stack list, the notification is sent to that rule's channel.
-2. **Falls back to global agents** — if no routing rule matches (or the alert has no stack context, such as host resource warnings), the notification goes to the global channels configured in **Settings → Notifications**.
+1. **Checks routing rules** sorted by priority (lowest number first). If the alert's stack matches a rule's stack list, the notification is sent to that rule's channel.
+2. **Falls back to global channels** if no routing rule matches (or the alert has no stack context, such as host resource warnings). Global channels are configured in **Settings > Notifications**.
-Routing rules and global agents are independent — a matched route **replaces** the global dispatch for that alert, it does not send to both.
+Routing rules and global channels are independent. A matched route **replaces** the global dispatch for that alert; it does not send to both.
-## Setting up routing rules
+## Creating a routing rule
-1. Go to **Settings → Routing** (requires admin and Admiral tier)
-2. Click **Add Route**
-3. Fill in:
+1. Go to **Settings > Routing**
+2. Click **+ Add Route**
+3. Fill in the form:
+
+
+
+
| Field | Description |
|-------|-------------|
-| **Name** | A label for this rule (e.g., "Production to Discord") |
-| **Stacks** | One or more stacks this rule applies to. Use the dropdown to search and select. |
-| **Channel** | Choose Discord, Slack, or Webhook and provide the endpoint URL |
+| **Name** | A label for this rule (e.g. "Production to Discord") |
+| **Stacks** | One or more stacks this rule applies to. Use the searchable dropdown to find and add stacks. Selected stacks appear as removable pills below the dropdown. |
+| **Channel** | Choose Discord, Slack, or Webhook using the tab bar, then enter the endpoint URL. The URL must use HTTPS. |
| **Priority** | Lower values are evaluated first. Default is 0. |
| **Enabled** | Toggle the rule on or off without deleting it |
4. Click **Create** to save the rule
-
-
-
-
## Managing rules
-From the routing rules list, you can:
+Each routing rule appears as a card showing the rule name, channel type badge, assigned stack pills, the channel URL (truncated), and priority (when non-zero).
+
+From the rule card, you can:
- **Toggle** a rule on/off with the switch
-- **Test** a rule by clicking the lightning bolt icon — sends a test notification to the rule's channel
+- **Test** a rule by clicking the lightning bolt icon, which sends a test notification to the rule's channel
- **Edit** a rule by clicking the pencil icon
-- **Delete** a rule via the trash icon (with confirmation)
+- **Delete** a rule via the trash icon (with a confirmation dialog)
## Priority and matching
-Rules are evaluated in ascending priority order. If multiple rules match the same stack, **all matching rules fire** — this allows you to send the same alert to multiple channels (e.g., both a Slack channel and a custom webhook).
+Rules are evaluated in ascending priority order. If multiple rules match the same stack, **all matching rules fire**. This lets you send the same alert to multiple channels (e.g. both a Slack channel and a custom webhook).
-If any rule matches, global agents are skipped for that alert.
+If any rule matches, global channels are skipped for that alert.
## Fallback behavior
-Alerts without a stack context always use global agents. These include:
+Alerts without a stack context always use global channels. These include:
- Host CPU, memory, and disk threshold warnings
-- Docker data accumulation (janitor) notifications
+- Docker data accumulation notifications
-Stack-scoped alerts that don't match any routing rule also fall back to global agents.
+Stack-scoped alerts that do not match any routing rule also fall back to global channels.
## Example setup
@@ -72,24 +74,7 @@ Stack-scoped alerts that don't match any routing rule also fall back to global a
| Rule | Stacks | Channel | Priority |
|------|--------|---------|----------|
-| Prod to Slack | `prod-api`, `prod-web` | Slack: `https://hooks.slack.com/...` | 0 |
-| Staging to Discord | `staging-api`, `staging-web` | Discord: `https://discord.com/api/webhooks/...` | 10 |
+| Prod to Slack | `prod-api`, `prod-web` | Slack | 0 |
+| Staging to Discord | `staging-api`, `staging-web` | Discord | 10 |
-Any other stack alerts (e.g., `dev-tools`) would fall back to your global notification settings.
-
-## Troubleshooting
-
-
-## Testing connectivity
+## Managing registries
-After adding a registry, click the checkmark icon on the registry card to test the connection. Sencho will attempt to authenticate against the registry's `/v2/` endpoint and report success or failure.
+Each configured registry appears as a card showing:
+
+- **Name** and **type badge** (e.g., Docker Hub, GHCR, AWS ECR, Custom)
+- **Registry URL** (monospaced)
+- **Username**
+- **Secret status** (whether a secret is stored)
+- **AWS Region**, if the registry is an ECR type
+- **Created date**
+
+Each card has three action buttons:
+
+- **Test connection** (checkmark icon) to verify credentials
+- **Edit** (pencil icon) to modify the registry name, URL, credentials, or type
+- **Delete** (trash icon) to remove the registry after confirmation
+
+When editing a registry, you can leave the secret field blank to keep the existing stored secret. Only fill it in if you want to replace it.
+
+### Testing connectivity
+
+Click the checkmark icon on any registry card to test the connection. Sencho authenticates against the registry's `/v2/` endpoint and reports success or failure.
For ECR registries, the test verifies that the AWS credentials can successfully obtain an authorization token.
@@ -103,11 +122,11 @@ AWS ECR uses short-lived authentication tokens (valid for 12 hours) derived from
## Security
-- **Encrypted storage** - Registry secrets are encrypted at rest using AES-256-GCM, the same encryption used for remote node tokens and SSO secrets.
+- **Encrypted storage** - Registry secrets are encrypted at rest, using the same encryption layer as remote node tokens and SSO secrets.
- **No persistent Docker login** - Credentials are written to a temporary file for the duration of each operation and immediately deleted afterward.
- **Secrets never exposed** - The API never returns decrypted secrets. The UI shows only whether a secret is stored.
- **Audit trail** - Registry create, update, and delete operations are recorded in the [Audit Log](/features/audit-log).
-- **Admin-only access** - Only admin users with a Admiral license can manage registry credentials.
+- **Admin-only access** - Only admin users with an Admiral license can manage registry credentials.
## Multi-node behavior
diff --git a/docs/features/rbac.mdx b/docs/features/rbac.mdx
index 0fe0d8bf..546967aa 100644
--- a/docs/features/rbac.mdx
+++ b/docs/features/rbac.mdx
@@ -1,50 +1,97 @@
---
title: RBAC & User Management
-description: Role-based access control for Sencho - manage admin, viewer, deployer, and node admin accounts with scoped permissions.
+description: Role-based access control for Sencho - manage admin, viewer, deployer, node admin, and auditor accounts with scoped permissions.
---
+
+
+From the user table you can:
+
+- **Edit** a user by clicking the pencil icon
+- **Delete** a user by clicking the trash icon (with a confirmation dialog)
+
+You cannot delete your own account. The delete button is disabled for the currently logged-in user.
+
+### Creating a user
+
+Click **Add User** to open the creation form.
+
+
+
+
+
+| Field | Description |
+|-------|-------------|
+| **Username** | At least 3 characters. Letters, numbers, underscores, and hyphens only. |
+| **Role** | Select from the available roles (see below) |
+| **Password** | At least 8 characters |
+| **Confirm Password** | Must match the password field |
+
+On Admiral, all five roles appear in the role selector. On Skipper, only Admin and Viewer are available.
+
+
+
+
+
+### Editing a user
+
+Click the pencil icon on a user row to edit. You can change the username, role, and optionally set a new password. Leave the password fields blank to keep the existing password.
## Scoped permissions
-
-
-Admins can manage accounts in **Settings → Users**. From there you can:
-
-- **Create** a new user with a username, password, and role
-- **Edit** an existing user's password or role
-- **Delete** a user account
-
-When creating or editing a user on Admiral, you'll see all four role options in the role selector. On Skipper, only Admin and Viewer are available.
-
-
-
-
-
-## Managing scoped permissions
-
-When editing a user on Admiral, a **Scoped Permissions** section appears below the user form. Here you can:
-
-1. **View** the user's current scoped assignments
-2. **Add** a new scope by selecting a role, resource type (stack or node), and specific resource
-3. **Remove** an existing scope
-
-Each scoped assignment grants the specified role's permissions on the specified resource only.
-
## SSO auto-provisioning
-With a Admiral license, users can also be created automatically when they log in via SSO (LDAP, Google, GitHub, or Okta). SSO users appear in the Users list alongside local accounts. They are assigned a role based on identity provider group membership or claim mapping.
+With an Admiral license, users can also be created automatically when they log in via SSO (LDAP, Google, GitHub, or Okta). SSO users appear in the Users list alongside local accounts and are assigned a role based on identity provider group membership or claim mapping.
-SSO users cannot log in with a password — they must always authenticate through their identity provider. After SSO provisioning, an admin can add scoped permissions to SSO users just like local accounts.
+SSO users cannot log in with a password; they must always authenticate through their identity provider. After SSO provisioning, an admin can add scoped permissions to SSO users just like local accounts.
-To set up identity provider authentication, see [SSO Authentication →](/features/sso).
+To set up identity provider authentication, see [SSO Authentication](/features/sso).
## Migration from single-admin setup
-When you upgrade to Sencho Pro, your existing single-admin credentials are automatically migrated to the new users table. No manual action is required — your login continues to work as before, and your account is assigned the Admin role.
+When you upgrade to a Skipper or Admiral license, your existing single-admin credentials are automatically migrated. No manual action is required; your login continues to work as before, and your account is assigned the Admin role.
-## License tiers
+## Account limits by tier
| Tier | Admin accounts | Non-admin accounts | Intermediate roles | Scoped permissions |
|------|---------------|-------------------|-------------------|-------------------|
-| **Community** | 1 | 0 | ❌ | ❌ |
-| **Skipper** | 1 | 3 | ❌ | ❌ |
-| **Admiral** | Unlimited | Unlimited | ✅ | ✅ |
+| **Community** | 1 | 0 | No | No |
+| **Skipper** | 1 | 3 | No | No |
+| **Admiral** | Unlimited | Unlimited | Yes | Yes |
diff --git a/docs/features/remote-updates.mdx b/docs/features/remote-updates.mdx
index afe442f8..debcbbc1 100644
--- a/docs/features/remote-updates.mdx
+++ b/docs/features/remote-updates.mdx
@@ -1,12 +1,12 @@
---
-title: Remote updates
-description: Check for outdated nodes and trigger over-the-air updates from the Fleet View.
+title: Remote Updates
+description: Check for outdated nodes and trigger over-the-air Sencho updates from the Fleet View.
---
-Sencho can update remote nodes directly from the dashboard. When the gateway is running a newer version than a remote node, a one-click update pulls the latest image and recreates the container automatically.
+Sencho can update remote nodes directly from the dashboard. When your primary instance is running a newer version than a remote node, a one-click update pulls the latest image and recreates the container automatically.
+
-Each node row shows:
+The dialog includes:
-| Element | Meaning |
-|---------|---------|
-| Version label (e.g. `v0.31.0`) | The node's current Sencho version |
-| **Up to date** badge | Node is running the latest version |
-| **Update** button | A newer version is available — click to update |
-| **Updating** badge | The node is pulling and restarting |
+- **Summary cards** at the top showing counts of nodes that are Up to date, have updates Available, are currently Updating, or have Failed
+- **Gateway version** label showing your primary instance's version
+- **Filter** search box to find specific nodes by name or type
+- **Node table** with columns for name, type, current version, latest version, and status
+
+Each node's status column shows one of:
+
+| Status | Meaning |
+|--------|---------|
+| **Up to date** badge | Node is running the same version as the gateway |
+| **Update** button | A newer version is available; click to update |
+| **Updating** badge | The node is pulling the new image and restarting |
| **Updated** badge | The node came back online with the new version |
| **Timed out** badge | The node did not come back within 5 minutes |
+| **Failed** badge | The update command failed |
-Nodes running a version too old to report their version will show `pre-X.Y.Z` as the current version.
+Nodes that are too old to report their version show "unknown" in the current version column. These nodes are treated as outdated.
## Updating a single node
-Click the **Update** button next to any outdated node in the Node Updates dialog, or click the **Update to vX.Y.Z** button directly on the node card in Fleet View. For remote nodes, the update happens in the background. For the local (gateway) node, a confirmation dialog appears warning about the brief dashboard disconnection.
+You can trigger an update in two ways:
+
+- Click the **Update** button next to a node in the Node Updates dialog
+- Click the **Update to vX.Y.Z** button directly on a node card in Fleet View
+
+For remote nodes, the update happens in the background. The Fleet View automatically polls at a faster rate (every 5 seconds) while an update is in progress, so you can watch the status change in near real-time.
## Updating all nodes
-Click **Update All (N)** in the Node Updates dialog to trigger updates on all outdated remote nodes simultaneously. The local node is intentionally excluded from bulk updates — update it separately to avoid losing dashboard connectivity.
+Click **Update All (N)** in the footer of the Node Updates dialog to trigger updates on all outdated remote nodes simultaneously. The local node is excluded from bulk updates to avoid losing dashboard connectivity.
## Local node updates
-When updating the local (gateway) node:
+When you update the local (gateway) node:
-1. A confirmation dialog explains that the dashboard will briefly disconnect.
-2. After confirming, the server pulls the new image and restarts.
-3. A reconnecting overlay appears and polls the server every 3 seconds.
+1. A confirmation dialog appears explaining that the dashboard will briefly disconnect.
+2. After confirming, the server pulls the latest image and restarts.
+3. A reconnecting overlay appears and polls the server every few seconds.
4. The page automatically reloads when the server comes back.
If the server does not return within 5 minutes, a timeout message appears with a manual reload option.
-## Troubleshooting
+## What happens during an update
-### Node shows "self-update unavailable"
+When an update is triggered on a node, Sencho:
-The node is not running inside a Docker Compose-managed container, or the Docker socket is not mounted. Check that your `docker-compose.yml` includes the socket volume mount:
+1. Pulls the latest `saelix/sencho` image using Docker Compose
+2. Recreates the container with the new image
+3. The node goes briefly offline during the restart
-```yaml
-volumes:
- - /var/run/docker.sock:/var/run/docker.sock
-```
+The gateway monitors the remote node until it comes back online with the new version, then marks it as **Updated**.
-### Update times out
-
-The image pull may be slow on the remote host, or the container failed to restart. SSH into the remote host and check `docker logs sencho` for errors. You can also manually update by running:
-
-```bash
-docker compose pull && docker compose up -d
-```
-
-### Old node does not show update button
-
-Nodes running Sencho versions before v0.32.0 do not advertise the `self-update` capability. Update them manually first, then future updates can be triggered from the dashboard.
+If you run into issues with remote updates, see the [Troubleshooting](/operations/troubleshooting#remote-update-button-does-not-appear) page.
diff --git a/docs/features/resources.mdx b/docs/features/resources.mdx
index 4766d484..d1e4ed9c 100644
--- a/docs/features/resources.mdx
+++ b/docs/features/resources.mdx
@@ -11,7 +11,7 @@ The **Resources** tab gives you a full view of everything Docker is storing on y
## Docker disk footprint
-The stacked bar at the top visualises how your Docker disk usage is distributed:
+The stacked bar at the top visualizes how your Docker disk usage is distributed:
| Segment | Meaning |
|---------|---------|
@@ -23,16 +23,16 @@ Click any segment to automatically filter the tabs below to that category.
## Quick Clean panel
-Four prune buttons let you reclaim disk space immediately. By default they operate on **Sencho-managed resources only** - they will not touch external Docker projects.
+Four prune buttons let you reclaim disk space immediately. By default they operate on **Sencho-managed resources only**, so they will not touch external Docker projects.
| Button | What it removes |
|--------|----------------|
| **Prune Unused Images** | Images with no running containers in Sencho stacks |
| **Prune Unused Volumes** | Volumes not attached to any Sencho container |
| **Prune Dead Networks** | Networks not connected to any Sencho container |
-| **Purge Unmanaged Containers** | Containers Sencho doesn't recognise (started outside it) |
+| **Purge Unmanaged Containers** | Containers Sencho doesn't recognize (started outside it) |
-Each button has a **⋮ More options** menu that lets you target **all Docker resources** instead of Sencho-only. Use this carefully - it can affect other Compose projects running on the same host.
+The first three buttons have a **More options** dropdown that lets you target **all Docker resources** instead of Sencho-only. Use this carefully, as it can affect other Compose projects running on the same host. The "Purge Unmanaged Containers" button always targets unmanaged containers only.
A confirmation dialog appears before any destructive operation, showing a summary of what will be removed.
@@ -42,10 +42,11 @@ A confirmation dialog appears before any destructive operation, showing a summar
Lists all Docker images on the host with their ID, repository tag, size, and status.
-**Filter buttons:** `All` · `Managed` · `External`
+**Filter buttons:** `All` / `Managed` / `External`
**Status badges:**
-- `In Use` + stack name - image is actively used by a running container
+- `In Use` + stack name - image is actively used by a running container in a Sencho-managed stack
+- `In Use` + `External` - image is used by a container outside Sencho
- `Unused` - image has no running containers; safe to delete
Click the trash icon on any row to delete an individual image. Sencho will warn you if the image is in use.
@@ -54,7 +55,7 @@ Click the trash icon on any row to delete an individual image. Sencho will warn
Lists all Docker volumes. Columns: name, driver, mount point, size, and managed status.
-**Filter buttons:** `All` · `Managed` · `External`
+**Filter buttons:** `All` / `Managed` / `External`
@@ -110,9 +111,9 @@ Switch to the **Topology** view to see an interactive graph of your Docker netwo
-- **Network nodes** — Dashed border cards showing network name and driver, color-coded by status (managed, external, system)
-- **Container nodes** — Cards showing container name with IP addresses per network
-- **Edges** — Animated connections between networks and their containers
+- **Network nodes** - Dashed border cards showing network name and driver, color-coded by status (managed, external, system)
+- **Container nodes** - Cards showing container name with IP addresses per network
+- **Edges** - Animated connections between networks and their containers
- Pan, zoom, and drag nodes to explore the topology
- A mini map in the bottom-right provides an overview
@@ -124,4 +125,4 @@ Switch to the **Topology** view to see an interactive graph of your Docker netwo
Lists containers running on the host that are not part of any Sencho-managed stack. This includes containers started with `docker run`, or Compose projects outside your `COMPOSE_DIR`.
-This view is useful for identifying orphaned containers after a failed deployment or after moving stacks in and out of `COMPOSE_DIR`.
+This view is useful for identifying orphaned containers after a failed deployment or after moving stacks in and out of `COMPOSE_DIR`. A badge on the tab shows the count of unmanaged containers when any are detected.
diff --git a/docs/features/scheduled-operations.mdx b/docs/features/scheduled-operations.mdx
index dda380ec..aec992d9 100644
--- a/docs/features/scheduled-operations.mdx
+++ b/docs/features/scheduled-operations.mdx
@@ -10,7 +10,7 @@ description: Automate recurring Docker operations like stack restarts, fleet sna
## Overview
-Scheduled Operations lets you automate recurring maintenance tasks across your infrastructure. Define a cron schedule, choose an action, and Sencho handles the rest - including a full execution history log so you always know what ran and when.
+Scheduled Operations lets you automate recurring maintenance tasks across your infrastructure. Define a cron schedule, choose an action, and Sencho handles the rest, including a full execution history log so you always know what ran and when.
@@ -20,29 +20,45 @@ Scheduled Operations lets you automate recurring maintenance tasks across your i
| Action | Target | Description |
|--------|--------|-------------|
-| **Restart Stack** | A specific stack (or specific services within it) on a specific node | Restarts all or selected containers in the stack via the Docker Engine API |
+| **Restart Stack** | A specific stack (or specific services within it) on a specific node | Restarts all or selected containers in the stack |
| **Fleet Snapshot** | All nodes | Creates a fleet-wide backup of all compose files and `.env` files |
-| **System Prune** | A specific node (or the default node) | Prunes selected resources, optionally filtered by Docker label |
+| **System Prune** | The default node | Prunes selected resources, optionally filtered by Docker label |
## Creating a Scheduled Task
1. Navigate to the **Schedules** tab in the top navigation bar (visible to Admiral admins).
2. Click **New Schedule**.
3. Fill in the form:
- - **Name** - a descriptive label (e.g. "Nightly staging restart").
- - **Action** - choose Restart Stack, Fleet Snapshot, or System Prune.
- - **Stack / Node** - if you chose Restart Stack, select the target stack and the node it runs on.
- - **Services** - optionally select specific services within the stack to restart. Leave empty to restart all services.
- - **Prune Targets** - if you chose System Prune, select which resources to prune (containers, images, networks, volumes). All are selected by default.
- - **Label Filter** - optionally filter prune operations to resources matching a specific Docker label (e.g. `com.docker.compose.project=mystack`).
- - **Cron Expression** - standard 5-field cron format. A human-readable preview appears below the input.
- - **Enabled** - toggle the task on or off.
+ - **Name**: A descriptive label (e.g. "Nightly staging restart").
+ - **Action**: Choose Restart Stack, Fleet Snapshot, or System Prune. The form fields below change based on your selection.
+ - **Node**: (Restart Stack only) Select the node where the target stack runs.
+ - **Stack**: (Restart Stack only) Select the stack to restart. Becomes available after choosing a node.
+ - **Services**: (Restart Stack only) Optionally select specific services within the stack to restart. Leave empty to restart all services.
+ - **Prune Targets**: (System Prune only) Select which resources to prune: containers, images, networks, volumes. All are selected by default.
+ - **Label Filter**: (System Prune only) Optionally filter prune operations to resources matching a specific Docker label (e.g. `com.docker.compose.project=mystack`).
+ - **Cron Expression**: Standard 5-field cron format. A human-readable preview appears below the input.
+ - **Enabled**: Toggle the task on or off.
4. Click **Create**.
+## Task List
+
+The task list is displayed as a table with the following columns:
+
+| Column | Description |
+|--------|-------------|
+| **Name** | The task name |
+| **Action** | Task type badge: Restart Stack, Fleet Snapshot, or System Prune |
+| **Target** | Stack name (with selected services, if any), or the target type for non-stack actions |
+| **Schedule** | Human-readable description with the raw cron expression below |
+| **Status** | Last run result: **Success** (green), **Failed** (red), or "Never run" |
+| **Next Run** | When the task will next execute |
+| **Enabled** | Toggle switch to enable or disable the task |
+| **Actions** | Action buttons (see [Managing Tasks](#managing-tasks)) |
+
## Granular Targeting
### Per-Service Restart
@@ -98,10 +114,14 @@ When creating a new task while a node filter is active, Sencho pre-selects that
## Managing Tasks
-- **Enable/Disable** - Use the toggle switch in the task list to pause or resume a schedule without deleting it.
-- **Run Now** - Click the play icon to immediately execute a task. This works even on disabled tasks, allowing you to test a schedule before enabling it. Manual runs are labeled "Manual" in the execution history.
-- **Edit** - Click the pencil icon to update the task name, schedule, or target.
-- **Delete** - Click the trash icon to permanently remove the task and all its execution history.
+Each task row has four action buttons:
+
+- **Run Now** (play icon): Immediately execute the task without waiting for the next scheduled run. Manual runs are labeled "Manual" in the execution history.
+- **Execution History** (clock icon): Open the run history panel for this task.
+- **Edit** (pencil icon): Modify the task name, action, target, or schedule.
+- **Delete** (trash icon): Permanently remove the task and all its execution history after confirmation.
+
+Use the **Enabled** toggle switch in the task list to pause or resume a schedule without deleting it.
## Failure Notifications
@@ -117,20 +137,20 @@ To configure notification channels, go to **Settings > Notifications**.
## Execution History
-Click the history icon on any task to view its execution log. Each entry shows:
+Click the clock icon on any task to open the run history panel. The history is displayed as a table with the following columns:
-- **Timestamp** - when the task ran.
-- **Source** - whether the run was triggered by the scheduler or manually via Run Now.
-- **Status** - success or failure.
-- **Duration** - how long the execution took.
-- **Details** - output message or error description.
+| Column | Description |
+|--------|-------------|
+| **Time** | When the run started |
+| **Source** | Whether the run was triggered by the **Scheduler** or **Manual** (via Run Now) |
+| **Status** | Success, Failed, or Running |
+| **Duration** | How long the run took (in seconds) |
+| **Details** | Output summary or error message |
+
+Run history is paginated at 20 entries per page. You can export the full history as CSV using the download button in the panel header.
Execution history is retained for 30 days.
-### Exporting History
-
-Click the download icon in the top-right corner of the execution history panel to export the full history as a CSV file. The export includes all runs within the 30-day retention window — not just the current page.
-
diff --git a/docs/features/sso.mdx b/docs/features/sso.mdx
index 9772ad31..424d00fa 100644
--- a/docs/features/sso.mdx
+++ b/docs/features/sso.mdx
@@ -81,23 +81,45 @@ SSO can be configured two ways:
### Via Settings UI
-Admins can manage SSO providers in **Settings → SSO**. Each provider has:
+Admins can manage SSO providers in **Settings → SSO**. Each provider is displayed as a collapsible card with:
-- An **enable/disable** toggle
-- Provider-specific configuration fields
+- An **enable/disable** toggle and an **Active** badge when enabled
+- Provider-specific configuration fields (expand the card to configure)
+- A **Save** button to persist changes
- A **Test Connection** button to verify connectivity before saving
+- A **Remove** button to delete an existing provider configuration
-Expand a provider card to configure it. Here's the LDAP configuration form:
+Expand a provider card to configure it. The LDAP configuration form includes:
+
+| Field | Description |
+|-------|-------------|
+| **Server URL** | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://...` for TLS) |
+| **Bind DN** | Service account DN used to search the directory |
+| **Bind Password** | Service account password |
+| **Search Base** | Base DN for user searches (e.g., `ou=users,dc=example,dc=com`) |
+| **Search Filter** | LDAP filter template using `{{username}}` as placeholder |
+| **Admin Group DN** | DN of the group whose members receive the Admin role |
+| **Default Role** | Role assigned to users not in the admin group (Viewer or Admin) |
+| **Verify TLS certificate** | Toggle to enable or disable TLS certificate verification |
-And an OIDC provider (Google) configuration form:
+OIDC providers (Google, GitHub, Okta) share a common configuration form:
+
+| Field | Description |
+|-------|-------------|
+| **Issuer URL** | (Okta only) Your Okta issuer URL |
+| **Client ID** | OAuth client ID from your identity provider |
+| **Client Secret** | OAuth client secret |
+| **Admin Claim** | JWT claim name inspected for role mapping (e.g., `groups`) |
+| **Admin Claim Value** | Value within the claim that grants Admin (e.g., `sencho-admins`) |
+| **Default Role** | Role assigned when no claim mapping matches (Viewer or Admin) |
@@ -114,6 +136,7 @@ Environment variables are useful for initial deployment or infrastructure-as-cod
| Variable | Default | Description |
|----------|---------|-------------|
| `SSO_LDAP_ENABLED` | `false` | Enable LDAP authentication |
+| `SSO_LDAP_DISPLAY_NAME` | `LDAP` | Label shown on the login button (e.g., "Corporate AD") |
| `SSO_LDAP_URL` | - | LDAP server URL (e.g., `ldap://ldap.example.com:389` or `ldaps://ldap.example.com:636`) |
| `SSO_LDAP_BIND_DN` | - | Service account DN for searching users |
| `SSO_LDAP_BIND_PASSWORD` | - | Service account password (encrypted at rest in the database) |
@@ -171,23 +194,8 @@ If not set, Sencho auto-detects the URL from the request's `Host` header and pro
- **PKCE** - All OIDC flows use `code_challenge_method=S256` to prevent authorization code interception
- **State parameter** - A cryptographic random value protects against CSRF attacks on the OAuth callback
-- **Encrypted secrets** - LDAP bind passwords and OIDC client secrets are encrypted at rest with AES-256-GCM
+- **Encrypted secrets** - LDAP bind passwords and OIDC client secrets are encrypted at rest
- **No local password** - SSO users are created with an unusable password hash. They cannot bypass SSO by using the password login form
- **Admin-only configuration** - Only Admiral administrators can enable or configure SSO providers
-## Troubleshooting
-
-### LDAP connection refused
-Verify the LDAP server is reachable from the Sencho container. If LDAP is on the host machine, use `host.docker.internal` (Docker Desktop) or the host's LAN IP address - not `localhost`.
-
-### TLS certificate errors
-If your LDAP server uses a self-signed certificate, set `SSO_LDAP_TLS_REJECT_UNAUTHORIZED=false`. For production, install a trusted certificate instead.
-
-### OAuth callback URL mismatch
-The redirect URI registered in your identity provider must exactly match what Sencho sends. Check:
-1. `SSO_CALLBACK_URL` is set to your external URL (e.g., `https://sencho.example.com`)
-2. The callback URL in your provider's settings is `https://sencho.example.com/api/auth/sso/oidc/
+
## Creating labels
### From Settings
-1. Open **Settings → Labels**
-2. Click **New Label**
-3. Enter a name and pick a color
+1. Open **Settings > Labels**
+2. Click **+ New Label**
+3. Enter a name and pick a color from the 10 available swatches
4. Click **Create**
-
+
+
+
+The label list shows each label's color, name, and how many stacks are currently assigned to it. Hover over a label row to reveal **Edit** and **Delete** buttons.
+
+
+
### From the stack context menu
-1. Right-click any stack in the sidebar (or click the three-dot menu)
-2. Hover over **Labels**
-3. Click **Manage labels...** at the bottom of the sub-menu
-4. Create your label from the Settings panel that opens
+You can also create labels without leaving the sidebar:
+
+1. Right-click any stack in the sidebar
+2. Hover over **Labels** to open the submenu
+3. Click **Manage labels...** at the bottom to open the Labels section in Settings
+
+### From the three-dot menu
+
+1. Click the three-dot menu on any stack row in the sidebar
+2. Click **Labels** to open the label assignment popover
+3. Click **Create new label** at the bottom of the popover to create and assign in one step
## Assigning labels to stacks
-1. Right-click a stack in the sidebar (or click the three-dot menu)
-2. Hover over **Labels** to open the sub-menu
-3. Click a label to toggle it on or off
+There are two ways to assign labels:
+
+**Right-click context menu:** Right-click a stack in the sidebar, hover over **Labels**, and click a label to toggle it on or off. A checkmark indicates the label is currently assigned.
-
+
+**Three-dot menu popover:** Click the three-dot menu on a stack row, then click **Labels**. The popover shows all labels with checkmarks for assigned ones. Click any label to toggle it.
+
A stack can have multiple labels. Changes save immediately.
## Filtering by label
### Sidebar
-When you have labels, a pill bar appears between the search box and the stack list. Click a label pill to filter — only stacks with that label are shown. Click multiple pills to see stacks matching **any** of the selected labels. Click an active pill again to deselect it.
+When at least one label exists, a pill bar appears between the search box and the stack list. Click a label pill to filter: only stacks with that label are shown. Click multiple pills to see stacks matching **any** of the selected labels. Click an active pill again to deselect it.
-
+
### Fleet View
-Label filter pills appear in the Fleet View toolbar alongside the existing Status and Type filters. Selecting a label filters to nodes that contain stacks with that label.
+The **Tags** filter in the [Fleet View](/features/fleet-view) toolbar lets you filter nodes by stack labels. Selecting a label filters to nodes that contain stacks with that label.
## Bulk actions
@@ -69,28 +84,17 @@ Right-click a label pill in the sidebar to access bulk actions:
| **Stop all** | Stops every stack with that label |
| **Restart all** | Restarts every stack with that label |
+
+
+
+
A confirmation dialog shows which stacks will be affected before executing.
## Managing labels
-Open **Settings → Labels** to view, edit, and delete your labels.
+Open **Settings > Labels** to view, edit, and delete your labels.
-- **Edit**: Change a label's name or color
-- **Delete**: Removes the label and unassigns it from all stacks
+- **Edit**: Click the pencil icon on a label row to change its name or color
+- **Delete**: Click the trash icon to remove the label. A confirmation dialog explains that the label will be removed from all stacks. This cannot be undone.
-## Troubleshooting
-
-
@@ -42,26 +42,37 @@ Select a stack and click **Deploy** in the stack header. This runs `docker compo
## Controlling a running stack
-The stack header exposes four actions:
+The stack header shows different actions depending on whether the stack is running or stopped.
+
+**When running:**
| Button | Command | What it does |
|--------|---------|--------------|
| **Stop** | `docker compose stop` | Stops containers without removing them. State is preserved. |
| **Restart** | `docker compose restart` | Restarts all containers in the stack. |
| **Update** | `docker compose pull` + `up -d` | Pulls the latest image tags and recreates containers. |
+| **Rollback** | Restores backup | Reverts compose and env files to the pre-deploy snapshot and redeploys. Only shown when a backup exists (Pro). |
| **Delete** | `down --volumes` + removes files | Stops and removes containers and volumes, then deletes the stack directory. |
+**When stopped:**
+
+| Button | Command | What it does |
+|--------|---------|--------------|
+| **Start** | `docker compose up -d` | Starts the stack. |
+| **Update** | `docker compose pull` + `up -d` | Pulls the latest image tags and recreates containers. |
+| **Delete** | Removes files | Deletes the stack directory. |
+
-- **New stacks detected** — a success notification lists the newly discovered stack names, and they appear immediately in the sidebar
-- **Stacks removed from disk** — an informational notification lists stacks that are no longer present in the directory
-- **No changes** — a notification confirms no new stacks were found
+- **New stacks detected** - a success notification lists the newly discovered stack names, and they appear immediately in the sidebar
+- **Stacks removed from disk** - an informational notification lists stacks that are no longer present in the directory
+- **No changes** - a notification confirms no new stacks were found
-
+Go to **Settings > Webhooks** and click **Create Webhook**. Fill in:
-Open **Settings → Webhooks** and click **Create Webhook**. Fill in:
+
+
+
| Field | Description |
|-------|-------------|
| **Name** | A display name (e.g. "Deploy on push") |
| **Stack** | The target stack to act on |
-| **Action** | One of: `deploy`, `restart`, `stop`, `start`, `pull` |
+| **Action** | One of the actions listed below |
-After creation, Sencho shows the webhook secret **once**. Copy it immediately - it cannot be retrieved later.
+After creation, Sencho shows the webhook secret **once** in a green banner. Copy it immediately; it cannot be retrieved later. The webhook list only shows the last four characters of the secret.
-### Actions explained
+
+
+
+
+### Available actions
| Action | What it does |
|--------|-------------|
-| **Deploy** | Runs `docker compose down` then `docker compose up -d` (full redeploy) |
-| **Restart** | Runs `docker compose restart` |
-| **Stop** | Runs `docker compose stop` |
-| **Start** | Runs `docker compose start` |
-| **Pull** | Pulls latest images and recreates changed containers |
+| **Deploy (down + up)** | Stops and redeploys the entire stack |
+| **Restart** | Restarts the stack's running containers |
+| **Stop** | Stops the stack |
+| **Start** | Starts a stopped stack |
+| **Pull & Update** | Pulls the latest images and recreates changed containers |
+
+## Managing webhooks
+
+Each webhook appears as a card showing the webhook name, an action badge, a stack badge, the trigger URL with a copy button, and the masked secret.
+
+
+
+
+
+From the webhook card you can:
+
+- **Toggle** the webhook on or off with the switch
+- **Delete** the webhook with the trash icon
+- **Copy** the trigger URL with the clipboard button
+- **View execution history** by clicking **Recent executions**
## Triggering a webhook
-Send a POST request to the trigger URL with an HMAC-SHA256 signature:
+Send a `POST` request to the trigger URL shown on the webhook card. Include an HMAC-SHA256 signature in the `X-Webhook-Signature` header:
```
POST /api/webhooks/{id}/trigger
@@ -52,7 +71,7 @@ Content-Type: application/json
X-Webhook-Signature: sha256={hmac}
```
-The signature is computed as `HMAC-SHA256(request_body, webhook_secret)` and sent in the `X-Webhook-Signature` header with a `sha256=` prefix.
+The signature is computed as `HMAC-SHA256(request_body, webhook_secret)` and sent with a `sha256=` prefix.
### Example with curl
@@ -61,7 +80,7 @@ SECRET="your-webhook-secret"
BODY='{}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
-curl -X POST https://your-sencho.com/api/webhooks/3/trigger \
+curl -X POST https://your-sencho.example.com/api/webhooks/3/trigger \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: sha256=$SIGNATURE" \
-d "$BODY"
@@ -75,6 +94,8 @@ By default, the webhook executes the action configured at creation time. You can
{ "action": "restart" }
```
+Valid override values are: `deploy`, `restart`, `stop`, `start`, `pull`.
+
### Response
A successful trigger returns `202 Accepted` immediately. The action executes asynchronously in the background.
@@ -114,19 +135,20 @@ deploy:
## Execution history
-Each webhook tracks its last 100 executions. Click **Recent executions** on any webhook row to see:
+Each webhook tracks its recent executions. Click **Recent executions** on any webhook card to expand the history, which shows:
-- Status (success/failure)
-- Action performed
-- Execution time
-- Duration
-- Error message (if failed)
+- **Status** icon (green check for success, red X for failure)
+- **Action** that was performed
+- **Timestamp** of the execution
+- **Duration** in seconds
+- **Error message** if the execution failed
## Security
-- Webhook secrets are 64-character hex strings generated with `crypto.randomBytes(32)`
-- Signature validation uses `crypto.timingSafeEqual` to prevent timing attacks
-- Secrets are shown only once at creation - API responses return masked values
-- Signature verification uses the raw request bytes, not re-serialized JSON, to prevent serialization-induced mismatches
-- Webhook trigger endpoints are public (no session cookie required) but protected by HMAC signature validation
-- Each webhook targets a single stack - there is no way to execute arbitrary commands
+- Secrets are shown only once at creation. The webhook list displays only the last four characters.
+- Trigger endpoints do not require a session cookie, but are protected by HMAC signature validation.
+- Each webhook targets a single stack. There is no way to execute arbitrary commands through a webhook.
+
+
@@ -31,25 +31,26 @@ Click **Update Password** to apply. The change takes effect immediately; existin
**Scope:** Global
-Manage your Sencho Pro license from this section.
+Manage your Sencho license and subscription from this section.
| Element | Description |
|---------|-------------|
-| **Current tier** | Shows whether you are on Community or Pro, with a tier badge |
+| **Current tier** | Shows your current tier (Community, Skipper, or Admiral) with a tier badge |
| **Trial status** | If on a trial, shows remaining days |
-| **Customer / Plan / Key** | Displayed when a Pro license is active |
| **Renews** | Next renewal date for active subscriptions |
-| **Upgrade cards** | Dynamic plan cards with feature highlights and direct Lemon Squeezy checkout links. Community users see Skipper and Admiral; Skipper users see Admiral only |
-| **Activate** | Enter a license key to activate Pro |
-| **Manage Subscription** | Opens the billing portal (active Pro only) |
-| **Deactivate License** | Reverts to Community features (active Pro only) |
+| **Upgrade cards** | Dynamic plan cards with feature highlights and checkout links. Community users see Skipper and Admiral; Skipper users see Admiral only |
+| **Activate** | Enter a license key to activate your plan (visible when no license is active) |
+| **Manage Subscription** | Opens the Lemon Squeezy billing portal (Skipper / Admiral only) |
+| **Deactivate License** | Reverts to Community features (Skipper / Admiral only) |
+
+See [Licensing & Billing](/features/licensing) for the full walkthrough.
---
## Users
Professional tools for solo operators.