Skip to content

imglife eol

imglife eol update [flags]
imglife eol notify [flags]

Fetches EOL data from endoflife.date for every product referenced in sync.entries[].lifecycle.product and writes the result to eol_data_file.

Flag Default Description
--dry-run false Fetch and display data without writing
--prune false Rebuild the cache from the collected cycles only, dropping the others
--data-file string eol-data.yaml Path to the output file (overrides lifecycle.eol_data_file)
Terminal window
# Refresh EOL data
imglife eol update
# Dry-run: fetch and display without writing
imglife eol update --dry-run
# Drop the cycles that are no longer used by any image
imglife eol update --prune
# Write to a custom path
imglife eol update --data-file /tmp/eol-cache.yaml

Run imglife eol update periodically (e.g. weekly) to keep EOL dates current. In CI, add it as a scheduled job:

# GitLab CI scheduled job
eol-update:
stage: eol
image: registry.gitlab.com/imglife-project/imglife:latest
script:
- imglife eol update
- |
if git diff --quiet eol-data.yaml; then
echo "EOL data unchanged"
else
git add eol-data.yaml
git commit -m "chore(lifecycle): update EOL data"
git push
fi
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"

The set of products to fetch is derived from sync.entries[].lifecycle.product in your imglife.yaml. Only products with a lifecycle: block are fetched.

sync:
entries:
- source: docker.io/library/alpine
lifecycle:
product: alpine # ← will be fetched
- source: docker.io/library/golang
lifecycle:
product: go # ← will be fetched
- source: docker.io/library/nginx
# no lifecycle: block — not fetched

Cycles come from two sources:

  1. Mirror tags — the tags present in the target registry for each sync entry with a lifecycle: block.
  2. Applicative build records — the base image of every build record published in the Package Registry.

The second source matters when an application stays on a version line whose mirror tags have already been removed by keep_last: without it, the EOL data for that cycle would never be fetched and status would show ⚠️ N/A. It requires the Package Registry token; when the token is missing, a warning is logged and only the mirror cycles are collected.

eol update merges the fresh data into the existing cache (local file or Package Registry): a cycle already known is kept even once no mirror tag references it. Fresh data wins on conflict.

--prune skips the merge and rebuilds the cache from the collected cycles only — the only way to remove a cycle that is no longer in use.

status, check and eol notify resolve a cycle that is absent from the cache by falling back on the neighboring cycles:

Case Result
Cycle newer than anything endoflife.date knows Inherits the previous cycle’s status if that cycle is still supported
Cycle older than every known cycle ⚠️ outdated — no status is fabricated
Unknown product ⚠️ N/A with the reason in the tooltip

By default, the data is written to a local file (git target). For environments where CI cannot push to git, switch to pkgregistry:

lifecycle:
eol_target: pkgregistry

The file is then stored in the Package Registry and read from there by imglife status and imglife check.

Code Meaning
0 Data fetched and written successfully
1 Fetch or write error

Evaluates all lifecycle entries and sends alerts to Slack or generic webhooks for images at or above the configured status threshold.

Flag Default Description
--dry-run false Print alerts without sending
--min-status string warning Minimum alert level: warning, critical, eol (overrides config)
Terminal window
# Send notifications using config defaults
imglife eol notify
# Preview without sending
imglife eol notify --dry-run
# Only notify for critical/EOL images
imglife eol notify --min-status critical

Destinations are configured in imglife.yaml under lifecycle.notifications:

lifecycle:
notifications:
min_status: warning # warning | critical | eol
slack_webhook_url: "${SLACK_WEBHOOK_URL}"
webhooks:
- url: "${MY_WEBHOOK_URL}"
headers:
Authorization: "Bearer ${TOKEN}"

Each base alert gets its own section block with associated applicative images grouped into two lists: Apps to update (a newer base tag is available) and Apps on latest (already on the most recent tag). A divider separates alerts; a context block closes the message.

{
"blocks": [
{"type": "header", "text": {"type": "plain_text", "text": "🚨 imglife Lifecycle Alerts"}},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🟡 *python/3.12* — WARNING · EOL: 2028-10-31\n*Apps to update:*\n • reg/apps/myapp:1.0.0\n `3.12.0-core1.0.0` → `3.12.5-core1.0.0`"
}
},
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🔴 *alpine/3.17* — EOL exceeded · EOL: 2023-11-01\n*Apps on latest (no newer version available):*\n • reg/apps/legacy:2.0.0 (base: `3.17.9-core1.0.0`)"
}
},
{"type": "context", "elements": [{"type": "mrkdwn", "text": "Generated by *imglife* · 2026-04-22"}]}
]
}

The latest_base field is populated only for applicative alerts (applicative_image non-empty). It contains the most recent available base tag when an upgrade exists, or is omitted when the app is already on the latest tag.

{
"alerts": [
{
"product": "python",
"cycle": "3.12",
"status": "WARNING",
"eol_date": "2028-10-31",
"applicative_image": "reg/apps/myapp:1.0.0",
"base_image": "reg/base/python:3.12.0-core1.0.0",
"latest_base": "3.12.5-core1.0.0"
},
{
"product": "alpine",
"cycle": "3.17",
"status": "EOL",
"eol_date": "2023-11-01",
"applicative_image": "reg/apps/legacy:2.0.0",
"base_image": "reg/base/alpine:3.17.9-core1.0.0"
}
]
}
[dry-run] Would send 2 base alert(s) + 2 app alert(s) to 1 destination(s):
python/3.12 WARNING EOL: 2028-10-31
→ reg/apps/myapp:1.0.0 (update: 3.12.0-core1.0.0 → 3.12.5-core1.0.0)
alpine/3.17 EOL EOL: 2023-11-01
→ reg/apps/legacy:2.0.0 (on latest, no upgrade available)
Code Meaning
0 Success (even when alerts are present)
1 Error (config invalid, HTTP send failure, etc.)