Image Update Watcher
pyportainer comes with a built-in background watcher: PortainerImageWatcher. The watcher can continuously monitor your running Docker containers for available image updates.
It polls Portainer at a configurable interval, compares the local image digest of each running container against the digest in the registry, and stores the results so your application can react to available updates without blocking.
How it works
- On
start(), a background asyncio task is created. - The first check runs immediately, then repeats after each configured
interval. - For every endpoint (or a specific one), the watcher fetches all running containers, deduplicates by image, and concurrently calls
container_image_status()for each unique image. - Results are stored internally and exposed via
watcher.results. - Errors for individual containers or endpoints are logged but never stop the polling loop.
Installation
No extra dependencies are needed, the watcher is part of the core pyportainer package and runs on asyncio.
Basic usage
import asyncio
from datetime import timedelta
from pyportainer import Portainer, PortainerImageWatcher
async def main() -> None:
async with Portainer(
api_url="http://localhost:9000",
api_key="YOUR_API_KEY",
) as portainer:
watcher = PortainerImageWatcher(
portainer,
interval=timedelta(hours=6),
)
watcher.start()
# Depending on the registires it may take some time for the first check to complete
# So we wait a bit before accessing results
await asyncio.sleep(timedelta(minutes=1))
for (endpoint_id, container_id), result in watcher.results.items():
if result.status and result.status.update_available:
print(f"Update available for container {container_id} on endpoint {endpoint_id}")
watcher.stop()
if __name__ == "__main__":
asyncio.run(main())
Watching a specific endpoint
Pass endpoint_id to limit monitoring to a single Portainer endpoint:
watcher = PortainerImageWatcher(
portainer,
endpoint_id=1,
interval=timedelta(hours=12),
)
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
portainer |
Portainer |
— | The Portainer client instance |
endpoint_id |
int \| None |
None |
Endpoint to monitor. None watches all endpoints |
interval |
timedelta |
12 hours | How often to poll for updates |
debug |
bool |
False |
Enable debug-level logging |
Results
watcher.results returns a copy of the current results as a dictionary:
dict[(endpoint_id, container_id), PortainerImageWatcherResult]
Each PortainerImageWatcherResult contains:
| Field | Type | Description |
|---|---|---|
endpoint_id |
int \| None |
The endpoint the container belongs to |
container_id |
str \| None |
The container ID |
status |
PortainerImageUpdateStatus \| None |
The image update check result |
PortainerImageUpdateStatus fields:
| Field | Type | Description |
|---|---|---|
update_available |
bool |
True if a newer image is available in the registry |
local_digest |
str \| None |
Digest of the locally running image |
registry_digest |
str \| None |
Digest of the latest image in the registry |
Callbacks
Instead of polling watcher.results yourself, you can register callbacks that are invoked automatically after each poll cycle, once per container result. Both sync and async callables are supported.
Registering a callback
from pyportainer import Portainer, PortainerImageWatcher
from pyportainer.watcher import PortainerImageWatcherResult
def on_result(result: PortainerImageWatcherResult) -> None:
if result.status and result.status.update_available:
print(f"Update available for container {result.container_id}")
watcher = PortainerImageWatcher(portainer, interval=timedelta(hours=6))
watcher.register_callback(on_result)
watcher.start()
Async callbacks
async def on_result(result: PortainerImageWatcherResult) -> None:
if result.status and result.status.update_available:
# Follow up with an async action, e.g. send a notification
await notify(result.container_id)
watcher.register_callback(on_result)
Filtering for updates only
Callbacks receive every result, including containers where no update is available. Filter inside the callback:
def on_result(result: PortainerImageWatcherResult) -> None:
if not result.status or not result.status.update_available:
return
# Handle update ...
Unregistering a callback
watcher.unregister_callback(on_result)
Notes
- Registering the same callable twice is a no-go; it will only be called once per result.
- Exceptions raised inside a callback are logged but do not stop the watcher or prevent other callbacks from running.
- The
WatcherCallbacktype alias is exported frompyportainerfor type annotations:from pyportainer import WatcherCallback.
Runtime control
Changing the interval
The polling interval can be updated at any time. The new value takes effect after the next completed check:
from datetime import timedelta
watcher.interval = timedelta(hours=1)
It is however recommended to not thunderherd the registry with too frequent checks, especially if you have many containers or endpoints. A reasonable interval is usually between 6 and 24 hours, depending on how often you update your images. Registries may rate-limit requests, so adjust accordingly if you have many containers or a registry with strict limits.
Checking when the last poll ran
import datetime
if watcher.last_check:
last = datetime.datetime.fromtimestamp(watcher.last_check)
print(f"Last checked at: {last}")
Stopping the watcher
watcher.stop()
Calling stop() cancels the background task. Call start() again to restart it.
API reference
Background image update watcher.
PortainerImageWatcher
Periodically checks all containers on an endpoint for image updates.
Results are stored and accessible via the results property after each check.
Source code in src/pyportainer/watcher.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
interval
property
writable
Polling interval.
last_check
property
Timestamp of the last completed check, or None if no checks have completed yet.
results
property
Latest update status as of the last check.
__init__(portainer, endpoint_id=None, interval=timedelta(hours=12), *, debug=False)
Initialize the PortainerImageWatcher.
portainer: An authenticated Portainer client instance.
endpoint_id: The ID of the endpoint whose containers to monitor. If None, all endpoints are monitored.
interval: How often to poll for updates. Defaults to 12 hours.
Source code in src/pyportainer/watcher.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
register_callback(callback)
Register a callback to be invoked for every result after each poll cycle.
Both synchronous and async callables are supported. The callback receives a
single :class:PortainerImageWatcherResult argument. Each unique callable
is only registered once; duplicate registrations are silently ignored.
callback: A sync or async callable that accepts a
:class:`PortainerImageWatcherResult`.
Source code in src/pyportainer/watcher.py
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
start()
Start the background polling loop.
The first check runs immediately; subsequent checks run after each interval. Must be called from within a running asyncio event loop.
Source code in src/pyportainer/watcher.py
88 89 90 91 92 93 94 95 | |
stop()
Cancel the background polling loop.
Source code in src/pyportainer/watcher.py
97 98 99 100 | |
unregister_callback(callback)
Remove a previously registered callback.
callback: The callable to remove. Raises :exc:`ValueError` if it was not registered.
Source code in src/pyportainer/watcher.py
118 119 120 121 122 123 124 125 126 | |
PortainerImageWatcherResult
dataclass
Represents the status of an image watcher.
Source code in src/pyportainer/watcher.py
26 27 28 29 30 31 32 | |