Skip to main content

Immutable release approval and rollback

Sailr supports two deployment contracts. Existing workflow run profiles keep the in-process transactional deployment gate. Portable releases add an offline prepare phase and apply only the canonical bytes stored in a sailr.deployment-bundle/v1 file. Legacy deploy and go behavior is unchanged.

Portable release profile

[workflow.production]
environment = "production"
mode = "deploy"
engine = "runkernel"
interactive = false
build = "disabled"
push = "disabled"
generate = "run"
deploy = "run"
deploy_context = "production-cluster"
namespace = "app"
approval = "signature"
apply = true

[workflow.production.signature]
trusted_public_key = "<base64-encoded raw 32-byte Ed25519 public key>"

[workflow.production.verification]
rollout_timeout_seconds = 300

[workflow.production.rollback]
timeout_seconds = 300

The trusted public key is repository policy. Sailr never reads a replacement key from the runtime environment and reports only its sha256:<hex> fingerprint. The private key must remain outside Sailr.

The target environment may strengthen approval and configure release locking:

[deployment_policy]
required_approval = "signature"

[deployment_policy.release_lock]
lease_duration_seconds = 60
renew_interval_seconds = 20
# lease_name = "optional-fixed-name"
# lease_namespace = "optional-lock-namespace"

Environment names carry no implicit security meaning.

Publication, promotion, and preparation

Every image-bearing workload for a build-backed service must use {{service_image}}. Normal generation resolves it to the tagged service image. Portable preparation replaces it in memory with the reviewed digest reference and verifies that every promoted image is present in the exact bundled workload payload. Services without a build configuration are external dependencies and retain explicit vendor image repositories with {{service_version}} in their templates. Omitting {{service_version}} for an external workload emits a warning but does not fail initialization or preparation.

# One-time setup: generate the safe publication profile from an environment.
sailr publication init publish-staging --environment staging

# CI: explicit --apply consent permits registry push, never Kubernetes deploy.
# Sailr produces and validates the report; no report-generation script is needed.
sailr publication run publish-staging \
--apply \
--out artifacts/publication-report.json

sailr promote plan \
--from-report artifacts/publication-report.json \
--to production \
--out artifacts/promotion-plan.json
sailr workflow prepare production \
--promotion-plan artifacts/promotion-plan.json \
--out artifacts/prepared-release

Preparation is offline with respect to Docker, registries, Git, hooks, and Kubernetes. It creates deployment.bundle, deployment-plan.json, deployment.diff, and preparation-evidence.json in a new output directory. It rejects pre-deployment hooks. Exact post-deployment hook commands are included in the bundle; database migrations should remain separate CI stages.

Candidate-selection adapters may instead write sailr.release-candidates/v1. Every entry binds a report path, relative to the manifest, to the report's canonical Sailr digest. Use --from-manifest; Sailr revalidates the report and digest before promotion. Direct and manifest selection produce the same promotion plan for the same reports.

Signing and applying

Sign the UTF-8 bytes below with the configured Ed25519 private key:

sailr-deployment-plan-v1:<64-character-plan-hash>

Then provide only the base64-encoded raw 64-byte signature:

export DEPLOY_APPROVAL_SIG="<base64 signature>"
sailr workflow apply production \
--bundle artifacts/prepared-release/deployment.bundle \
--non-interactive \
--apply \
--release-id "$CI_RELEASE_ID"

Apply revalidates the bundle, current profile, environment approval policy, target, service coverage, target registry/repository policy, and signer fingerprint before Kubernetes access. It never opens templates or regenerates manifests.

After approval, Sailr acquires a namespaced Kubernetes Lease. The default name is derived from the environment, context, and namespace. The Lease is renewed through apply, rollout checks, post-deployment hooks, and rollback; loss of ownership stops forward mutation. Process death is recovered through Lease expiry without changing application resources.

Deployments, StatefulSets, and DaemonSets are polled for typed readiness every two seconds. Apply, rollout, or post-hook failure triggers reverse-journal rollback within the configured timeout. The resulting sailr.workflow-report/v1 records approval, bundle and provenance digests, lock, rollout, deployment, and rollback evidence.

Release outcomes distinguish bundle validation, approval, unavailable Kubernetes targets, lock, apply, rollout verification, and post-hook failures. External approval is recorded as satisfied but not cryptographically verified by Sailr. Reports are written atomically even when failure occurs before mutation.