Container Estate Review — Sample
A worked example of reviewing every image an organisation runs — what is in them, what they run as, the base images nobody owns, and the findings ordered by what they would let an attacker do.
Markdown. No sign-up, no email.
This is an illustrative example. The organisation, images and figures are invented to show the shape of a review that produces a short, ordered list of work. Copy the structure.
Container estate review — August 2026#
| Scope | 61 images across 4 environments |
| Method | Registry inspection, manifest review, runtime configuration audit, scan results |
| Prepared for | Platform team and engineering leads |
1. Summary#
| Images | Share | |
|---|---|---|
| Run as root | 38 | 62% |
| Base pinned by tag, not digest | 44 | 72% |
| Writable root filesystem | 51 | 84% |
| No memory limit set | 29 | 48% |
| Contain a package manager and shell | 47 | 77% |
| Built in the last 90 days | 33 | 54% |
Two findings account for most of the risk and both are one line each. Nothing in this review requires an architecture change.
2. What the images contain#
| Median | Largest | |
|---|---|---|
| Image size | 412 MB | 2.1 GB |
| Size after multi-stage rebuild (sampled 8) | 78 MB | 240 MB |
| Layers | 14 | 31 |
Eight images were rebuilt with a proper stage boundary as an experiment. Median size fell 81% and no functionality changed, because what was removed was compilers, headers, package caches and test fixtures.
The 2.1 GB image is a Python service that installs a full build toolchain to compile one dependency. The toolchain is never used again after the build.
3. Base images#
| Count | |
|---|---|
| Distinct base images | 19 |
| Pinned by digest | 17 |
| Pinned by tag | 44 |
| Base image with no named owner | 11 |
| Base built in-house, last updated 2024 | 3 |
The three in-house base images last updated in 2024 are the most serious structural finding. They are used by 22 of the 61 images, they carry known vulnerabilities in packages nobody uses, and no one is responsible for rebuilding them. They were created by a team that has since been reorganised.
The 44 tag-pinned images are a reproducibility problem rather than a security one, and they are the reason four builds this quarter produced images that behaved differently from the previous build with no code change.
4. Secrets#
| Found | |
|---|---|
| Images with credentials in an environment variable | 6 |
Images containing a .env file | 4 |
Images containing a .git directory | 9 |
| Images with a private key in a layer | 2 |
| Images where a secret was added then deleted in a later layer | 3 |
The last row is the one to explain to teams. Those three images still contain the secret. Deleting a file adds a layer recording the deletion; every earlier layer, including the one holding the file, remains in the image and is readable by anyone who can pull it.
All 11 credentials found have been rotated. The images are being rebuilt.
5. Runtime configuration#
| Images | |
|---|---|
| Non-root user | 23 |
| Root | 38 |
| Read-only root filesystem | 10 |
no-new-privileges set | 12 |
| Capabilities dropped | 8 |
| Privileged | 2 |
| Handles SIGTERM correctly | 31 |
The two privileged containers are both monitoring agents. One genuinely needs elevated access; the other was made privileged during an installation problem in 2024 and never reverted. Nobody knew.
Thirty images do not handle SIGTERM. These are the cause of the intermittent errors during deployments that three teams have reported separately and nobody has connected. The container is asked to stop, ignores the request because an unhandled signal to PID 1 is discarded, and is killed after the grace period with work in flight.
6. Ordered by what it would let an attacker do#
| Priority | Finding | Images | Effort |
|---|---|---|---|
| 1 | Private keys in layers | 2 | Rotate and rebuild — done |
| 2 | Privileged container with no need | 1 | One line |
| 3 | Credentials in environment variables | 6 | Rotate, move to secret store |
| 4 | Running as root | 38 | One line each, plus fixing what breaks |
| 5 | Unowned base images with known vulnerabilities | 22 | Assign an owner, rebuild |
| 6 | Writable root filesystem | 51 | One line, plus explicit volumes |
7. Recommendations#
- Rebuild the two images containing keys and rotate. Already in progress.
- Revert the unnecessary privileged container. One line, and it removes the largest single piece of standing access in the estate.
- Assign an owner to the three in-house base images, or retire them onto a maintained public base. Twenty-two images depend on them and nobody is responsible.
- Non-root by default in the shared build template. New images inherit it; existing images convert as they are next touched rather than in a campaign.
- Fail the build on a tag-pinned base. Stops the reproducibility problem growing.
- Add SIGTERM handling to the shared service template. Fixes the deployment-window errors for every service that adopts it.
Deliberately not recommended: a full re-platform, or converting all 61 images at once. Items 1–3 are a week. Items 4–6 change the default so the estate improves as it is worked on.
Notes on using this format#
Count what images contain, not just whether they scan clean. A scanner reports known vulnerabilities. It does not tell you that 47 images ship a package manager and a shell that nothing uses.
Order by what the finding permits. Root in 38 images sounds worse than a private key in 2. The key is a credential an attacker already has.
Separate the one-line fixes from the campaigns. Six of the seven finding types are a single line per image. Presented together with a re-platform proposal, none of them get done.