docs: tutorials batch 1 (#1656)
* docs: scaffold Tutorials tab and write enroll-a-remote-node Adds the Tutorials tab to docs.json with 15 stub pages across three groups (Fleet & nodes, Deploy & automate, Secure & integrate), and writes the first full tutorial: enrolling a remote node via Pilot Agent mode, verified end to end against a live control instance and a second host running an existing Jellyfin Compose stack. * docs: write Schedule an Operation tutorial * docs: fix MDX parse error in Schedule an Operation tutorial * docs: write Set Up SSO with Custom OIDC tutorial Registers an OAuth client in a self-hosted identity provider (Keycloak worked example), configures Sencho's Custom OIDC settings, tests the connection, and verifies a real end-to-end login with auto-provisioning from two independent surfaces. * docs: drop unused SSO tutorial screenshot sso-settings-empty.png isn't referenced by the tutorial content. * docs: write Set Up Fleet Federation tutorial Migrates a Blueprint-managed workload from one node to another using pin and cordon, with the confirm-before-mutate rollout in between. Corrects the published feature page's claim that pin requires the global admin role; the code gates cordon and pin identically, scoped to the target node. * docs: write Create and Approve a Blueprint tutorial Covers labeling a target node, authoring a stateless Blueprint, walking through the create-then-approve rollout flow, verifying from the Deployments tab and the audit log, and recovering from a port-conflict deploy failure. Cross-links with Move a Blueprint Deployment to a New Node in both directions. * docs: write Automatically Patch a Stack With an Auto-Update Label tutorial * docs: write Configure Auto-Heal Policies tutorial Adds the full step-by-step content for the Configure Auto-Heal Policies stub: an nginx+redis scenario stack, adding a service-scoped policy, and a live verification that breaks a container's healthcheck, confirms the policy restarts it, and recovers it. * docs: write Set Up Deploy Enforcement tutorial Covers configuring a block-on-deploy scan policy against a stack running a deliberately outdated nginx image, reading the block dialog, and overriding it as an admin with the bypass confirmed in the audit log. Includes a stack-pattern mismatch as the most likely first-time failure. * docs: write Configure Environment Guardrails tutorial Covers the Block deploy on missing required env vars guardrail end to end: deploy a Postgres stack with a required password, enable the guardrail, watch a real update get refused with a named-variable message, fix it, and verify from the Activity and Environment tabs. * docs: write Deploy a Stack Automatically From Your CI Pipeline tutorial * docs: write Catch and Fix a Container That's Drifted From Its Compose File tutorial Covers reading a real Drift finding after an out-of-band container change and resolving it by redeploying through Sencho. * docs: write Connect a Git Source tutorial * docs: write Push a Shared Environment File to Every Node tutorial Writes the Fleet Secrets tutorial: create a bundle, target nodes by label, read the push preview/results, verify via the audit log, and recover from a stack-name typo. Removes the three unwritten placeholder stubs (RBAC, Sencho Mesh, private registries) that had no scheduled content.
@@ -209,6 +209,40 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Tutorials",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Fleet & nodes",
|
||||
"pages": [
|
||||
"tutorials/enroll-a-remote-node",
|
||||
"tutorials/set-up-fleet-secrets",
|
||||
"tutorials/set-up-fleet-federation",
|
||||
"tutorials/configure-recovery-vault-backups"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deploy & automate",
|
||||
"pages": [
|
||||
"tutorials/create-and-approve-a-blueprint",
|
||||
"tutorials/connect-a-git-source",
|
||||
"tutorials/schedule-an-operation",
|
||||
"tutorials/configure-auto-update-policies",
|
||||
"tutorials/configure-auto-heal-policies",
|
||||
"tutorials/set-up-deploy-enforcement"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Secure & integrate",
|
||||
"pages": [
|
||||
"tutorials/set-up-sso",
|
||||
"tutorials/configure-environment-guardrails",
|
||||
"tutorials/set-up-a-webhook",
|
||||
"tutorials/resolve-stack-drift"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "API Reference",
|
||||
"openapi": "openapi.yaml",
|
||||
|
||||
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Automatically Restart a Container When It Goes Unhealthy
|
||||
sidebarTitle: Auto-restart an unhealthy container
|
||||
description: Add an Auto-Heal policy that restarts a container when its healthcheck fails or it crashes, then prove it fires against a real failure.
|
||||
---
|
||||
|
||||
Say a small internal service starts failing its healthcheck at 3 AM: a dependency hiccups, a worker wedges, whatever the cause. Nobody wants to be paged for a problem a restart would fix. This walks through adding an Auto-Heal policy to a two-service stack, `ops-status` (an nginx `web` service in front of a `redis` cache), so that if `web` stays unhealthy past a threshold you set, Sencho restarts it on its own, then confirms the policy actually fired by breaking the healthcheck for real and watching the restart happen.
|
||||
|
||||
This tutorial covers one Auto-Heal policy scoped to a single Compose service. It does not cover alert rules (a related but separate tab in the same sheet), stack-wide **All services** policies, or the crash-based healing path (no healthcheck required, triggered by a non-zero exit instead). See the [Auto-Heal Policies](/features/auto-heal-policies) feature page for the full picture, including the four safety rails and the multi-node behavior.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An account with edit access to this stack (admin by default, or a custom role granted `stack:edit` for this stack). Viewing existing policies is open to every signed-in role.
|
||||
- A running stack with at least one service that declares a Docker `HEALTHCHECK`. Auto-Heal's healthcheck-based path only sees containers that report a health status; a service with no `HEALTHCHECK` block never goes `unhealthy`, no matter how broken it is.
|
||||
- This tutorial uses a small stack called `ops-status`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8092:80"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost/ || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
cache:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
`web` has the healthcheck; `cache` doesn't need one for this tutorial. Deploy this stack (or adapt an existing one with a `HEALTHCHECK` block) before continuing.
|
||||
|
||||
<Note>
|
||||
Auto-Heal restarts the container in place; it does not recreate it. If the thing making a container unhealthy lives in its writable layer (a moved file, a corrupted local state), a restart alone won't fix it and the container will go unhealthy again on the next check. This matters for the verification step below.
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the stack's Monitor sheet on the Auto-heal tab">
|
||||
Right-click the `ops-status` stack in the sidebar (or focus it and press **H**) and select **Auto-Heal** in the **Inspect** group. The Monitor sheet opens directly on the **Auto-heal** tab, with **Active policies** showing `No auto-heal policies configured for this stack.`
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/sidebar-context-menu-auto-heal.png" alt="The ops-status stack's sidebar context menu with Alerts and Auto-Heal listed under Inspect, Auto-Heal highlighted with its H shortcut." />
|
||||
</Frame>
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/monitor-sheet-empty.png" alt="The Stack ops-status Monitor sheet on the Auto-heal tab, showing no active policies and the empty Add new policy form with Service, Unhealthy for, Cooldown, Max restarts per hr, and Auto-disable after fields." />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Scope the policy to the web service and set thresholds">
|
||||
In **Add new policy**, open the **Service** combobox and pick **web** instead of the default **All services**, since this policy should only watch the service that has a healthcheck. Set **Unhealthy for (minutes)** to `1` and **Cooldown (minutes)** to `1` so you don't have to wait long to see it fire; leave **Max restarts / hr** at `3` and **Auto-disable after (failures)** at `5`.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/add-policy-form-filled.png" alt="The Add new policy form filled in: Service web, Unhealthy for 1, Cooldown 1, Max restarts per hr 3, Auto-disable after failures 5." />
|
||||
</Frame>
|
||||
|
||||
In production you'd typically set **Unhealthy for** higher (5 minutes or more) so a brief blip doesn't trigger a restart. The 1-minute value here is only to make the next step observable without a long wait.
|
||||
</Step>
|
||||
<Step title="Add the policy and confirm it's active">
|
||||
Click **Add Policy**. A toast confirms `Policy added.`, and the policy now appears in **Active policies**: `web` with the summary `Unhealthy for 1 min · Cooldown: 1 min · Max 3/hr` and its **ON** toggle already enabled.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/active-policy-web.png" alt="Active policies showing one policy for the web service, Unhealthy for 1 min, Cooldown 1 min, Max 3 per hr, with the ON toggle enabled, a history chevron, and a delete icon." />
|
||||
</Frame>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Verify it worked
|
||||
|
||||
The policy is saved and enabled, but that alone doesn't prove it fires. Auto-Heal evaluates every 30 seconds in the background, and there's no manual "run now" button, so the only real proof is causing an actual failure and watching Sencho react to it.
|
||||
|
||||
If you have shell access to the host running this stack, break the `web` container's healthcheck on purpose:
|
||||
|
||||
```bash
|
||||
docker exec ops-status-web-1 mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index.html.bak
|
||||
```
|
||||
|
||||
Nginx now returns an error for every request, so the healthcheck's `wget --spider` fails. Within about a minute, the stack header and the `web` container both flip to **unhealthy**:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/container-unhealthy-triggered.png" alt="The ops-status stack showing RUNNING UNHEALTHY, with ops-status-web-1 marked unhealthy and nginx forbidden errors in the logs." />
|
||||
</Frame>
|
||||
|
||||
Give it another 30–60 seconds for the next evaluation tick, then reopen the Monitor sheet and expand the policy's history chevron. **Recent activity** shows a **Restarted** entry with the reason `Container unhealthy for 1 minute(s); auto-restarted.`:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/policy-recent-activity-restarted.png" alt="The web policy's Recent activity panel showing two Restarted entries for ops-status-web-1, each with the reason Container unhealthy for 1 minute(s); auto-restarted." />
|
||||
</Frame>
|
||||
|
||||
Because a restart doesn't recreate the container, the moved file is still missing after the restart, so the container goes unhealthy again and the policy keeps restarting it every cooldown window (you can see two **Restarted** entries above, roughly two minutes apart). Restore the file to let it actually recover:
|
||||
|
||||
```bash
|
||||
docker exec ops-status-web-1 mv /usr/share/nginx/html/index.html.bak /usr/share/nginx/html/index.html
|
||||
```
|
||||
|
||||
The next few healthchecks pass, and the container settles back to **healthy** on its own, with no further restarts needed:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/container-healthy-recovered.png" alt="The ops-status stack back to RUNNING HEALTHY, with ops-status-web-1 showing healthy status about a minute after restarting." />
|
||||
</Frame>
|
||||
|
||||
Check from a second, independent surface too: open the dashboard's **Stack Restarts (7d)** card. It shows an `ops-status` row tagged **AUTO-HEAL** with a restart count, confirming the restarts came from the policy and not a manual action:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-auto-heal-policies/dashboard-stack-restarts-autoheal.png" alt="The dashboard's Stack Restarts (7d) card showing an ops-status row with an AUTO-HEAL badge and a restart count of 2, and the Configuration Status card showing Auto-heal policies 1 / 1 active." />
|
||||
</Frame>
|
||||
|
||||
## If something goes wrong
|
||||
|
||||
**The policy never fires, and the container never shows a health word at all.** Compare the container card's status line: a container with a working healthcheck reads `up X minutes · healthy` (or `unhealthy`); a container with no `HEALTHCHECK` declared just reads `up X minutes`, with nothing after it. If your service is missing the health word entirely, Auto-Heal's healthcheck path has nothing to evaluate: add a `healthcheck` block to that service in `docker-compose.yml` (see the [Prerequisites](#prerequisites) snippet above) and redeploy the stack. See [Auto-Heal Policies · Troubleshooting](/features/auto-heal-policies#troubleshooting) for the other ways a policy can fail to fire.
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Auto-Heal Policies" icon="heart-pulse" href="/features/auto-heal-policies">
|
||||
The full mechanics: safety rails, stack vs. service scope, multi-node behavior, and notifications.
|
||||
</Card>
|
||||
<Card title="Alerts & Notifications" icon="bell" href="/features/alerts-notifications">
|
||||
Pair a policy with an alert rule on the same Monitor sheet for visibility before a restart even fires.
|
||||
</Card>
|
||||
<Card title="Automatically Patch a Stack With an Auto-Update Label" icon="arrows-rotate" href="/tutorials/configure-auto-update-policies">
|
||||
Another hands-off stack policy: keep images current instead of watching for unhealthy containers.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Automatically Patch a Stack With an Auto-Update Label
|
||||
sidebarTitle: Auto-patch a stack with a label
|
||||
description: Tag a stack for hands-off patching, schedule a label-driven check, and confirm a real digest rebuild gets applied without you clicking Apply yourself.
|
||||
---
|
||||
|
||||
Say you're running a self-hosted app on a rolling image tag like `latest`. The upstream maintainer regularly rebuilds that tag with security patches, same version, same tag name, new image content, and every rebuild is one you'd apply if you noticed it. Nobody wants to check the **Update** board every day for that. This walks through tagging a stack with a **Stack Label** built for exactly this ("safe to patch unattended"), pointing a schedule at that label instead of at one specific stack, and running it once to watch a real pending rebuild get pulled and recreated.
|
||||
|
||||
The worked example is `jackett`, a small self-hosted indexer proxy pinned to `lscr.io/linuxserver/jackett:latest`. This tutorial does not cover bumping a stack to a *newer semver tag* (that always needs a manual Compose edit first, since a schedule never rewrites an image reference) or building the label itself from Settings instead of inline. See the [Auto-Update Policies](/features/auto-update-policies) feature page for the semver workflow and risk badges, and [Stack Labels](/features/stack-labels) for every other way to create and manage labels.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **An admin or node-admin account.** Assigning a Stack Label and creating a scheduled task both require one of these roles; viewers and deployers can see the result but can't set it up.
|
||||
- **A running stack.** Any stack works, but only a stack whose currently-pinned tag has a real pending **same-tag digest rebuild** actually gets pulled and recreated by the schedule. A stack that's fully up to date, or one pinned to a tag with a *newer* tag available instead, still gets checked on schedule, it just has nothing to apply until you edit its Compose pin (see [Auto-Update Policies · Workflow](/features/auto-update-policies#workflow)).
|
||||
- **The hub (Local) selected as the active node for the scheduling step.** Schedules is a hub-level view and isn't available while a remote node is the active selection; labeling the stack itself works from whichever node it lives on.
|
||||
|
||||
<Steps>
|
||||
<Step title="Spot the pending update and create the label">
|
||||
Open the stack that has an update pending. Its **Anatomy** tab shows an **Update available** panel naming the image and marking it `same-tag digest rebuild`, the same signal the sidebar's dot and the **Update** board also carry for this stack.
|
||||
|
||||
Right-click the stack in the sidebar (or use its kebab menu) and choose **Labels → New label**. Type a name that describes the policy, not the app, since you'll likely tag other stacks with it later: `Auto-update`. Pick a color and click **Create**.
|
||||
|
||||
<Frame caption="The stack's own Update available panel, alongside the sidebar context menu's Labels submenu with the inline New label form filled in.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/context-menu-new-label-form.png" alt="jackett's Anatomy tab showing an Update available panel for lscr.io/linuxserver/jackett:latest marked 'patch · same-tag digest rebuild', with the stack's right-click context menu open on the Labels submenu showing an inline New label form: a text input reading 'Auto-update', a row of ten color swatches with green selected, and Create and Cancel buttons." />
|
||||
</Frame>
|
||||
|
||||
Creating a label from this menu assigns it to the stack in the same round trip, no separate assignment step.
|
||||
</Step>
|
||||
<Step title="Confirm the sidebar grouped by label">
|
||||
The sidebar now splits into label groups instead of one flat list: an **AUTO-UPDATE** section holding this stack, and an **UNLABELED** section for everything else on the node.
|
||||
|
||||
<Frame caption="The sidebar after the label exists, grouped by label instead of flat.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/sidebar-auto-update-group.png" alt="Sidebar showing an AUTO-UPDATE group containing jackett, and an UNLABELED group containing keycloak-idp and whoami-edge." />
|
||||
</Frame>
|
||||
|
||||
This is cosmetic confirmation, not the policy itself: the schedule you create next is what actually drives updates.
|
||||
</Step>
|
||||
<Step title="Create a schedule that targets the label">
|
||||
Open **More → Schedules**, click **New Schedule**, and set **Action** to **Auto-update stacks by label** (in the **Updates** group). Fill in:
|
||||
|
||||
- **Name**: `Nightly patch check`
|
||||
- **Stack Label**: type `Auto-update` and pick the suggestion that appears (it shows the live match count: `1 stack · 1 node`)
|
||||
- **Scope**: **Entire fleet**. A stack-label schedule resolves membership at run time, so leaving it fleet-wide means any stack you tag with this label later, on any node, is covered automatically without editing the schedule again.
|
||||
- **Schedule**: leave the default **Daily** at **03:00**
|
||||
|
||||
Below the Stack Label field, **Current matches** previews exactly which stacks this task would act on right now, before you save anything.
|
||||
|
||||
<Frame caption="The New scheduled task modal with the label-driven fields filled in and the live match preview resolved.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/new-schedule-label-filled.png" alt="New scheduled task modal: Name 'Nightly patch check', Action 'Auto-update stacks by label', Stack Label 'Auto-update', Scope segmented control with Entire fleet selected, a Current matches panel reading '1 stack on 1 node' with 'Local: jackett' listed, and Schedule set to Daily at 03:00 with the cron preview 0 3 * * *." />
|
||||
</Frame>
|
||||
|
||||
Click **Create**. See [Schedule an Operation](/tutorials/schedule-an-operation) for the general mechanics of the Schedules timeline and table if this is your first scheduled task; this tutorial goes straight to running it.
|
||||
</Step>
|
||||
<Step title="Confirm the task, then run it once">
|
||||
Switch to **All tasks**. The new row shows **Auto-update stacks by label** as the action and `Label: Auto-update · Entire fleet` as the target, with **Status** `Never run`.
|
||||
|
||||
<Frame caption="The new label-driven task in the All tasks table before its first run.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/all-tasks-row.png" alt="All tasks table with one row: Nightly patch check, Auto-update stacks by label, Label: Auto-update · Entire fleet, At 03:00 AM / 0 3 * * *, Status Never run, Next Run 8/6/2026 11:00:00 PM, Enabled ON." />
|
||||
</Frame>
|
||||
|
||||
Click **Run now** (play icon) rather than waiting for 03:00. This is a real pull and recreate, so it takes longer than a restart task, tens of seconds rather than a fraction of one. Click **Refresh** after it finishes.
|
||||
|
||||
<Frame caption="The same row after a successful run: Status flips to Success and the pending update clears.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/all-tasks-success.png" alt="All tasks table with the Nightly patch check row now showing a green Success badge, and the sidebar's UPDATES filter chip reading 0 instead of 1." />
|
||||
</Frame>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Verify it worked
|
||||
|
||||
Check from two places, since a status badge alone can't tell you *what* got updated, and a cleared update alone can't tell you it was this schedule that cleared it.
|
||||
|
||||
**The Execution history.** Click the **Execution history** button (clock icon) on the row. The run shows **Source** `Manual`, **Status** `Success`, and details naming exactly what happened: `Selector: stack-label="Auto-update" · scope=entire fleet` followed by `Stack "jackett": updated (lscr.io/linuxserver/jackett:latest).`
|
||||
|
||||
<Frame caption="Execution history showing the label selector that was resolved and the exact image that got updated.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/run-history.png" alt="Execution history sheet for Nightly patch check showing one run: Source Manual, Status Success, Duration 24.1s, and details naming the stack-label selector and 'Stack jackett: updated (lscr.io/linuxserver/jackett:latest)'." />
|
||||
</Frame>
|
||||
|
||||
**The Update readiness board.** Open **More → Update**. Where jackett's card used to show `Rebuild available`, the board now reads `Everything is up to date`, and the sidebar's **Updates** filter chip is back to `0`.
|
||||
|
||||
<Frame caption="The Update readiness board after the schedule cleared the only pending rebuild.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/readiness-board-up-to-date.png" alt="Update readiness board showing the empty state: a shield icon, the headline 'All stacks on current builds', and the subtitle 'Sencho rechecks registries on the configured interval.' The sidebar's UPDATES chip reads 0." />
|
||||
</Frame>
|
||||
|
||||
## If something goes wrong
|
||||
|
||||
**A later run reports success but changes nothing.** Run the task again (or let it fire at 03:00 the next night) once jackett is already current, and the row still shows the green **Success** badge, not a failure. Open its Execution history and the details read `Stack "jackett": all images up to date.` instead of naming an updated image. This is the expected steady state, not a bug: the label schedule checks every stack under the label on every run, and "nothing changed because there was nothing to change" is success, not skipped. It only becomes worth investigating if a stack you know has a real pending digest rebuild keeps reporting "up to date"; in that case, confirm the label is still assigned to that stack (right-click it and check the **Labels** submenu for a checkmark) and that the stack's own image reference still uses the same tag Sencho detected the rebuild against.
|
||||
|
||||
<Frame caption="A second run against an already-current stack: still Success, with a different, unambiguous detail message.">
|
||||
<img src="/images/tutorials/configure-auto-update-policies/run-history-two-runs.png" alt="Execution history sheet showing two runs for Nightly patch check: the newer run at 9:49:09 PM with Status Success, Duration 0.8s, and details 'Stack jackett: all images up to date', and the earlier run at 9:44:20 PM with Duration 24.1s and details naming the updated image." />
|
||||
</Frame>
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Auto-Update Policies" icon="arrows-rotate" href="/features/auto-update-policies">
|
||||
The full readiness board reference: risk badges, the semver-bump workflow this tutorial doesn't cover, and multi-node behavior.
|
||||
</Card>
|
||||
<Card title="Stack Labels" icon="tags" href="/features/stack-labels">
|
||||
Every other way to create, assign, and manage labels, plus the label-driven Fleet Actions and mute shortcuts.
|
||||
</Card>
|
||||
<Card title="Scheduled Operations" icon="calendar-clock" href="/features/scheduled-operations">
|
||||
The full action list and cron reference shared by every scheduled task type, including this one.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: Stop a Deploy When a Required Environment Variable Is Missing
|
||||
sidebarTitle: Block a missing required var
|
||||
description: Turn on the deploy guardrail that refuses a stack update when a required environment variable has no value, and watch it catch a real missing password before anything breaks.
|
||||
---
|
||||
|
||||
Say a stack declares a required database password with `${DB_PASSWORD:?message}` in its Compose file. Nothing stops a teammate from clearing that value in `.env` by accident, and a normal `docker compose up` would let the mistake ride until the container fails at runtime. This walks through turning on the guardrail that catches it up front instead: you'll deploy a small Postgres stack with a required password, clear the password to simulate the mistake, watch the next update get refused with a clear message naming the variable, then fix it and confirm the update goes through.
|
||||
|
||||
This tutorial covers one guardrail: **Block deploy on missing required env vars**. It doesn't cover the rest of the Environment inventory (secret classification, duplicate or shell-only detection, project environment file selection) or the other Deploy Guardrails settings (health observation, rollback retention, automatic external network creation); see the [Environment and Secrets Guardrails](/features/environment-guardrails) feature page for the complete picture.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The `admin` or `node-admin` role. Changing the guardrail setting needs `node:manage` permission, which both roles hold; updating the stack needs `stack:deploy`, which every role except `viewer` and `auditor` holds.
|
||||
- Available on every tier; no Admiral requirement.
|
||||
- The setting is scoped to one node. If you manage more than one, turn it on for the node you deploy the scenario stack to.
|
||||
|
||||
<Steps>
|
||||
<Step title="Deploy a stack with a required password">
|
||||
Select **Create Stack**, name it `inventory-db`, and replace its Compose file with:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD in .env before deploying}
|
||||
POSTGRES_DB: inventory
|
||||
ports:
|
||||
- "5432:5432"
|
||||
```
|
||||
|
||||
The `${DB_PASSWORD:?message}` syntax tells Compose the variable is required: it refuses to render the stack at all if `DB_PASSWORD` has no value, anywhere in the file. The **.env** tab stays disabled until a project environment file exists, so switch to the **Files** tab, select **New file**, and create one named `.env`. Open it and add `DB_PASSWORD=devpassword123` before your first deploy. Setting it now gets you a clean first deploy; the next steps remove it on purpose to see the guardrail react.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/compose-required-password.png" alt="Compose editor for inventory-db showing a single db service: image postgres:16, restart always, environment with POSTGRES_PASSWORD set to the interpolation ${DB_PASSWORD:?Set DB_PASSWORD in .env before deploying}, POSTGRES_DB inventory, and port 5432 published." />
|
||||
</Frame>
|
||||
|
||||
Select **Save & Deploy**. The container comes up healthy.
|
||||
</Step>
|
||||
<Step title="Confirm the variable in the Environment tab">
|
||||
Open the stack and switch to the **Environment** tab in the Anatomy panel header. `DB_PASSWORD` appears under **present** with **secret** and **required** badges, sourced from `.env` and scoped to interpolation. The lock badge means its value is never read into the inventory, only its presence and status.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/env-tab-db-password-required.png" alt="Environment tab for inventory-db showing 3 vars, 2 likely secret, and a present group listing DB_PASSWORD (secret, required, present, source .env, scope interpolation), POSTGRES_DB (present, source inline, scope injected), and POSTGRES_PASSWORD (secret, present, source inline, scope injected)." />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Turn on the guardrail">
|
||||
Open **Settings** → **Infrastructure** → **Stacks** and find the **Deploy Guardrails** section. Turn on **Block deploy on missing required env vars**, then select **Save settings**. It's off by default: without it, Compose only reports a missing required variable when the deploy or update actually runs.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/deploy-guardrails-setting-on.png" alt="Deploy Guardrails settings section with Block deploy on missing required env vars set to ON, alongside Observe health after updates, Observation window, Superseded rollback retention, Maximum retained rollback generations, and Automatically create missing external networks." />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Clear the password to simulate the mistake">
|
||||
Back on `inventory-db`, open the **.env** tab and clear the value so the line reads `DB_PASSWORD=`. Select **Save & Deploy** and choose **Save Only** from the dropdown next to it, so the change is written to disk without triggering a deploy yet.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/env-file-password-cleared.png" alt=".env editor for inventory-db showing a single line, DB_PASSWORD with no value, with the stack still running above." />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Update the stack and watch the guardrail fire">
|
||||
Select **Update**. A readiness dialog summarizes preflight, drift, current containers, and a few other checks; note that none of them mention environment variables; the dialog can say **ready** even though this update is about to be refused. Select **Update now** anyway.
|
||||
|
||||
The update fails immediately, before any image pull or container change, with **Deploy blocked: required environment variable DB_PASSWORD is missing. Define it in a .env or env_file, then deploy again.** The full message is easiest to read from the notification bell in the top bar.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/update-blocked-notification.png" alt="Notifications panel with the top entry reading 'Deploy blocked: required environment variable DB_PASSWORD is missing. Define it in a .env or env_file, then deploy again.', 2 minutes ago, above older entries for a vulnerability scan, a health gate pass, and an image update." />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="Fix it and confirm the update succeeds">
|
||||
Reopen the **.env** tab, set the line back to `DB_PASSWORD=devpassword123`, and save. Select **Update** again. This time it runs to completion: the image pulls, the container recreates, and the health gate passes.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/update-succeeded-health-gate.png" alt="Updating inventory-db progress dialog showing Succeeded, a passed health gate message, and a log ending in '=== Stack updated successfully ===' and '=== Pruned dangling images ==='." />
|
||||
</Frame>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Verify it worked
|
||||
|
||||
Check from two independent surfaces so you're not trusting a single UI element.
|
||||
|
||||
**The Activity tab.** Open the stack's **Activity** tab. Reading newest first, you'll see the health gate pass and the successful update sitting right above the earlier **Deploy blocked** entry, so the block and the recovery are both on the record with the account that triggered each one.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/activity-tab-block-and-fix.png" alt="Activity tab for inventory-db listing, newest first: health gate passed, stack updated, update started, Deploy blocked: required environment variable DB_PASSWORD is missing, an earlier vulnerability scan and health gate pass, and the original deploy." />
|
||||
</Frame>
|
||||
|
||||
**The Environment tab.** Reopen it: `DB_PASSWORD` is back under **present** with its **required** badge, and the summary line reads 0 missing.
|
||||
|
||||
## If something goes wrong
|
||||
|
||||
**The variable still shows missing after you set it, but in the wrong place.** Compose's `${VAR:?message}` interpolation only resolves from the project `.env` file (or the shell), never from a service's `env_file:` entries: those are only injected into the container after interpolation already ran. Moving `DB_PASSWORD` into a separate `env_file:`-declared file instead of the project `.env` reproduces the exact same block, because as far as interpolation is concerned the variable is still unset.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorials/configure-environment-guardrails/compose-env-file-mistake.png" alt="Compose editor for inventory-db showing env_file: - secrets.env added under the db service, with POSTGRES_PASSWORD still set to the ${DB_PASSWORD:?...} interpolation." />
|
||||
</Frame>
|
||||
|
||||
Move the value back into the project `.env` file (or add it there in addition to the `env_file`) and redeploy; the guardrail clears as soon as interpolation can see it. See [Environment and Secrets Guardrails](/features/environment-guardrails#interpolation-versus-container-injection) for the full interpolation-versus-injection explanation.
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Environment and Secrets Guardrails" icon="triangle-exclamation" href="/features/environment-guardrails">
|
||||
The full inventory: secret classification, env file status, and project environment file selection.
|
||||
</Card>
|
||||
<Card title="Compose Doctor" icon="stethoscope" href="/features/compose-doctor">
|
||||
Pre-deploy preflight that also surfaces a missing `env_file:` as a high-risk finding.
|
||||
</Card>
|
||||
<Card title="Fleet Secrets" icon="lock" href="/features/fleet-secrets">
|
||||
Author encrypted env-var bundles on the hub and push them to labeled stacks across the fleet.
|
||||
</Card>
|
||||
<Card title="Health-Gated Updates" icon="shield-check" href="/features/health-gated-updates">
|
||||
The post-deploy health observation configured in the same Deploy Guardrails settings section.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Configure Recovery Vault Backups
|
||||
sidebarTitle: Configure Recovery Vault backups
|
||||
description: Step-by-step instructions for configuring and restoring fleet backups with Recovery Vault.
|
||||
---
|
||||
|
||||
<Note>
|
||||
This tutorial is a placeholder pending full content. See the [Fleet Backups feature page](/features/fleet-backups) for the current reference documentation.
|
||||
</Note>
|
||||
|
||||
## What you'll do
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<Steps>
|
||||
<Step title="">
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Fleet Backups" icon="shield-halved" href="/features/fleet-backups">
|
||||
Configure Recovery Vault backups and restore stacks or nodes.
|
||||
</Card>
|
||||
</CardGroup>
|
||||