Builds, releases, deployments — how they connect
The model
| Concept | What it is | Created by |
|---|---|---|
| Build | an image built from a commit (<registry>/<repo>:<commit>), identified by its digest |
a push to the platform registry, or a deployment that reports it |
| Release | a version of the service (1.9.0), tied to a commit |
a deployment that names a version (matelot deploy --version), a git tag / GitHub or GitLab release sync, or Record release |
| Deployment | "this image / release now runs in this environment" | matelot deploy, matelot promote, a push to an environment repository, Kubernetes sync |
A release does not have an environment — a deployment puts it there. One release can run in several environments (test, then prod, then a DR region); the graph draws release → image → environment for every current deployment.
Standard flow
commit ──build──► image demo:<sha> ──deploy──► dev (no release: a build of main)
tag v1.9.0-rc1 ──► image …:v1.9.0-rc1 ──deploy──► test release 1.9.0-rc1 created by the deployment
tag v1.9.0 ──► image …:v1.9.0 ──deploy──► prod release 1.9.0 created by the deployment
Every deploy step reports --version (for rc and final), --image <image>@<digest> and --commit.
What the platform guarantees
- A deployment that names a version creates the release if it does not exist yet (version-like names
only), with the commit from
--commit/SECOPS_COMMIT; audited asrelease.created. Git tag sync later enriches the same release (URL, notes, date) — it no longer depends on webhooks reaching the cell. - A digest is one image, whatever registry or tag it lives under:
- a deployment reported by digest from a registry the platform has not read yet triggers an immediate registry read, and every later sync attaches digest-only deployments to their tag;
- the security graph, Vulnerabilities → Running in, the trend snapshots and the correlation all resolve "what runs here" by digest;
- the same image pushed to several registries is scanned once (one SBOM per digest).
- Tags link to releases by version (
v1.9.0↔1.9.0), by commit (<sha>tags), and by a deployment that names both a version and an image.
Built-in registry: what records a deployment
matelot promoterecords the deployment itself, the moment the copy is done (no dependency on registry notifications reaching the API or on the reconcile loop).- A real
docker pushinto an environment repository is recorded by the registry notification. - The reconcile pass (every 2 minutes, or POST /organizations/:id/registry/reconcile) only fills an environment that has nothing recorded, or records a tag built after the current deployment (a push the notification missed). It never overwrites a deployment reported from another registry, by hand or by Kubernetes — older tags lying in the environment repository are ignored. All three paths are serialised per environment, so they cannot record the same deployment twice.
Environment lineage (Security graph → Environments)
Per environment, two lanes: Released — repository (commit) → release (version tag) → registries holding that
tag → environment; Running now — repository (commit of the running build) → image digest → registries
holding that digest (the one it was deployed from marked) → environment. Only the digest identifies what runs;
a tag is a pointer that can move. Drift is flagged when the lanes disagree: the version tag was rebuilt
after the deployment (tag_moved), the tag points to different images in different registries
(tag_split), the running digest is in no known registry (unknown_image), no release exists for the
deployed version (no_release), the running image was never scanned (not_scanned), a registry sync fails.
API: GET /environments/:id/lineage.
Where to see it
Releases & images (release card: images + environment chips) · Running now · Security graph (hover a
release) · Vulnerabilities → Running in · Audit (release.created, deployment.recorded).
Matelot