id: docker-hub-rate-limit-and-image-pull-backpressure-sentinel
namespace: company.infrastructure
inputs:
- id: dockerhub_username
type: STRING
required: false
description: Docker Hub username for authenticated rate limit checks (leave
empty for anonymous checks).
- id: remaining_pull_threshold
type: INT
defaults: 25
description: Threshold of remaining pull requests before triggering an alert.
- id: alert_on_breach
type: BOOL
defaults: true
description: Whether to dispatch a Slack notification when the quota is below
the safety threshold.
tasks:
- id: inspect_docker_rate_limit
type: io.kestra.plugin.scripts.python.Script
description: Queries the Docker Hub registry token endpoint and extracts active
rate limit headers.
containerImage: python:3.11-slim
beforeCommands:
- pip install requests
env:
DOCKERHUB_USERNAME: "{{ inputs.dockerhub_username }}"
DOCKERHUB_PAT: "{{ secret('DOCKERHUB_PAT') }}"
script: |
import os
import requests
from kestra import Kestra
username = os.environ.get("DOCKERHUB_USERNAME", "").strip()
pat = os.environ.get("DOCKERHUB_PAT", "").strip()
auth = None
if username and pat:
auth = (username, pat)
token_resp = requests.get(
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull",
auth=auth,
timeout=15
)
token_resp.raise_for_status()
token = token_resp.json().get("token")
head_resp = requests.head(
"https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest",
headers={"Authorization": f"Bearer {token}"},
timeout=15
)
limit_header = head_resp.headers.get("ratelimit-limit", "100;w=21600")
remaining_header = head_resp.headers.get("ratelimit-remaining", "100;w=21600")
source_ip = head_resp.headers.get("docker-ratelimit-source", "unknown")
total_limit = int(limit_header.split(";")[0])
remaining_pulls = int(remaining_header.split(";")[0])
reset_seconds = int(remaining_header.split("w=")[1]) if "w=" in remaining_header else 0
saturation_pct = round(((total_limit - remaining_pulls) / total_limit) * 100, 2) if total_limit > 0 else 0.0
Kestra.outputs({
"total_limit": total_limit,
"remaining_pulls": remaining_pulls,
"reset_seconds": reset_seconds,
"saturation_pct": saturation_pct,
"rate_limit_source": source_ip,
"is_authenticated": bool(username and pat)
})
- id: check_quota_threshold
type: io.kestra.plugin.core.flow.If
description: Evaluates whether remaining Docker Hub pulls have dropped below the
safe operational threshold.
condition: "{{ outputs.inspect_docker_rate_limit.vars.remaining_pulls <=
inputs.remaining_pull_threshold and inputs.alert_on_breach }}"
then:
- id: notify_slack_rate_limit_warning
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches a high-priority incident warning to platform operations
Slack channel.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "#platform-alerts",
"attachments": [
{
"color": "#D32F2F",
"title": "⚠️ Docker Hub Pull Rate Limit Exhaustion Warning",
"text": "Docker Hub pull capacity is critically low. Downstream CI/CD pipelines and Kubernetes image pulls risk encountering HTTP 429 errors.",
"fields": [
{
"title": "Remaining Pulls",
"value": "{{ outputs.inspect_docker_rate_limit.vars.remaining_pulls }} / {{ outputs.inspect_docker_rate_limit.vars.total_limit }}",
"short": true
},
{
"title": "Quota Saturation",
"value": "{{ outputs.inspect_docker_rate_limit.vars.saturation_pct }}%",
"short": true
},
{
"title": "Window Reset In",
"value": "{{ outputs.inspect_docker_rate_limit.vars.reset_seconds }} seconds",
"short": true
},
{
"title": "Rate Limit Source",
"value": "{{ outputs.inspect_docker_rate_limit.vars.rate_limit_source }} ({{ outputs.inspect_docker_rate_limit.vars.is_authenticated ? 'Authenticated' : 'Anonymous IP' }})",
"short": true
}
],
"footer": "Kestra Cloud Infrastructure Sentinel"
}
]
}
else:
- id: log_healthy_quota
type: io.kestra.plugin.core.log.Log
description: Logs normal quota state when remaining pulls are above the threshold.
message: "Docker Hub quota healthy: {{
outputs.inspect_docker_rate_limit.vars.remaining_pulls }} pulls
remaining out of {{ outputs.inspect_docker_rate_limit.vars.total_limit
}} ({{ outputs.inspect_docker_rate_limit.vars.saturation_pct }}%
saturated) for source {{
outputs.inspect_docker_rate_limit.vars.rate_limit_source }}."
- id: emit_audit_manifest
type: io.kestra.plugin.core.debug.Return
description: Emits structured telemetry for integration with observability tools
and long-term compliance tracking.
format:
timestamp: "{{ execution.startDate }}"
total_limit: "{{ outputs.inspect_docker_rate_limit.vars.total_limit }}"
remaining_pulls: "{{ outputs.inspect_docker_rate_limit.vars.remaining_pulls }}"
saturation_pct: "{{ outputs.inspect_docker_rate_limit.vars.saturation_pct }}"
rate_limit_source: "{{ outputs.inspect_docker_rate_limit.vars.rate_limit_source }}"
is_breached: "{{ outputs.inspect_docker_rate_limit.vars.remaining_pulls <=
inputs.remaining_pull_threshold }}"
errors:
- id: alert_on_rate_limit_inspection_failure
type: io.kestra.plugin.core.log.Log
description: Logs diagnostic context if Docker Hub registry token or header query fails.
message: "Docker Hub rate limit sentinel failed during execution {{ execution.id
}} on task {{ errorLogs()[0]['taskId'] ?? 'unknown' }}: {{
errorLogs()[0]['message'] ?? 'Check network connectivity to
registry-1.docker.io' }}"
triggers:
- id: hourly_docker_rate_limit_monitor
type: io.kestra.plugin.core.trigger.Schedule
description: Hourly automated audit of regional Docker Hub rate limit status.
cron: "0 * * * *"