DockFeed
Docker app from DockFeed's Repository
Overview
Readme
View on GitHubDockFeed
One Atom/RSS feed with the release notes of everything running on your Docker host.
DockFeed reads the Docker socket, works out which source repository each container comes from, and merges their release feeds into a single Atom feed you can subscribe to in any RSS reader.
Complementary to Watchtower (auto-updates) and Diun (notifications), DockFeed gives you the full release notes in one place so you stay informed on your terms.
No database, no required API tokens, no config file. Plain Python with a single dependency (feedparser), shipped as a 24 MB scratch image (about 9 MB to download) for amd64, arm64 and armv7.
Quick start
services:
dockfeed:
image: ghcr.io/p6g9yhk6/dockfeed:latest
container_name: dockfeed
restart: unless-stopped
ports:
- 8080:8080
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
Unraid (Community Applications)
DockFeed is available in Community Applications (search "DockFeed").
The template mounts the Docker socket read-only, drops all capabilities, and runs read-only. A secure, zero-config install that fits right alongside Watchtower or Diun on your Unraid server.
Then subscribe to http://<host>:8080/feed.atom in your RSS/Atom reader. The first scan takes a few seconds; /status shows what was found.
Image tags
| Tag | Built from |
|---|---|
latest |
Every push to main |
1.2.3, 1.2 |
Release tags v1.2.3 |
sha-abc1234 |
A specific commit |
Endpoints
| Path | What |
|---|---|
/feed.atom (or /) |
The merged Atom feed, newest first |
/feed.atom?container=sonarr,radarr |
Only these containers |
/feed.atom?kind=release |
Drop the commit fallbacks, kind=commit for the opposite |
/status |
JSON: every repo, how it was resolved, errors, and which containers couldn't be resolved |
/healthz |
Liveness |
Subscribe with any RSS/Atom reader — the feed works with both.
Each entry is titled <container>: <release> (or <container> (commit): <message>), keeps the original release notes, and ends with a line linking the repo, the containers using it and the host they run on, and the version currently running when the image says so, e.g. github.com/DIYgod/RSSHub · used by rsshub on Tower · running 2026-10-31. Containers are also set as Atom categories, so readers that filter on tags can use them.
How a container is matched to a repo
First match wins:
dockfeed.sourcelabel on the containerorg.opencontainers.image.sourceororg.label-schema.vcs-urlimage label (most images built with GitHub Actions have one)org.opencontainers.image.urlwhen it points at a known forge- The image name:
ghcr.io/owner/repo,codeberg.org/...,registry.gitlab.com/..., linuxserver.io images (lscr.io/linuxserver/xmaps tolinuxserver/docker-x), and a built-in list of popular Docker Hub official images (redis,postgres,nginx, ...)
If none of that works, DockFeed looks the image up (disable with DISCOVER=false):
- A forge next to the registry:
dock.mau.dev/mautrix/signalis found atmau.dev/mautrix/signal(GitLab, Gitea and Forgejo are detected through their API) - For Docker Hub
owner/nameimages,github.com/owner/namethengithub.com/owner/docker-name - Repo links in the image's Docker Hub description, preferring a link whose name matches the image, then one whose owner matches the namespace, then the most starred. Packaging repos like
docker-library/*are ignored - A GitHub search, keeping only a repo whose owner matches the Docker Hub namespace and whose name matches the image (
umap/umapfindsumap-project/umap,portainer/portainer-eefindsportainer/portainer, never an unrelated repo that happens to share the name)
Every candidate is checked to exist before it's used. Results are cached: a match for the life of the process, a miss for 24 hours. Rate limits or network errors aren't cached, so the lookup is retried on the next scan. /status shows which step matched each repo.
Containers sharing a repo (an app plus its worker, for example) are merged into one set of entries. Anything left over, typically closed-source images, is listed under unresolved in /status. Add a dockfeed.source label to point it somewhere, or dockfeed.enable=false to silence it.
Releases or commits
In the default auto mode, DockFeed uses the repo's releases, and switches to commits when:
- the repo has no releases and no tags, or
- the newest release is older than
STALE_RELEASE_AFTER(one year by default)
GitHub and GitLab list tags in place of releases when a repo only tags, so tag-only projects are still treated as releasing.
Commit entries are titled with the commit's first line and show the full message, line breaks included, whatever forge it comes from.
Supported forges: GitHub, GitLab (gitlab.com and self-hosted), Gitea, Forgejo and Codeberg. Self-hosted instances are detected through their API.
Container labels
| Label | Effect |
|---|---|
dockfeed.enable=false |
Leave this container out |
dockfeed.source=https://github.com/owner/repo |
Override the detected repo |
dockfeed.mode=releases |
auto (default), releases or commits |
dockfeed.branch=dev |
Branch to follow in commit mode, defaults to the repo's default branch. Handy for images that track main or dev |
dockfeed.include=^v\d |
Only keep entries whose title matches this regex |
dockfeed.exclude=(?i)rc|beta |
Drop entries whose title matches, overrides the global EXCLUDE |
Filters run before the staleness check, so a project that only ships filtered-out prereleases falls back to commits.
Environment
| Variable | Default | |
|---|---|---|
DOCKER_HOST |
unix:///var/run/docker.sock |
tcp://socket-proxy:2375 works too |
LISTEN_ADDR |
:8080 |
|
REFRESH_INTERVAL |
1h |
90s, 30m, 1h30m, 1d... |
STALE_RELEASE_AFTER |
365d |
0 never falls back on age |
ITEMS_PER_REPO |
10 |
|
MAX_ITEMS |
500 |
Cap on the merged feed, applied after ?container= filtering |
CONCURRENCY |
8 |
Parallel forge requests |
EXCLUDE |
Regex on titles applied to every repo, e.g. (?i)-rc|beta|alpha|nightly |
|
INCLUDE_STOPPED |
false |
Also follow stopped containers |
LOG_LEVEL |
INFO |
DEBUG also logs every HTTP request |
DISCOVER |
true |
Steps 5 to 8 above |
GITHUB_TOKEN |
Optional, any token with no scopes. Raises the GitHub API limit used by steps 7 and 8 from 60 to 5000 requests an hour | |
HOST_NAME |
the Docker host's name | Shown in each entry as "used by x on HOST_NAME". Read from the engine (/info) by default, since inside a container $HOSTNAME is the container ID |
FEED_TITLE |
DockFeed |
|
PUBLIC_URL |
Base URL for the feed's self link when behind a proxy |
Security
DockFeed makes exactly three calls to the Engine API, all GET: /containers/json, /images/<digest>/json and /info. The Docker client checks every request against that list before it touches the socket and refuses anything else, so no other endpoint (create, exec, delete, logs...) can be reached through DockFeed's code.
The web server only hands out pages: GET and HEAD are the only methods it accepts (anything else gets a 405), requests with a body get a 400, URLs over 2 KB get a 414, idle connections time out after 15 seconds and at most 64 are served at once. The Host and X-Forwarded-Proto headers only make it into the feed's self link when they look like a real host and http/https. Nothing a client sends is ever passed to Docker.
Note that :ro on the socket mount doesn't make the API read-only, it only stops the socket file being replaced. The allowlist above is what limits the calls. If you want that enforced outside DockFeed too, point DOCKER_HOST at a socket proxy that only allows CONTAINERS, IMAGES and INFO (leave INFO off and set HOST_NAME instead if you prefer):
socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
environment:
CONTAINERS: 1
IMAGES: 1
INFO: 1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
dockfeed:
image: ghcr.io/p6g9yhk6/dockfeed:latest
environment:
DOCKER_HOST: tcp://socket-proxy:2375
The feed lists everything you run and which versions, so don't expose it publicly without auth.
Release and commit feeds are fetched without credentials, with conditional requests so an unchanged feed costs a 304.
Development
Needs Python 3.12 or newer.
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
python -m dockfeed --once > feed.atom # one scan, print the feed, exit
python -m dockfeed # serve on :8080
pytest --cov # tests, fails under 80% coverage
ruff check . && ruff format --check . && mypy dockfeed
DOCKER_HOST can point at a remote engine for testing, for example through an SSH-forwarded socket:
ssh -NL /tmp/remote.sock:/var/run/docker.sock myserver &
DOCKER_HOST=unix:///tmp/remote.sock python -m dockfeed --once
The code is split by concern, all under dockfeed/: docker.py (socket client), source.py (label and image name rules), discover.py (network lookups for unlabeled images), forge.py (GitHub, GitLab, Gitea and Forgejo feeds), feed.py (Atom and RSS in, Atom out) and app.py (config, scan loop, HTTP). Apart from feedparser everything is the standard library.
The image is built from python:3.14-alpine, then docker/slim.sh copies only what DockFeed needs onto scratch: the interpreter, the stdlib minus tests, GUI, REPL, database and other unused modules (as bytecode only), the shared libraries those link against, and the CA bundle. A smoke stage (docker/smoke.py) runs inside that filesystem during the build, so pruning too much fails the build rather than the container. If a new import fails in the image only, the module was pruned: take it out of the rm lists in slim.sh. Tracebacks in the image show no source lines, since only bytecode ships.
requirements.lock is the hash-pinned resolution of requirements.txt that the image installs. After changing requirements.txt, regenerate it with:
uv pip compile requirements.txt -o requirements.lock --python-version 3.14 --generate-hashes
CI
- Lint and test runs on every push and pull request: ruff (lint and format), mypy in strict mode, pip-audit on the locked dependencies, hadolint on the Dockerfile, pytest on Python 3.12 and 3.14, and a check that
requirements.lockstill matchesrequirements.txt..pre-commit-config.yamlruns the same ruff checks locally (pip install pre-commit && pre-commit install). - Publish Docker image builds the multi-arch image and pushes it to GHCR on every push to
mainand onv*.*.*tags. - Dependabot opens weekly grouped PRs for the Python dependencies, the base image and the actions. They're merged automatically once lint and tests pass; for Python bumps the lock file is regenerated first.
To cut a release: git tag v1.0.0 && git push --tags.
Contributing
Issues and pull requests are welcome. If an image isn't matched to its repo, open an issue with the image name and the repo it should map to. Most of the time the right fix is a rule in dockfeed/source.py or an entry in the official image list, which helps everyone running it.
License
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/p6g9yhk6/dockfeed:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8080]/feed.atom- Network
bridge- Shell
bash- Privileged
- false
- Extra Params
--restart unless-stopped --read-only --cap-drop=ALL --security-opt=no-new-privileges:true
Template configuration
Port the DockFeed web interface listens on.
- Target
- 8080
- Default
- 8080
- Value
- 8080
Docker socket for reading container information. Mount read-only for security.
- Target
- /var/run/docker.sock
- Default
- /var/run/docker.sock
- Value
- /var/run/docker.sock
Address and port to listen on (e.g., :8080, 0.0.0.0:8080).
- Target
- LISTEN_ADDR
- Default
- :8080
- Value
- :8080
How often to scan containers and update the feed (e.g., 90s, 30m, 1h30m, 1d).
- Target
- REFRESH_INTERVAL
- Default
- 1h
- Value
- 1h
Fall back to commits when the newest release is older than this (e.g., 365d, 0 to never fall back).
- Target
- STALE_RELEASE_AFTER
- Default
- 365d
- Value
- 365d
Maximum feed entries to keep per repository.
- Target
- ITEMS_PER_REPO
- Default
- 10
- Value
- 10
Hard cap on the merged feed after filtering.
- Target
- MAX_ITEMS
- Default
- 500
- Value
- 500
Parallel forge requests during a scan.
- Target
- CONCURRENCY
- Default
- 8
- Value
- 8
Regex on titles applied to every repo, e.g., '(?i)-rc|beta|alpha|nightly'.
- Target
- EXCLUDE
Also follow stopped containers (true/false).
- Target
- INCLUDE_STOPPED
- Default
- false
- Value
- false
Log level: DEBUG, INFO, WARNING, ERROR.
- Target
- LOG_LEVEL
- Default
- INFO
- Value
- INFO
Enable network lookups for unlabeled images (true/false).
- Target
- DISCOVER
- Default
- true
- Value
- true
Optional GitHub token (no scopes needed) to raise API limit from 60 to 5000/hr for Docker Hub image lookups.
- Target
- GITHUB_TOKEN
Override the Docker host name shown in feed entries (defaults to engine's hostname).
- Target
- HOST_NAME
Title of the generated Atom feed.
- Target
- FEED_TITLE
- Default
- DockFeed
- Value
- DockFeed
Base URL for the feed's self link when behind a reverse proxy (e.g., https://dockfeed.example.com).
- Target
- PUBLIC_URL