---
title: Drift Detection
description: See at a glance whether a stack's running containers still match the Compose file on disk, with specific, actionable reasons when they have diverged.
---

The **Drift** tab in the right-hand **Anatomy** panel answers a single day-two question: does what is actually running still match the Compose file on disk? Sencho treats your Compose file as the source of truth, so it compares the file against the live Docker runtime and reports exactly where the two have diverged.

The check is read-only. It tells you what changed and never alters a stack on its own, so you can trust the report before deciding what to do about it. Opening the tab builds the comparison fresh. When you deploy a stack through Sencho, it also records the Compose file it deployed as a baseline, so it can later tell you whether the file has changed since then, and it keeps a short history of the findings it has seen.

## Status

Every stack resolves to one of four states, shown as a badge at the top of the tab:

| Status | Meaning |
|--------|---------|
| **In sync** | The running containers match the Compose file: same services, images, and published ports. |
| **Drifted** | Something running differs from the file. The specific reasons are listed below the badge. |
| **Not running** | The stack is defined on disk but no containers are running. |
| **Unreachable** | Docker could not be reached, so drift cannot be assessed right now. |

## Since your last deploy

Below the status badge, a second line compares the Compose file on disk against the version you last deployed through Sencho:

| Signal | Meaning |
|--------|---------|
| **Matches last deploy** | The Compose file is unchanged since you last deployed it. |
| **Source changed** | The Compose file has been edited since the last deploy. If the change affects the model (an image, port, or service), Sencho says so; a comments or formatting only edit is called out separately. |
| **No deploy baseline** | This stack has not been deployed through Sencho yet, so there is nothing to compare against. Deploy it once to start tracking. |

This is independent of the runtime status above: a stack can be **In sync** with its running containers while its file has already **changed** for the next deploy.

## Findings

When a stack is drifted, each reason is listed against the service it affects:

| Finding | What it means |
|---------|---------------|
| **Service missing** | The Compose file declares a service, but it has no running container. |
| **Undeclared** | A container is running for the stack, but no matching service exists in the Compose file. |
| **Image** | A running container uses a different image than the Compose file declares. The expected and running values are shown side by side. |
| **Ports** | The published ports of a service differ from what the Compose file declares. |

Image references are compared after normalizing the implicit Docker Hub registry and a missing tag to `:latest`, so `nginx` and `docker.io/library/nginx:latest` are treated as the same image. A running container pinned to a digest is compared against the declared tag as written.

## Drift history

Each time you **re-check** a stack, and after every deploy, Sencho records the findings it sees. The **Drift history** list under the findings shows recent entries with when each was first **detected** and, once it clears, when it was **resolved**. This turns a point-in-time check into a short ledger of how a stack has drifted and recovered over time.

The history is labelled with when it was last checked. The status badge at the top is always live, recomputed each time you open the tab, while the history reflects the last time the ledger was reconciled. When the two differ, re-check to bring the history up to date.

Drift that appears or clears is also written to the stack's **Activity** timeline, so **Drift detected** and **Drift resolved** events sit alongside deploys and restarts.

## Accessing the Drift tab

1. Click any stack in the left sidebar to open it.
2. Switch to the **Drift** tab in the Anatomy panel header.
3. Read the status badge and any findings. Use **re-check** to run the comparison again after you deploy or change something.

On a phone, the same report appears under the **Compose** section of the stack detail.

## Troubleshooting

<AccordionGroup>
  <Accordion title="A stack I deliberately stopped shows as 'Not running'">
    That is expected. Drift compares the file on disk against what is actually running, so a stack with no running containers reports **Not running** even when you stopped it on purpose. Deploy it to return it to **In sync**.
  </Accordion>
  <Accordion title="The image finding flags a stack I just updated">
    Open **re-check** after the deploy finishes. During a rolling update, replicas can briefly run different images, and the report is a snapshot of that moment. Once every container is on the declared image, the finding clears.
  </Accordion>
  <Accordion title="A stack that uses a published port range shows a ports finding">
    A Compose port range such as `8000-8002:8000-8002` is compared conservatively and can read as a ports difference even when the deployment is correct. Sencho errs toward surfacing a possible difference rather than hiding one. The status reflects this as drift you can confirm against the file.
  </Accordion>
  <Accordion title="The status says 'Unreachable'">
    Sencho could not reach Docker on the active node, so it cannot compare the runtime. Confirm the Docker engine is running and the node is online, then use **re-check**. Other stacks on the same node will show the same state until Docker responds. While Docker is unreachable, Sencho does not change the recorded drift history, so an open finding is never cleared just because the check could not run.
  </Accordion>
  <Accordion title="The tab says 'No deploy baseline'">
    Sencho records a baseline the first time you deploy a stack through it. A stack you imported or have not yet deployed from Sencho has nothing to compare against. Deploy it once from Sencho and the line changes to **Matches last deploy**.
  </Accordion>
</AccordionGroup>
