Scheduling Scans
Scan schedules in HarborGuard are configured per registry, not globally. Each registry has a scanning policy with a schedule mode, the scanner set to use, and tag include/exclude filters.
Schedule modes
| Mode | Behavior |
|---|---|
manual | No automatic scans. Triggered only by UI button or API. |
daily | Re-scan tags whose most recent scan is older than 24 hours. |
weekly | Re-scan tags whose most recent scan is older than 7 days. |
on_push | Scan a tag the first time it is observed in the registry catalog. |
A scan job is only enqueued when no PENDING or IN_PROGRESS scan already exists for that tag, so safe to run frequently. Dedup is enforced via a per-tag advisory lock at insertion time.
The schedule field stores one of those preset modes. Free-form cron expressions are not currently accepted - if you need finer granularity (e.g. "every 6 hours", "weekdays at 2am"), the recommended pattern is to drive it from your own scheduler and call POST /api/scans on the cadence you want.
Conceptual cron equivalents
If you are driving scans externally, useful cron expressions:
Pair each with a curl call to POST /api/scans - see CI/CD.
Tag filters
Each registry policy supports include and exclude glob filters:
| Field | Example | Meaning |
|---|---|---|
tagInclude | prod-*,v[0-9]* | Comma-separated globs. Defaults to *. |
tagExclude | *-rc,*-beta,sha-* | Comma-separated globs. Empty by default. |
A tag must match an include and not match any exclude to be eligible for scheduled scans.
On-push scans and registry webhooks
on_push mode scans a tag the first time HarborGuard sees it. Two ways HarborGuard sees new tags:
- Native webhook - the registry calls HarborGuard when a tag is pushed.
- Polling - HarborGuard syncs the catalog on a schedule and detects new digests.
Webhook support varies by registry. The honest state of the world:
| Registry | Native push webhook | HarborGuard support |
|---|---|---|
| Docker Hub | Yes (paid plans) | Webhook + polling |
| GitHub Container Registry (GHCR) | Via repo package events | Webhook + polling |
| AWS ECR | Yes (EventBridge) | Polling (EventBridge bridge is user-configured) |
| Google Artifact Registry / GCR | Via Pub/Sub | Polling (Pub/Sub bridge is user-configured) |
| Azure Container Registry | Yes (webhook config) | Webhook + polling |
| Quay.io | Yes | Webhook + polling |
| Harbor | Yes | Webhook + polling |
| Generic OCI | No standard | Polling only |
For registries without a native webhook integration, polling is what you get. Polling cadence is the registry's catalog sync interval, not the scan schedule itself.
Configuring a schedule
UI: Registries -> [registry] -> Settings -> Scanning policy.
API: PUT /api/registries/{id} with the scanning block:
method is "cloud" or "sensor" and selects the scan origin (see Sensor Architecture).
Verifying the schedule fires
The Scans page filter origin: cloud plus status: PENDING shows queued work. After a scheduler tick you should see one new PENDING row per eligible tag, then IN_PROGRESS, then COMPLETED. If the count stays at zero, check:
- Tags actually exist in the registry catalog (the Images page lists them).
- Tag filters are not excluding everything.
- The registry's
scanning.methodmatches an available origin (a sensor must be online and bound formethod: "sensor").