Put dependency, code and image scanning in the build stage, config and policy checks in the release stage, and keep the run stage as plain as possible. Twelve-factor separates the three stages strictly, which gives you three natural points to stop a bad change before it reaches users.
The three stages in plain terms
The methodology defines them this way. The build stage turns a code repo at a given commit into an executable bundle, fetching dependencies and compiling assets. The release stage combines that build with the deploy's current config. The run stage launches processes from a chosen release.
Two rules matter for security. Releases are an append-only ledger with a unique ID, and a release cannot be changed once created, so any change makes a new release. And you cannot change code at runtime, because there is no path to push changes back to the build stage.
What to check at each stage
| Stage | Checks that fit | Why here |
|---|---|---|
| Build | Dependency scan, SBOM generation, static analysis, container image scan, signing | Input is source and packages, and errors show up in front of the developer |
| Release | Config review, policy gates, approval, provenance check | The release is the exact thing that will run, so this is the last cheap place to block it |
| Run | Admission rules, runtime limits, monitoring | Few moving parts, because failures here happen at night with nobody watching |
The methodology notes that the build stage can be more complex because a developer is present to read errors, while the run stage should have as few moving parts as possible. Apply the same logic to security tooling: heavy analysis belongs where a human is waiting, and the run stage should only enforce simple, fast decisions.
Why immutability matters
Because a release cannot be mutated, you can answer "what exactly was running at 3 pm on Tuesday?" with a release ID. That is useful evidence in an incident or an audit. It also means a hotfix applied directly to a server is outside the model, which is exactly why it is risky: the change is not in any release, not scanned, and not reproducible.
If your tooling lets people edit running containers or SSH into hosts and patch files, you have a gap between the model and reality. Closing it is a security control in its own right. See also why pinning image digests beats tags.
A minimal setup
- Fail the build on known critical issues in direct and transitive dependencies.
- Produce an SBOM at build time and store it with the release ID.
- At release, check that the config is complete and contains no plaintext credentials.
- Require a review or approval for changes to production config.
- Deploy by release ID or image digest, never by a mutable tag.
- Keep rollback to a previous release one command, and record who ran it.
Try it yourself
Click through it at your own pace, or open it full screen.
Click through the pull request gate and notice which result stops the merge, since that is the same decision you want at the build and release boundary.
Quick check
Should security scanning run at runtime instead of build time?
Do both, but start at build time. Findings there are cheaper to fix and block the change before it ships. Runtime checks cover what changes after release.
What is a release in twelve-factor terms?
A build combined with the deploy's config, with a unique ID. It cannot be modified after creation, so any change produces a new release.
Can I patch a running container directly?
You can, but it falls outside the build, release, run model. The change is not scanned or reproducible, so make the fix in code and ship a new release.
Where Safeguard fits
Safeguard policies and gates can block a pull request or release that fails your rules, and dependency scanning and SBOM generation run against the build. Fix pull requests for vulnerable dependencies are available through autonomous remediation, which is off by default with every category on Manual. Try a free scan.
Sources
Last reviewed: 19 October 2026.