Skip to content

Three-Tier Model

A typical organisation using Docker faces several compounding problems:

  • Registry sprawl — every team pulls from Docker Hub directly, creating hidden dependencies on external availability and licensing.
  • Base image drift — different projects use different versions of the same base image without coordination.
  • EOL blindness — nobody knows which images are approaching end-of-life until a CVE scanner screams.
  • Audit gaps — it’s impossible to answer “which applications are using Alpine 3.19 right now?”

imglife addresses all of these with a single architecture.

Mirrors are direct, unmodified copies of upstream public images stored in your private registry.

docker.io/library/alpine:3.21.3 ──copy──▶ registry.example.com/mirrors/alpine:3.21.3
docker.io/library/golang:1.22 ──copy──▶ registry.example.com/mirrors/golang:1.22
quay.io/prometheus/prometheus ──copy──▶ registry.example.com/mirrors/prometheus:...

Why mirrors?

  • Your builds never depend on external network availability.
  • You control which upstream tags exist in your environment.
  • Docker Hub rate limiting no longer affects your CI pipelines.
  • You have a complete audit trail of when each upstream tag was pulled.

Mirrors are managed by imglife sync.

Base images are built on top of your mirrors. They add organisation-standard tooling, security configurations, and metadata. This is where your team’s policies are enforced.

registry.example.com/mirrors/alpine:3.21.3
+ Dockerfile template (ca-certificates, tzdata, org CA, etc.)
+ OCI labels (build date, git revision, EOL date, base image ref)
──build──▶ registry.example.com/bases/alpine:3.21.3-core1.0.0

The tag format encodes both the upstream version and your organisation’s build version: <upstream-version>-core<org-version>. This means:

  • alpine:3.21.3-core1.0.0 → upstream Alpine 3.21.3, first org build version
  • alpine:3.21.3-core1.1.0 → same upstream, updated org configuration

Base images are managed by imglife build.

Applicative images are the final output built by individual teams. They use your base images as their FROM:

ARG BASE_IMAGE
FROM ${BASE_IMAGE}
COPY myapp /usr/local/bin/
CMD ["myapp"]

The key insight: applicative teams don’t build their own base. They consume the one you publish. When you update your base (new Alpine version, patched tooling), they rebuild with the new base and record the fact using imglife register.

When an applicative image is built, imglife register stores a JSON record in the Package Registry linking:

  • The applicative image tag (registry.example.com/apps/myservice:1.2.0)
  • The base image it was built on (registry.example.com/bases/alpine:3.21.3-core1.0.0)
  • The git revision and project name
  • The build timestamp

This creates a complete dependency graph. When you run imglife status, it can answer: “Which applications are still on Alpine 3.19?”

imglife status generates a Markdown README with three tables — one per tier. Each row shows:

ImageVersionPlatformsEOLStatus
alpine3.21.3-core1.0.0linux/amd64, linux/arm642026-11-01✅ OK
golang1.21.6-core1.0.0linux/amd64❌ EOL❌ Expired

The status column aggregates EOL data (from endoflife.date) with your retention policy.

The imglife check command is designed to run in applicative CI pipelines. It:

  1. Reads the BASE_IMAGE used in the Dockerfile.
  2. Queries the Package Registry for the latest base image version.
  3. Compares the base image with the latest available.
  4. Reports if the base is outdated or approaching EOL.

With --strict, it fails the CI job if the base is out of date, enforcing that applicative teams stay current.

TierManaged byProduced byRegistry path
Mirrorsimglife syncBase images repo CIregistry/mirrors/
Basesimglife buildBase images repo CIregistry/bases/
ApplicativeApp teamsApp repo CIregistry/apps/