New to Kestra?
Use blueprints to kickstart your first workflows.
Verify a pinned container image digest still resolves from its registry with Dagger, classify integrity status, and alert Slack on breach.
A deployment manifest pinned to an exact image digest is supposed to be immutable, but the registry side is not: digests get garbage-collected, images get deleted for licensing or security reasons, and retention policies silently expire old manifests. The first sign is usually a failed deploy at the worst possible time. This blueprint uses plugin-dagger's native Commands task as the probe, running a containerized Dagger pipeline that attempts to pull the exact pinned digest and classifies whether the registry still serves it.
verify_pinned_digest (io.kestra.plugin.dagger.Commands) runs container | from <pinned_image_digest_ref> | with-exec echo pinned-digest-resolved | stdout via dagger shell -c on a Docker-based task runner. The task's own install step pulls a pinned Dagger CLI release and verifies it against the official checksums file before use. allowFailure: true lets the flow continue instead of aborting when the pull or exec fails.classify_digest_integrity (io.kestra.plugin.core.debug.Return) checks outputs.verify_pinned_digest.exitCode. If it is not defined at all (the task never produced output, for example a Docker runner or Dagger install failure), the classification is PROBE_FAILED. If it is defined and non-zero, the classification is DIGEST_MISSING, meaning Dagger ran but the registry rejected the pinned digest. If it is exactly zero, the classification is PINNED_DIGEST_INTACT.route_by_digest_status (io.kestra.plugin.core.flow.Switch) branches on that string. DIGEST_MISSING runs evaluate_alert_toggle (io.kestra.plugin.core.flow.If), which pages Slack via dispatch_digest_missing_alert only when alert_on_missing_digest is true, logging a suppressed-alert line otherwise. PINNED_DIGEST_INTACT logs a clean audit line. PROBE_FAILED pages Slack directly since integrity is now unknown. The defaults branch logs a fallback line that is unreachable under normal operation.log_audit_summary records a status line on every run regardless of branch, so execution history reads as a continuous digest-integrity trend.errors block pages Slack separately if the sentinel flow itself fails outright.digest_integrity_schedule trigger polls every 6 hours with explicit input overrides, shipped disabled: true until pointed at a real pinned digest.status output (PINNED_DIGEST_INTACT / DIGEST_MISSING / PROBE_FAILED) other flows can gate on, for example before promoting a release that references the same digest.A one-off docker pull from a laptop proves a digest is resolvable for one instant and leaves no record. This flow keeps every check in Kestra's execution history, turns the result into a typed status output other flows (release gates, promotion pipelines) can depend on, and gives you an If/Switch branch to grow into real remediation (re-pinning to a new approved digest, opening a ticket) without rewriting the probe itself.
verify_pinned_digest runs on the io.kestra.plugin.scripts.runner.docker.Docker task runner.SLACK_WEBHOOK secret.pinned_image_digest_ref with a real registry/image@sha256:digest your deployments depend on.status reads PINNED_DIGEST_INTACT.digest_integrity_schedule.Loop task to audit a whole fleet in one run.DIGEST_MISSING, automatically open a ticket or trigger a re-pin workflow instead of only alerting.status into a KV store to alert only on state transitions instead of every run.SLACK_WEBHOOK: Slack incoming webhook used for the breach, probe-failure, and flow-failure alerts.SECRET_SLACK_WEBHOOK as base64, for example echo -n "<your-webhook-url>" | base64.pinned_image_digest_ref (STRING, default <your-registry>/<your-image>@sha256:<your-pinned-digest>): fully qualified pinned image reference to audit.dagger_container_image (STRING, default curlimages/curl:latest): base image the Dagger CLI installs into.alert_on_missing_digest (STRING, default "true"): when "true", page Slack on a missing digest; when "false", only log it.{{ outputs.verify_pinned_digest.exitCode }}: exit code of the Dagger pipeline invocation.{{ outputs.classify_digest_integrity.value }}: classification string, one of PINNED_DIGEST_INTACT, DIGEST_MISSING, PROBE_FAILED.{{ outputs.status }}: flow-level alias of the same classification.image-ref, the Dagger Container API function that returns a resolved reference, can only be called immediately after a from() call; it is not used here because chaining it with a comparison inside the same pipeline is not something this blueprint asserts without having executed it. The probe instead proves resolvability indirectly through with-exec, which is the mechanism actually verified against plugin-dagger's source and documented CLI examples.plugin-dagger's ScriptOutput does not expose raw stdout text as a queryable field, only vars (populated solely via Kestra's ::{"outputs":...}:: marker convention), exitCode, and outputFiles. This blueprint classifies on exitCode alone for that reason; it does not attempt to parse the Dagger pipeline's printed output.PROBE_FAILED, not DIGEST_MISSING.dagger_container_image must provide curl, tar, uname, and sha256sum, since the task's install step downloads and checksum-verifies the pinned Dagger CLI release on first use.:latest) always "resolves" to whatever it currently points at; this gate is only meaningful against a true @sha256: digest reference, not a tag.This flow needs a Docker daemon reachable from the Kestra Worker, not a separate broker container. If Kestra itself runs in Docker, confirm the Worker container can reach the host's Docker socket, and that the Worker's Docker network can resolve the image registry you are auditing:
docker network connect YOUR_KESTRA_NETWORK
(Run docker network ls to verify your active Kestra network name.)
