Files
sencho/docs/features/compose-doctor.mdx
T
Anso 9ff678a7bb docs: v1 docs refresh (batch 5) (#1395)
* docs(introduction): refresh for the redesigned UI and replace screenshots

Bring the Getting Started Introduction page in line with the current
product:

- Add the Security top-level view to the navigation list and a
  dedicated Security section with a new screenshot.
- Correct the Fleet tab names (Snapshots, Status, Map, Deployments,
  Routing, Federation, Actions, Secrets).
- Split Settings out from security and list the current nine setting
  groups (Security graduated to its own view).
- Refine the navigation paragraph so role, tier, and local-vs-remote
  context read accurately.

Replace all four existing screenshots (Home, stack workspace, Fleet,
Resources) with fresh captures of the redesigned UI and add a Security
overview screenshot.

* docs(configuration): document advanced env vars and clarify deployment vs runtime config

Add an Advanced environment variables section (TRIVY_BIN, SENCHO_MESH_SUBNET,
GITSOURCE_MAX_CLONE_BYTES, SENCHO_PUBLIC_URL, SENCHO_COMPOSE_STALL_TIMEOUT_MS) and
reframe the intro to separate deployment-time configuration from the runtime
settings that live in the in-app Settings Hub. Cross-link the pilot-agent
variables to the Pilot Agent page instead of duplicating them.

* docs(sso): refresh SSO Setup Guide and SSO & LDAP reference for the redesigned UI

Refresh both SSO documentation pages against the current product and the
redesigned settings UI.

- Correct the navigation path to Settings -> Access -> SSO on both pages.
- Fix the "Require 2FA on SSO sign-in" toggle location to
  Settings -> Personal -> Account.
- Describe the login-page experience (the Local / LDAP toggle and the
  branded OIDC buttons under the "Or continue with" divider) and the SSO
  panel masthead (SCOPE, PROVIDERS, ENABLED).
- Replace all six SSO screenshots with fresh captures of the redesigned UI.

* docs(features): refresh the Features Overview page for the redesigned UI

Rewrite docs/features/overview.mdx to mirror the current Features
navigation grouping (Stacks, Deployment, Resources, Observability,
Fleet, Automation, Security & Identity) and add the recently shipped
capabilities surfaced in the redesign: Stack Dossier, Drift Detection,
Compose Doctor, Compose Networking, Environment & secrets guardrails,
Storage portability, Health-Gated Updates, Fleet Dossier, and the
dedicated Security page.

Correct stale claims (the file explorer now gates writes on stack edit
permission, not an admin role; downloads are a read action; bulk label
assign now spans nodes) and standardize the tier callouts so partly
paid features read as "Admiral adds X". Replace the three pre-redesign
screenshots and add a Security overview banner, all captured from a
populated fleet.

* docs(features): refresh the Appearance page for the redesigned UI

Add fresh screenshots and a troubleshooting section to the Appearance
page, verified against the live product.

- Add four screenshots: the Theme card (live preview, mode, accent, and
  fine-tune sliders), the top-bar quick switcher, the Typography card,
  and the Display card.
- Refresh the Density screenshot used by the Settings reference page.
- State that the quick switcher also covers text size, and that the
  contrast, border, and glow sliders stay in Settings.
- Add a Troubleshooting accordion covering per-browser persistence,
  resets to defaults, cross-operator scope, and the quick-switcher
  versus full-Settings split.

* docs(introduction): refresh screenshots and correct stale content

* docs(reference): refresh the Settings Reference page for the redesigned UI

Replace all seven stale screenshots with fresh 1920x1080 captures.
Add five new screenshots for the sections that previously had none.

Content changes:
- Sidebar table: rename Infrastructure "Fleet Mesh" entry to "Fleet";
  add "Image update checks" to the Automation group list
- Fleet section: rename heading to match registry label; add the
  Documentation snapshots subsection (snapshot_documentation toggle)
- Container Alerts: add screenshot
- Image update checks: add the full section (Registry checks table,
  scheduling mode, interval presets, cron expression support)
- Stacks / Deploy Guardrails: add screenshot
- Recovery: add the full section (System health snapshot, Environment
  preflight checks, Safe actions, Command-line recovery table)

* docs(sso): refresh screenshots for SSO quickstart and feature pages

* docs: refresh Features Overview screenshots and content

Replace all 4 hero screenshots with fresh 1920x1080 production captures.
Correct security posture state names (Action needed / Monitoring / Secure),
add the Policies tab to the Security section tab list, mention the Simple
mode in Scheduled operations, and update all alt text to match the new
screenshots.

* docs: refresh Appearance page screenshots and correct quick-switcher scope

Replace all four Appearance screenshots with fresh production captures.
Fix the quick-switcher control list: remove fonts (not present in the
popover), add visual style and readability which are. Add Log chip color
to the Display section. Update all screenshot alt text to match new
captures.

* docs: refresh stack management page with current UI and anatomy tabs

* docs: fix convert-tab-error screenshot with fully visible error toast

* docs: convert troubleshooting section to AccordionGroup format

* docs(quickstart): refresh screenshots and align dashboard description

Replace all three first-boot and dashboard screenshots with current UI.
Add Security to the top navigation list, update gauge and Stack health
descriptions to reflect sparklines and column detail, and align
Configuration Status wording with the Introduction page.

* docs(editor): rewrite anatomy panel, replace all screenshots

- Correct the anatomy panel tab inventory: the panel has eight tabs
  (Anatomy, Activity, Dossier, Drift always; Environment, Networking,
  Doctor, Storage when the node advertises the matching capability), not
  three as previously documented
- Add table describing all eight tabs with capability gates and links to
  dedicated feature pages
- Add anatomy-tabs.png screenshot showing the scrollable tab row
- Note the Doctor severity dot (red for blocker, amber for high-risk)
- Remove the stale Markdown-export subsection; Dossier and Activity are
  now covered in the tab table
- Replace all six stale screenshots with fresh 1920x1080 captures
- Replace the compose diff preview screenshot

* docs(files): refresh Files & Volumes screenshots and fix context-menu alt text

Replace all 9 stale screenshots on the Files & Volumes page with fresh
captures from the production node. Fix three alt-text strings that did
not match the live UI: removed hardcoded octal value 644, and added the
Duplicate, Copy to, and Move to entries missing from the context-menu
alt text.

* docs: rewrite Stack Activity page with full event categories and fresh screenshots

Expands the event category table from 5 to 10 entries to cover drift
detected, drift resolved, update started, health gate passed, and health
gate failed. Adds a live-disconnected-state section, a background-actor
attribution table, and a corrected troubleshooting accordion covering the
WebSocket reconnect case. Replaces both stale screenshots with fresh
1920x1080 captures from the production node.

* docs(drift): rewrite drift detection page with screenshots and full coverage

Full rewrite of the Drift Detection feature page. Adds two previously
undocumented finding types (network-undeclared, network-missing), expands
the temporal section to distinguish the raw-file hash from the parsed-model
hash, documents the two-layer spatial-engine and ledger architecture,
explains when the ledger is reconciled (post-deploy vs manual re-check vs
tab open), adds Activity timeline integration note, introduces a Limitations
section (no background scanner, port-range caveat, history cap, advisory-only
enforcement), expands Troubleshooting from five entries to seven using the
AccordionGroup convention, and adds four production screenshots.

* docs(drift): use CardGroup for Related section

* docs(dossier): rewrite Stack Dossier page with full feature coverage

* docs(networking): rewrite Compose Networking page with full feature coverage

* docs(doctor): rewrite Compose Doctor with full 30-rule reference, screenshots, and cross-links

* docs(networking): add production screenshots and correct alt text

Adds 7 production screenshots for all sections of the Compose Networking
page and updates the four placeholder alt texts written before screenshots
were taken to match what the actual images show (arr-net external badge,
swag service with 443/tcp and 80/tcp, single-service exposure intent row).
Also adds the full-panel overview image at the top of the page.

* docs(environment-guardrails): rewrite with project env file, env file status, and screenshots

* docs(storage): rewrite Storage Portability page with screenshots and full coverage

Rewrites compose-storage.mdx from a 61-line sketch into a complete
reference page. Key additions: Where to find it section with screenshot,
full storage inventory section documenting all mount type/access/status
chips and the Linux owner display, expanded portability verdict section
with per-reason detail and edge-case caveats (read-only binds, symlink
escapes, anonymous volume risks), snapshot coverage section with admin
scope and remote-node behavior, Findings in Doctor cross-reference, and
six troubleshooting accordions covering tab visibility, bind status,
external named volumes, render errors, and snapshot coverage states.
Adds two production screenshots: storage-tab.png and storage-node-bound.png.

* docs(stack-labels): rewrite with accurate permissions, capability gate, dry run, live preview, and color conflict docs

* docs: rewrite Stack Sidebar page with accurate feature coverage

Rewrites the Stack Sidebar documentation page to match the current UI.

Key changes:
- Fix branding header description (shows logo + version, not just version)
- Fix bulk mode icon description (stacked-rows, not square)
- Add cross-node search section (fan-out behavior, Other nodes section,
  unreachable-node warnings, click-to-switch navigation)
- Update Labels submenu description (inline New label creation, Manage labels link)
- Note that Delete only appears when the user has delete permission
- Remove the auto-update implication from Schedule task description
- Rewrite the Activity ticker section with the full 6-state priority cascade
  table; remove the non-existent IDLE state; correct pulsing-dot behavior
- Replace all 7 stale screenshots with fresh production screenshots
- Add new sidebar-cross-node-search.png screenshot

* docs(atomic-deployments): refresh screenshot and document project env files, rollback readiness, and recovery actions

* docs(atomic-deployments): fix rollback permission visibility and banner string accuracy

The Rollback menu entry is hidden by the frontend when the user lacks
stack:deploy; it never appears and does not 403. Fixed the step-4
narrative and troubleshooting accordion to match.

The rollback-failure banner emitted by ComposeService is
'=== Rollback failed. Manual intervention may be required ==='
(period, capital M). Fixed both occurrences in the page.

Updated the Settings navigation path from the nonexistent
'Roles & Access' to the real 'Access'.

* docs(deploy-progress): rewrite with health gate, inline style, and 9 fresh screenshots

Add health gate section covering all four states (observing, passed, failed,
unknown) with exact UI banner text and the configurable observation window.
Expand the inline style section with full band content, 4s auto-dismiss, and
pill handoff. Add Scanning as a supported entry point. Replace all 6 existing
screenshots and add 3 new ones (modal-health-gate, inline-banner, setting-style).
Add two health gate troubleshooting accordions. Add Related CardGroup linking to
health-gated-updates, stack-activity, deploy-enforcement, and atomic-deployments.

* docs(health-gated-updates): refresh screenshots and correct signal row order and label

* docs(deploy-enforcement): rewrite with fleet replication, honor suppressions location, scan-failed dialog state, and fresh screenshots

Adds the Fleet policy replication section covering control/replica behavior,
Managed by control node banner, and Demote to control. Documents the exact
location of the Honor suppressions toggle (bottom of Policies tab). Expands
the block dialog section with the scan-failed row state. Updates all three
screenshots to the current visual design. Restores the Admiral license note
and corrects the policy-card scope description.

* docs(app-store): rewrite with mobile layout, fresh screenshots, and registry admin note

- Replace all 5 stale screenshots with 1920x1080 production captures
- Add app-store-mobile.png showing the status masthead layout
- Document mobile single-column layout in a new Mobile subsection
- Note that the featured hero has its own Deploy button
- Mark the category rail as desktop only with a cross-link to Mobile
- Add admin-account requirement to the custom registry section
- Add Related CardGroup linking vulnerability scanning, deploy progress, deploy enforcement, and resources
2026-06-29 01:29:03 -04:00

280 lines
21 KiB
Plaintext

---
title: Compose Doctor
description: Run a preflight check on a stack before you deploy. Compose Doctor renders the effective Compose model and reports common failure modes, grouped by severity, with a clear fix for each.
---
The **Doctor** tab in the right-hand **Anatomy** panel answers one question before you apply a change: *what will Docker actually run, and is it safe on this node?* Compose Doctor renders the effective Compose model (the fully resolved result after interpolation, includes, profiles, `.env`, and `env_file` are applied) and then runs a set of deterministic checks against it and the live Docker state on the node it would deploy to.
The check is advisory. It never blocks a deploy and never changes a stack. It runs on demand: press **run preflight** and Sencho renders the model, runs all 30 checks, and stores the result so the tab still shows it the next time you open the stack.
## Where to find it
<Frame>
<img
src="/images/compose-doctor/compose-doctor-tab.png"
alt="The tab bar at the top of the Anatomy panel showing the Doctor tab selected"
/>
</Frame>
The **Doctor** tab appears in the Anatomy panel header when the active node supports Compose Doctor. Click any stack in the sidebar, then switch to the **Doctor** tab.
## How it works
Every preflight run follows three steps:
1. **Render** the effective model. Sencho calls `docker compose config` on the stack, which resolves all variable interpolation, `include` directives, profile overrides, and `env_file` references into a single, normalized model.
2. **Snapshot** live Docker state. Sencho reads which host ports are in use, which containers are running, and which named networks and volumes exist on the target node.
3. **Run 30 deterministic rules** against the combination. Each rule is pure and produces zero or more findings with a severity, a message, and a suggested fix.
Sencho stores exactly one run per stack per node, so a new run immediately overwrites the previous one; there is no history.
## Privacy and environment values
Compose Doctor reads the structure of the effective model (service names, images, ports, volumes, and the *names* of environment variables), never their values. A secret injected through `environment:` or `env_file:` never appears in the report, the stored run, or the logs.
One caveat: a secret interpolated into a structural field (such as a port, an image tag, or a volume path built from `${VAR}`) is resolved before the model is read, so its value does show in the rendered model. Render errors are redacted before storage; Sencho never writes the raw `docker compose config` error to the database. See [Environment and Secrets Guardrails](/features/environment-guardrails).
## Severity levels
Every finding is graded so you can triage at a glance. The tab groups findings under four headings and the summary reflects the most severe one:
| Severity | Meaning |
|----------|---------|
| **Blocker** | The deploy will fail or the model cannot be rendered. Fix these before applying. |
| **High risk** | The deploy will likely succeed but does something dangerous or surprising, such as exposing a port to every network or mounting the Docker socket. |
| **Warning** | A reliability or reproducibility concern, such as a moving `latest` tag or a missing restart policy. |
| **Info** | Something to be aware of, such as a network or volume that will be created on first deploy. |
## Run status
The summary card at the top of the Doctor tab reflects the overall outcome of the last run:
| Status | When it appears | Summary card |
|--------|-----------------|--------------|
| **No run yet** | Preflight has never been triggered for this stack on this node | Placeholder with "no preflight yet" and a Stethoscope icon |
| **All clear** | Model rendered; no findings | Green card with "No issues found in the effective model." |
| **Cannot render** | `docker compose config` failed | Red card with the (redacted) render error message |
| **Blocker** | Highest finding is a blocker | Red card with a count of findings by severity |
| **High risk** | Highest finding is high risk | Amber card with a count of findings by severity |
| **Warning** | Highest finding is a warning | Blue card with a count of findings by severity |
| **Info** | All findings are informational | Muted card with a count of findings by severity |
The summary card also shows when the run happened and who triggered it: "ran 5 minutes ago by admin".
A small colored dot appears on the **Doctor** tab label when the last run found blockers (red) or high-risk issues (amber). Opening the tab removes neither the dot nor the findings; run preflight again after fixing the issues to clear them.
<Frame>
<img
src="/images/compose-doctor/compose-doctor-severity-dot.png"
alt="The tab bar showing a small amber dot on the Doctor tab label indicating a high-risk finding from the last preflight run"
/>
</Frame>
## What it checks
All 30 rules are listed below, organized by topic.
### Model rendering
| Rule | Severity | What it detects |
|------|----------|----------------|
| Compose model could not be rendered | Blocker | `docker compose config` failed; the model cannot be assessed and all other rules are skipped. Fix the reported error first. |
### Environment
| Rule | Severity | What it detects |
|------|----------|----------------|
| Unset variable | High | A variable referenced by the model has no value in the environment or any consulted env file. Compose silently substitutes an empty string, which often breaks the container without a clear error. |
| Missing env file | High | A path listed under `env_file:` does not exist in the stack directory. Compose fails to start the stack when a required env file is absent. |
### Port conflicts
| Rule | Severity | What it detects |
|------|----------|----------------|
| Host port is already in use | Blocker | Another stack or unmanaged container on this node already binds the same host port. The deploy will fail. Requires live Docker state; skipped when the daemon is unreachable. |
| Two services publish the same port | Blocker | Two services within this stack both claim the same host port on overlapping interfaces. Only one can bind it. |
| Port exposed on all interfaces | High | A port is published on all interfaces (`0.0.0.0`), making it reachable from every network the host is attached to. |
### Bind mounts
| Rule | Severity | What it detects |
|------|----------|----------------|
| Bind mount path is missing | High | A relative host path referenced by the model does not exist inside the node's Compose base directory. Docker will create it as a root-owned directory on deploy, which often leaves the container unable to write to it. |
| Bind mount may have wrong ownership | Warning | A resolvable path is owned by root but the container runs as a non-root user. Applies to POSIX nodes only. |
### Security
| Rule | Severity | What it detects |
|------|----------|----------------|
| Docker socket mounted | High | A service mounts `docker.sock`, which grants it root-equivalent control over the host. |
| Privileged container | High | A service runs with `privileged: true`, disabling most container isolation. |
| Host network mode | High | A service uses `network_mode: host`, bypassing Docker's network isolation and ignoring published-port mappings. |
| Check UID/GID alignment | Warning | A service sets a UID or GID and mounts host paths that Sencho cannot inspect from inside its container. Mismatched ownership between the host path and the container user is a common source of permission errors. |
### Reliability
| Rule | Severity | What it detects |
|------|----------|----------------|
| Image uses a moving tag | Warning | A service uses `:latest` or a tag-less image reference, making deploys non-reproducible and subject to unexpected changes. |
| No restart policy | Warning | A service has no restart policy and will not come back after a crash or host reboot. |
| No healthcheck | Warning | A service declares no healthcheck in the Compose model. The image itself may define one, but Sencho cannot see it from the model alone. |
### Compose semantics
| Rule | Severity | What it detects |
|------|----------|----------------|
| Swarm-only deploy fields | Warning | A service sets `deploy` keys (`placement`, `update_config`, `rollback_config`, `endpoint_mode`) that standalone Compose ignores; these apply to Swarm only. |
| Duplicate container_name | Blocker | Two services within this stack both set the same `container_name`. Docker requires unique names; the deploy will fail. |
| Anonymous volume in use | Info | A service mounts one or more anonymous volumes. Anonymous volumes have no name, so they are easy to miss when backing up and are orphaned when the container is recreated. |
| Effective model adds services | Info | The effective model includes services not visible in this file, pulled in via `include`, `extends`, or profiles. What deploys may differ from what you see here. |
### Node state
The rules in this category (except "Node-state checks skipped" itself) read live Docker state. When the daemon is unreachable, they are all skipped and the "Node-state checks skipped" info finding is added in their place. "Host port is already in use" in Port conflicts also reads node state and is skipped under the same conditions. See [Node-state checks and graceful degradation](#node-state-checks-and-graceful-degradation).
| Rule | Severity | What it detects |
|------|----------|----------------|
| Node-state checks skipped | Info | The Docker daemon could not be reached; node-state rules did not run and this result is partial. |
| External network not found | Blocker | A network declared `external: true` does not exist on this node. The deploy will fail. |
| External volume not found | Blocker | A volume declared `external: true` does not exist on this node. The deploy will fail. |
| New network will be created | Info | A non-external network that does not yet exist on this node will be created on first deploy. |
| New volume will be created | Info | A named volume that does not yet exist on this node will be created on first deploy. |
| container_name already in use | Blocker | A service's `container_name` is already used by a different stack or unmanaged container on this node. The deploy will fail with a name conflict. |
### Exposure intent
These rules activate when the stack publishes at least one host port. They use the exposure intent you set in the Networking tab and any access URLs documented in the Stack Dossier to flag mismatches. See [Exposure intent checks](#exposure-intent-checks).
| Rule | Severity | What it detects |
|------|----------|----------------|
| Classified internal but publishes a host port | High | A service is classified as internal or same-node exposure but publishes a host port that contradicts that intent. |
| Sensitive service exposed on all interfaces | High | A service that looks like a database or admin tool (postgres, redis, adminer, and similar) publishes on all interfaces. |
| Stack publishes ports without an exposure intent | Warning | The stack publishes host ports but no exposure intent has been set in the Networking tab. |
| Published port not in documented access URLs | Warning | A host port is not referenced by any access URL recorded in the Stack Dossier; the documentation may be stale. |
| Has reverse-proxy labels but no documented URL | Warning | A service carries Traefik, Caddy, or similar reverse-proxy labels but has no documented access URL or reverse-proxy intent set. |
## Exposure intent checks
Five of the 30 rules cross-reference the stack's exposure intent and the access URLs documented in the Stack Dossier. These rules only fire when the stack publishes at least one host port.
To resolve exposure-related findings:
- **Set an exposure intent** in the [Compose Networking](/features/compose-networking) tab. Options are: internal (no host access), same-node (loopback only), LAN, reverse proxy (traffic arrives via a proxy), and public.
- **Document access URLs** in the [Stack Dossier](/features/stack-dossier). When a dossier URL references a host port, the port-vs-dossier rule uses that to confirm the port is intentional and expected.
Once an intent and access URLs are set, Sencho can detect when the configuration contradicts the documented intent, making future runs more precise.
## Node-state checks and graceful degradation
Six of the 30 rules require live Docker state to run: five are in the Node state category (external networks and volumes, new-resource notices, and container_name collision) and one is "Host port is already in use" in Port conflicts. All six are skipped when the Docker daemon is unreachable.
When the daemon is unreachable:
- All model-only rules still run and produce findings as normal.
- All node-state rules are skipped.
- An info finding ("Node-state checks skipped") is added to make the gap visible.
- The run completes with a valid status reflecting whatever model-only rules found.
A partial run (where node-state checks were skipped) is stored and displayed like any other run. Run preflight again once the node is reachable to get full coverage.
## Health-Gated Updates
The last stored preflight result feeds directly into the [Health-Gated Updates](/features/health-gated-updates) readiness check. When you trigger **Update** from a stack, Sencho opens a readiness dialog before pulling anything. The dialog reads your most recent preflight result:
- A blocker finding sets the update verdict to **Blocked**.
- One or more high-risk findings set the verdict to **Review required**.
- Preflight that has never run is noted in the dialog without dragging the verdict down.
Running preflight before updating gives the readiness check the most accurate signal. A clean pass after resolving findings contributes a positive readiness signal for that stack.
## Running preflight
<Frame>
<img
src="/images/compose-doctor/compose-doctor-never-run.png"
alt="The Doctor tab in the never-run state, showing the 'no preflight yet' placeholder with a Stethoscope icon and the run preflight button in the top-right corner"
/>
</Frame>
1. Click any stack in the left sidebar to open it.
2. Switch to the **Doctor** tab in the Anatomy panel header.
3. Press **run preflight**. Sencho renders the effective model, runs all checks, and updates the tab with the result.
4. Fix anything that matters, then run it again to confirm the findings clear.
Preflight runs against the **active node**, so selecting a remote node checks the stack against that machine's live Docker state. On mobile, the same report appears under the **Compose** section of the stack detail view.
<Frame>
<img
src="/images/compose-doctor/compose-doctor-findings.png"
alt="The Doctor tab showing an amber high-risk summary card with counts for high-risk and warning findings, followed by grouped finding rows each showing a service tag, title, explanation, suggested fix, and source file path"
/>
</Frame>
## Capability gating
The **Doctor** tab only appears when the active node reports the `compose-doctor` capability. A node running an older version of Sencho does not advertise this capability and the tab is hidden for that node until it is updated.
There is no tier gate: Compose Doctor is available on all plans.
## Limitations
- **Advisory only.** Compose Doctor never blocks a deploy or changes any stack configuration. Act on findings or ignore them; the choice is always yours.
- **Bind-mount checks are scoped.** Only paths that resolve inside the node's Compose base directory can be checked for existence and ownership. Absolute host paths outside that directory (such as `/mnt/media`) are not reported as missing.
- **Healthcheck rule cannot see image-level healthchecks.** The no-healthcheck rule fires when the Compose model does not declare a healthcheck. Many images define one internally that Sencho cannot see from the rendered model; treat the finding as a prompt to confirm the image provides one.
- **One run stored per node.** There is no history. Each new run overwrites the previous one for that stack on that node.
- **Node-state rules require a reachable Docker daemon.** See [Node-state checks and graceful degradation](#node-state-checks-and-graceful-degradation).
- **Port conflict check excludes the checked stack.** Host ports already held by the stack being checked are ignored, so redeploying a running stack does not generate a false conflict with itself.
- **Exposure intent rules require published ports.** All five exposure intent rules are skipped entirely when the stack publishes no host ports.
## Troubleshooting
<AccordionGroup>
<Accordion title="The report says it cannot render the model">
Compose Doctor runs `docker compose config` to resolve the effective model, and that command failed. Common causes are a YAML syntax error, an unresolved `include` or `merge` key, or a required variable with no value. Fix the Compose or env file and run preflight again. Sencho does not echo the raw error in the UI because it can contain values from your files.
</Accordion>
<Accordion title="A bind mount I use every day is flagged as missing">
Compose Doctor can only check paths that resolve inside the node's Compose base directory, such as a relative `./data` mount. An absolute host path like `/mnt/media` is outside what Sencho can see from inside its container and is never reported as missing. A missing relative path is a real finding: Docker would create it as a root-owned directory on deploy.
</Accordion>
<Accordion title="Every service is flagged for no healthcheck">
The no-healthcheck rule fires when the Compose model does not declare a healthcheck. Many images define their own healthcheck internally, which Sencho cannot see from the model alone. Treat these findings as a prompt to confirm the image provides a healthcheck rather than a hard problem.
</Accordion>
<Accordion title="A port conflict is flagged for a port my own stack uses">
Preflight ignores ports already held by the stack being checked, so redeploying a running stack does not flag its own bindings. A conflict finding means a different stack or an unmanaged container holds that host port on this node.
</Accordion>
<Accordion title="The report says node-state checks were skipped">
The node-state checks (external networks and volumes, host-port conflicts, and container_name collisions) read live Docker state. When the Docker daemon on the target node is unreachable, those checks are skipped and the report shows an info finding instead of those results. The model checks still run. Confirm the daemon is reachable on the node, then run preflight again for full coverage.
</Accordion>
<Accordion title="The Doctor tab is not visible on a remote node">
The tab appears when the active node reports that it supports Compose Doctor. A node running an older version of Sencho does not advertise that capability, so the tab is hidden for that node until it is updated.
</Accordion>
<Accordion title="Exposure intent rules are firing unexpectedly">
The five exposure intent rules activate when the stack publishes at least one host port. The "unclassified" warning clears as soon as you set an exposure intent in the Networking tab. The "port not in dossier" warning clears once you add a matching access URL in the Stack Dossier. If an intent is already set and the finding still fires, check that the intent is configured on the correct node and for the correct service.
</Accordion>
<Accordion title="I want to see who ran preflight last">
The summary card shows the username and relative time: "ran 5 minutes ago by admin". This reflects the most recent run stored for this stack on the currently active node.
</Accordion>
</AccordionGroup>
## Related features
<CardGroup cols={2}>
<Card title="Compose Networking" icon="network-wired" href="/features/compose-networking">
Set the exposure intent that drives the exposure intent preflight rules.
</Card>
<Card title="Stack Dossier" icon="book-open" href="/features/stack-dossier">
Document access URLs that Compose Doctor uses to verify published ports are intentional.
</Card>
<Card title="Health-Gated Updates" icon="shield-alt" href="/features/health-gated-updates">
Preflight results feed directly into the update readiness check before every update.
</Card>
<Card title="Environment Guardrails" icon="key" href="/features/environment-guardrails">
Keep secrets out of structural Compose fields to avoid them appearing in the rendered model.
</Card>
<Card title="Stack Drift" icon="code-branch" href="/features/stack-drift">
Drift detection checks whether what is running still matches the Compose file.
</Card>
<Card title="Compose Storage" icon="database" href="/features/compose-storage">
Understand named and anonymous volumes before running preflight on storage-heavy stacks.
</Card>
</CardGroup>