DockFeed

DockFeed

Docker app from DockFeed's Repository

Overview

One Atom 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 feed you can subscribe to in any reader. When a project doesn't publish releases (or stopped tagging long ago), it follows the commits on the default branch instead. 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.

DockFeed icon

DockFeed

Lint and test Publish Docker image License: MPL 2.0

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:

  1. dockfeed.source label on the container
  2. org.opencontainers.image.source or org.label-schema.vcs-url image label (most images built with GitHub Actions have one)
  3. org.opencontainers.image.url when it points at a known forge
  4. The image name: ghcr.io/owner/repo, codeberg.org/..., registry.gitlab.com/..., linuxserver.io images (lscr.io/linuxserver/x maps to linuxserver/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):

  1. A forge next to the registry: dock.mau.dev/mautrix/signal is found at mau.dev/mautrix/signal (GitLab, Gitea and Forgejo are detected through their API)
  2. For Docker Hub owner/name images, github.com/owner/name then github.com/owner/docker-name
  3. 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
  4. A GitHub search, keeping only a repo whose owner matches the Docker Hub namespace and whose name matches the image (umap/umap finds umap-project/umap, portainer/portainer-ee finds portainer/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.lock still matches requirements.txt. .pre-commit-config.yaml runs 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 main and on v*.*.* 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

Mozilla Public License 2.0

Related apps

Details

Repository
ghcr.io/p6g9yhk6/dockfeed:latest
Last Updated2026-11-02
First Seen2026-11-02

Runtime 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

WebUI PortPorttcp

Port the DockFeed web interface listens on.

Target
8080
Default
8080
Value
8080
Docker SocketPathro

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
Listen AddressVariable

Address and port to listen on (e.g., :8080, 0.0.0.0:8080).

Target
LISTEN_ADDR
Default
:8080
Value
:8080
Refresh IntervalVariable

How often to scan containers and update the feed (e.g., 90s, 30m, 1h30m, 1d).

Target
REFRESH_INTERVAL
Default
1h
Value
1h
Stale Release ThresholdVariable

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
Items Per RepoVariable

Maximum feed entries to keep per repository.

Target
ITEMS_PER_REPO
Default
10
Value
10
Max Items in FeedVariable

Hard cap on the merged feed after filtering.

Target
MAX_ITEMS
Default
500
Value
500
ConcurrencyVariable

Parallel forge requests during a scan.

Target
CONCURRENCY
Default
8
Value
8
Exclude RegexVariable

Regex on titles applied to every repo, e.g., '(?i)-rc|beta|alpha|nightly'.

Target
EXCLUDE
Include Stopped ContainersVariable

Also follow stopped containers (true/false).

Target
INCLUDE_STOPPED
Default
false
Value
false
Log LevelVariable

Log level: DEBUG, INFO, WARNING, ERROR.

Target
LOG_LEVEL
Default
INFO
Value
INFO
Enable DiscoveryVariable

Enable network lookups for unlabeled images (true/false).

Target
DISCOVER
Default
true
Value
true
GitHub TokenVariable

Optional GitHub token (no scopes needed) to raise API limit from 60 to 5000/hr for Docker Hub image lookups.

Target
GITHUB_TOKEN
Host NameVariable

Override the Docker host name shown in feed entries (defaults to engine's hostname).

Target
HOST_NAME
Feed TitleVariable

Title of the generated Atom feed.

Target
FEED_TITLE
Default
DockFeed
Value
DockFeed
Public URLVariable

Base URL for the feed's self link when behind a reverse proxy (e.g., https://dockfeed.example.com).

Target
PUBLIC_URL