---
title: "Deploy Enforcement"
description: "Block deploys that violate a scan policy before docker compose up runs, with an admin bypass path and a full audit trail."
---

Deploy enforcement is the pre-flight half of Sencho's vulnerability workflow. When a [scan policy](/features/vulnerability-scanning#scan-policies) with **Block on deploy** enabled matches a stack, Sencho scans every image referenced by the stack's compose file before starting any container. A policy gates on exploitation risk: a known-exploited CVE (CISA KEV), a fixable Critical/High finding, or a raw severity threshold. If any image matches an enabled condition, the deploy is rejected and the stack never starts. Detection always continues post-deploy and on a schedule, so images that develop new vulnerabilities after the initial deploy still surface through alerts.

<Note>
  Deploy enforcement and scan policies require an **Admiral** license.
</Note>

## Configuring a block policy

Policies are managed on the **Security** page → **Policies** tab. The **Add policy** button opens the editor; existing policies appear as a list of cards with a badge per active block condition (`KEV`, `Fixable`, and `max: <SEVERITY>` when the severity threshold is on), a `block` badge, the configured stack-pattern scope, and pencil and trash buttons.

<Frame>
  <img src="/images/deploy-enforcement/policy-list.png" alt="Scan Policies card showing a configured policy with the name 'Production block on critical', a max: CRITICAL badge, a block badge, and the Scope demo-blocked-* set as the stack pattern" />
</Frame>

The editor exposes the fields that govern enforcement:

<Frame>
  <img src="/images/deploy-enforcement/policy-edit-modal.png" alt="New policy modal with kicker SECURITY · NEW POLICY, fields for Name, Stack pattern, the Block conditions group (Severity threshold, Known-exploited (KEV), Fixable Critical/High), a Block on deploy toggle, and an Enabled toggle" />
</Frame>

| Field | Purpose |
|-------|---------|
| **Name** | A descriptive label that appears on the block dialog and in audit log entries. |
| **Stack pattern (optional)** | Glob-style match against stack names. `prod-*` matches `prod-api` but not `production-api`. Leave blank to apply to every stack on the node. |
| **Block conditions** | What makes the policy fire. Enable any combination: **Severity threshold** (highest finding meets or exceeds the chosen severity), **Known-exploited (KEV)** (a CVE on the CISA known-exploited list), and **Fixable Critical/High** (a Critical or High finding with a fix available). At least one is required to block on deploy. |
| **Block on deploy** | When on, the pre-flight gate hard-rejects deploys that match any block condition. When off, the policy still evaluates post-deploy and scheduled scans and dispatches warning alerts on matches. |
| **Enabled** | Disabled policies are skipped during evaluation. |

New policies default to a risk-first posture: known-exploited and fixable conditions on, the severity threshold off until you turn it on. CVSS is always captured and shown for context but is never the sole basis for a block. A finding whose exploitability cannot be confirmed is treated as risky, not safe.

The editor sets the pattern, the block conditions, and the toggles. Per-node scoping is set via the [Security API](/api-reference/security#scan-policies) (`node_id`) or replicated from a control node via [Fleet Federation](/features/fleet-federation). When more than one enabled policy matches a stack on the target node, a node-scoped policy wins over a fleet-wide one, a policy with a stack pattern wins over a catch-all, and the lowest-numbered policy breaks any remaining tie.

## How enforcement runs

Sencho applies the pre-flight gate on every code path that can start a compose stack:

- **Deploy** from the stack page.
- **Update** (re-pull plus restart).
- **Rollback** to a saved backup, which restores the previous files and re-runs the gate before redeploying.
- **Deploy from a Git Source**, both the initial deploy at create time and a manual apply-with-deploy of a pulled commit.
- **Template deploy** from the App Store.
- **Bulk deploy** from the [Stack Labels](/features/stack-labels) page.
- **Fleet deploy** to a node from the fleet view.
- **Webhook-triggered deploys**.
- **Mesh cascade redeploys** when a dependency change ripples to a stack.
- **Scheduled auto-start and auto-update** (see [Auto-update scheduler interaction](#auto-update-scheduler-interaction)).

On every one of these actions, Sencho:

1. Picks the matching enabled policy by precedence (node-scoped over fleet-wide, stack-pattern over catch-all, then lowest id) on the target node.
2. If the policy has **Block on deploy** off, lets the deploy proceed and evaluates the post-deploy scan against the policy for alerting.
3. If **Block on deploy** is on, enumerates the stack's images with `docker compose config --images`, runs a pre-flight Trivy scan against each one, and evaluates each enabled block condition (known-exploited, fixable, severity threshold) against the scan's non-suppressed findings.
4. If no image matches a block condition, the deploy proceeds. A post-deploy drift scan still runs in the background.
5. If the compose file fails to parse, enforcement fails closed: the deploy is rejected with a single synthetic violation labeled `(compose parse error)` so a malformed file cannot slip past the gate.
6. If any image matches a block condition, the deploy is rejected with HTTP `409 Conflict` and the stack never starts. The UI opens a dialog listing the offending images and the conditions each one matched.

Pre-flight scans use the same 24-hour digest cache as on-demand scans, so the second deploy of the same image does not pay the full scan time.

## What the block dialog shows

<Frame>
  <img src="/images/deploy-enforcement/block-dialog.png" alt="Deploy blocked dialog with kicker DEMO-BLOCKED-APP · SCAN POLICY · BLOCKED, title 'Deploy blocked by security policy', a row listing the offending image redis:7.0-alpine with 8 critical · 59 high counts and a CRITICAL severity chip, a Close button, and a destructive Deploy anyway button" />
</Frame>

The dialog shows:

- A kicker with the stack name, the words **SCAN POLICY**, and **BLOCKED** in the destructive accent color.
- The policy name and a sentence naming the conditions the policy blocks on.
- One row per offending image, with the image reference in monospace, the counts of critical and high findings (plus known-exploited and fixable counts when present), a badge for each matched condition, and a severity chip in the matching color.
- A **Close** button that dismisses the dialog without deploying.
- A destructive **Deploy anyway** button when the current user is an admin (see bypass below). For non-admin sessions the primary slot is replaced by a disabled outline button labeled **Admin required to bypass**.

The dialog is informational; the rows are not interactive. To dig into individual findings, open the stack's image in the [Resources Hub](/features/resources) and review the scan from there or jump straight from the Vulnerability Scanner [scan results drawer](/features/vulnerability-scanning#the-scan-results-drawer).

## Bypassing a block

Blocks can be overridden by admins on a per-deploy basis. The **Deploy anyway** button is only active when:

- The current user has the `admin` role.
- The deploy came from the UI. The bypass flag is ignored when the caller role is not admin server-side, so non-admin sessions cannot forge the header.

Every bypass is recorded in the [Audit Log](/features/audit-log) with:

- `method` and `path` of the originating request, so you can tell whether the bypass came from a deploy, update, template, or git-from call.
- `username` of the admin who bypassed.
- A `policy.bypass` summary in the format `policy.bypass stack="<name>" policy="<policyName>" violations=<n> images=[<comma-joined-refs>]`.

API callers can pass `?ignorePolicy=true` on any deploy endpoint to request a bypass. The flag is only honored when the token's session resolves to a user with the `admin` role; tokens whose user is not an admin cannot bypass a policy. See the [Security API reference](/api-reference/security#bypassing-a-block) for the exact request shape.

### Auto-update scheduler interaction

The auto-update scheduler runs deploys without an interactive user, so the bypass flag does not apply. When a scheduled auto-update is rejected by a policy, Sencho dispatches a `scan_finding` warning alert with the stack name, the policy name, and the offending images, then skips that stack and continues the rest of the schedule. Re-enabling that stack in auto-update means either bringing the image down to compliant severity or relaxing the policy. A scheduled auto-start that is rejected is reported as a failed run with the same warning alert.

## Drift detection keeps running

Deploy enforcement prevents a new deploy from introducing known vulnerabilities at the front door. Long-running stacks whose images were clean at deploy time can still develop new CVEs as upstream feeds update. Two surfaces catch this:

- **Post-deploy scans** run automatically on every successful deploy, whether the pre-flight gate was tripped or not. When a post-deploy scan violates an enabled policy, Sencho dispatches a warning alert and the scan details sheet shows a policy-violation banner.
- **[Scheduled fleet scans](/features/vulnerability-scanning#scheduled-fleet-scans)** re-scan every image on a node at a cron schedule you pick. Any scan that violates a matching policy produces the same warning alert and banner, so you never need to babysit running stacks.

Neither drift mechanism blocks, stops, or quarantines a running stack automatically. The gate is intentionally limited to deploy time.

## Troubleshooting

<AccordionGroup>
  <Accordion title="CI pipelines feel slower after I enabled a block policy">
    Only the first deploy of a new image pays the full Trivy runtime. Sencho caches scan results by image digest for 24 hours, so repeat deploys of the same image hit the cache in under a second. For greenfield CI where every deploy ships a new image tag, budget 30 to 120 seconds per image on the first deploy depending on image size and whether Trivy's vulnerability database is already seeded on the node.
  </Accordion>
  <Accordion title="The gate let a deploy through even though I have a block policy">
    Check the following in order:

    1. Open the **Security** page → **Scanner setup** tab on the target node and confirm Trivy is installed. Sencho fails open when Trivy is missing, dispatching a warning alert instead of blocking. [Install Trivy](/operations/trivy-setup) to enforce the policy.
    2. Confirm the policy is enabled and the stack pattern matches the stack name. `prod-*` matches `prod-api` but not `production-api`. An empty pattern matches every stack on the node.
    3. Check the latest scan for each image against the policy's block conditions. If no image carried a known-exploited CVE, a fixable Critical/High finding, or (when the severity threshold is on) a finding at or above the threshold, the gate correctly allowed the deploy.
  </Accordion>
  <Accordion title="A deploy is blocked and I cannot bypass as a non-admin">
    Only users with the `admin` role can bypass a block. Ask an admin to review the violations and either bypass the single deploy, upgrade the base image, or [suppress](/features/cve-suppressions) the offending CVE with an expiry.
  </Accordion>
  <Accordion title="The block dialog mentions an image I do not recognize">
    Pre-flight enumerates images via `docker compose config --images`, which expands any `extends`, `env_file`, or variable substitution in the compose file. An image pulled by a dependency you did not author may appear in the list. Review the stack's compose file and confirm the reference.
  </Accordion>
  <Accordion title="The block dialog says (compose parse error)">
    Enforcement fails closed when the compose file cannot be parsed. The synthetic `(compose parse error)` violation prevents a malformed file from slipping past the gate. Open the stack's [file explorer](/features/stack-file-explorer) and fix the YAML; the deploy succeeds once the file parses cleanly and every image clears the threshold.
  </Accordion>
  <Accordion title="The auto-update scheduler skipped a stack I expected to roll forward">
    A scheduled auto-update that pulls an image violating a matching policy is skipped, not blocked with a 409. The skip is announced through a `scan_finding` warning alert naming the policy and the offending images. To unblock the schedule, either upgrade the image to a compliant version or relax the policy threshold for that stack.
  </Accordion>
  <Accordion title="I want a block policy that only applies to a subset of my fleet">
    Combine two mechanisms: scope the policy to a specific node (policies scoped to a node win over global ones) and tighten the stack-pattern glob. For example, a policy with `stack_pattern=prod-*` scoped to your production node fires only on `prod-*` stacks deployed to that node.
  </Accordion>
</AccordionGroup>
