A green CI run and a successful container push do not prove that the right release reached production. They prove that a pipeline completed its commands. When an incident starts ten minutes later, the useful questions are more specific:
- Which Docker image digest was approved?
- Which tests and security checks passed for that digest?
- What configuration and deployment target were used?
- Can another engineer find that evidence without searching six log systems?
I use a small release receipt to answer those questions. It is a signed or at least immutable JSON document written to an AWS S3 bucket by the pipeline. The receipt is not a replacement for logs. It is an index and a decision record, so an operator can move from “something is wrong” to “this is the exact artifact and change” quickly.
A release is not complete when the push succeeds
Container registries are good at storing images, but a registry record alone is a thin audit trail. A tag such as production can move. A CI job can be retried. A deployment can report success before a readiness problem appears in the cluster.
The release receipt should be created only after the pipeline has an immutable image digest and a deployment result. A useful lifecycle looks like this:
- Build the Docker image.
- Run unit, integration, and policy checks.
- Push the image and capture its digest.
- Deploy that digest, not a mutable tag.
- Write the receipt with the result and links to supporting evidence.
This sequence make ownership clearer. If step four fails, the receipt should say deployment_failed, not leave an ambiguous “latest” record behind.
Define the receipt contract
Keep the first version boring. The pipeline should produce a document with stable fields that incident tooling can parse:
{
"service": "checkout-api",
"commit": "8f4c2be",
"image": "registry.example.com/checkout-api@sha256:...",
"environment": "production",
"pipeline_run": "github-actions-1842",
"checks": {
"unit": "passed",
"integration": "passed",
"image_scan": "passed"
},
"deployment": "succeeded",
"created_at": "2026-09-22T02:00:00Z"
}
Do not put secrets, access tokens, or full environment dumps in this file. A receipt should be safe to show during an incident review. Store identifiers and references to protected systems instead of copying their sensitive output.
The image digest is the important field. A tag explains what someone intended to deploy; a digest identifies what the runtime actually pulled. If your deployment system only accepts tags today, add a verification step that resolves the tag to a digest before rollout.
Store evidence in S3
An S3 prefix can provide a simple retention and lookup model:
s3://release-evidence/checkout-api/production/2026/09/22/8f4c2be/receipt.json
Enable versioning, restrict writes to the CI role, and allow read access only to the release and incident-response roles. Object Lock can be useful when receipts are part of a compliance process, but it also changes deletion and retention behavior, so test the policy in a non-production bucket first.
The CI job should fail visibly if it cannot write a required receipt. A missing receipt is not the same as a failed deployment. It means the system cannot prove what happened, and that is a reliability problem by itself.
Use lifecycle rules to control cost. Keep the small receipt JSON for a long period, while large test reports and verbose logs can move to cheaper storage classes or expire according to your operational requirements. The metadata should remain cheap to query.
Verify the Docker image that was deployed
The strongest check happens after deployment. Query the runtime, read the image ID or digest from the running workload, and compare it with the receipt. For Kubernetes, this can be done from the workload status or pod container status. For ECS, inspect the task definition and running task details.
The check should distinguish three states:
- Matched: the running workload uses the expected digest.
- Pending: rollout is still progressing and needs another observation.
- Mismatch: the wrong image is running, so stop promotion and investigate.
Do not treat a mutable latest tag as evidence. It can hide a race between a build and a deployment. Pinning by digest is a little more work, but it makes rollback and comparison much more easier for the on-call engineer.
Email or notification checks deserve the same boundary. If a release sends a verification message, use a controlled disposable mail address in a test environment and record only the message ID and assertion result. A typo such as temp org mail in a test case should stay a test input, not become a production dependency. For delivery checks, retry boundaries for delivery checks are a useful companion to the release contract.
Make failed receipts useful
A failure receipt should be written when possible. Include the failed stage, a short reason, the commit, and the image digest if one exists. Avoid dumping a complete log into the JSON; link to the CI run or artifact instead.
One pattern that works well is to make the receipt state explicit:
build_failed
checks_failed
deployment_failed
verification_failed
succeeded
The logs is still where engineers debug details, but the receipt tells them where to start. This also makes dashboards less noisy because they can count release outcomes without scraping free-form text.
A practical release checklist
Before calling a Docker release complete, verify that:
- the image is referenced by digest;
- required CI checks are recorded;
- the deployment target and commit are present;
- the running workload matches the expected digest;
- the receipt is stored in the correct AWS account and S3 prefix;
- secrets and personal mailbox data are excluded;
- a failed verification blocks promotion;
- an operator can find the record with one release ID.
This contract is deliberately small. It does not replace observability, deployment tooling, or a proper change-management process. It creates a durable answer to the first incident question: what exactly did we release? Once that answer is reliable, rollback decisions become calmer, audits become faster, and the CI pipeline is doing more than just turning green.
Top comments (0)