Stashboard Tag Pattern Filtering: Stop False Update Alerts from Pre-Release Builds

stashboard-docker-tag-pattern-filter.md
title Stashboard Tag Pattern Filtering: Stop False Update Alerts from Pre-Release Builds
date
author VahaC
read 13 min read
category Self-Hosting
tags #container #Docker #Homelab #Stashboard
Stashboard Tag Pattern Filtering: Stop False Update Alerts from Pre-Release Builds

If you rely on a Docker tag pattern filter homelab tool to track container updates, you have probably seen the badge flip to Update available when nothing you actually care about changed. An upstream project pushed v2.1.0-rc1 or latest-beta overnight, and your monitor dutifully reported it as a new release. Stashboard’s V2.1 Docker tag pattern filter was built to solve exactly this problem. Instead of comparing the digest of your pinned tag directly, it lists the registry’s available tags, applies your regex, picks the highest semver match, and only then performs the digest comparison. This post explains how that pipeline works, what the three built-in presets do under the hood, how to write custom patterns, and — critically — the ways an overly strict filter can silently hide real updates from you.

Why “latest” Pulls In Pre-Release Tags

Without a tag filter, Stashboard uses a simple V1 approach: resolve the manifest digest of your pinned tag — say latest — and compare it to what the registry currently serves under that same tag. This works when registries are well-behaved, but most active open-source projects are not. Upstream maintainers routinely push v2.1.0-rc1, v2.0.0-beta3, and nightly-20250401 alongside the stable release track. If any of those pre-release tags move the latest pointer — or if you pinned to a major-version alias like v2 that the maintainer now uses for release candidates — you get a false positive on every pre-release push.

The V2.1 fix changes the question Stashboard asks. Rather than “what is the current digest of my pinned tag?”, it becomes “what is the highest tag in the registry that matches my pattern, and what digest does that tag resolve to?” Pre-release tags are excluded before any digest comparison happens, so they can never trigger an alert you did not explicitly opt in to.

The Docker Tag Pattern Filter Homelab Algorithm

The Docker tag pattern filter homelab pipeline runs in five sequential steps inside DockerUpdateChecker.ResolveFilteredLatestAsync:

  1. Compile the regex. Stashboard compiles your pattern with RegexOptions.CultureInvariant and a hard 250 ms execution timeout. These are .NET regexes — not JavaScript/ECMAScript — which means negative lookaheads like ^(?!.*-rc).*$ work exactly as written.
  2. List tags from the registry. An OCI Distribution request — GET /v2/{name}/tags/list?n=200 — fetches a page of tags, and Stashboard follows the registry’s Link: rel="next" header to walk through additional pages until the registry stops advertising one (capped at 20 pages or 5000 tags as a safety limit). Both anonymous and Bearer-authenticated registries are supported, using the same auth context as the rest of the watch.
  3. Apply the filter. Each returned tag is tested against the compiled regex and must match the tag in full — Stashboard verifies the match starts at the first character and spans the entire tag, not just a substring. That means an un-anchored pattern like v\d+\.\d+\.\d+ will not sneak v1.2.3-rc1 through by matching only its v1.2.3 prefix. Non-matching tags are discarded entirely.
  4. Sort and pick the winner. Matching tags are ordered by TagVersionComparer (described below) and the highest-ranked tag is selected as the “latest” candidate. If no tags matched, the function returns (null, null) and the orchestrator surfaces a “filter matched nothing” state.
  5. Resolve the digest. Stashboard fetches the manifest digest for the winning tag and compares it to the running container’s digest. A mismatch triggers an Update available badge; a match stays Up to date.

How Tag Ordering Is Determined

Selecting the “highest” tag requires understanding version numbers, not just alphabetical order. Stashboard’s TagVersionComparer uses a two-stage approach.

First, it attempts to parse each tag with the pattern ^v?(\d+(?:\.\d+)*)(?:-([^+]*))?(?:\+.*)?$. A tag matches this shape if it is one or more dot-separated integers with an optional leading v, an optional pre-release suffix after a hyphen, and optional build metadata after a plus sign. Build metadata never affects ordering — per semver, v1.0.0+build.5 ranks equal to plain v1.0.0 and above any pre-release of the same version. When both tags in a comparison parse successfully, their numeric components are compared as integers: v1.10.0 correctly ranks above v1.2.0 because 10 > 2 as integers, not as strings. Missing trailing components count as zero, so v1.2 compares equal to v1.2.0.

Second, when the numeric components are equal, the pre-release suffix decides the winner. Per semver.org §11, a plain release tag outranks any pre-release variant: v1.2.3 beats v1.2.3-rc1, which beats v1.2.3-alpha. Among multiple pre-release tags with the same base version, the suffixes are compared identifier-by-identifier per semver §11: dot-separated identifiers are evaluated left to right, purely numeric identifiers are compared as numbers (so v1.2.3-rc.10 correctly beats v1.2.3-rc.2, not the reverse), numeric identifiers rank below alphanumeric ones, and a longer run of identifiers wins when every earlier one is equal. v1.2.3-rc2 still beats v1.2.3-rc1.

When a tag cannot be parsed as a version at all — a date stamp like 20250401, a word like edge, or any free-form string — two such tags are compared as plain ordinal strings. Crucially, a semver-shaped tag always outranks a non-semver one, regardless of how the two would sort alphabetically. This matters in mixed lists: a real version like 1.28.0 always wins over a rolling tag such as nightly or edge, even though nightly would sort higher as a raw string. Without this rule, a filter that admits both could lock onto a moving nightly tag and report a permanent false “update available”.

The Three Built-In Presets

Stashboard ships three preset patterns in the Docker watch form, under the collapsible “Tag pattern filter” row. Here is what each one does in practice.

SemVer Only — ^v?\d+\.\d+\.\d+$

This tag pattern filter matches any tag that is exactly three dot-separated integers with an optional leading v. The trailing $ anchor is critical: the string must end immediately after the third number group, so tags like v1.2.3-rc1 and v1.2.3-alpha do not match — the hyphen suffix violates the end-of-string boundary. Date-based tags (20250401), single-word tags (stable, edge), abbreviated versions (v1.2), and all pre-release variants are excluded. Use this preset when your image follows strict three-part versioning and you want zero pre-release noise.

Stable Only — (?i)^(?!.*-(rc|beta|alpha|dev|snapshot|pre|preview|canary|next|nightly|edge)).*$

This pattern uses a .NET negative lookahead, with a leading (?i) case-insensitive flag, to exclude any tag whose name contains a known pre-release or rolling marker after a hyphen — -rc, -beta, -alpha, -dev, -snapshot, -pre, -preview, -canary, -next, -nightly, or -edge — in any letter case. Every other tag passes: v1.2.3, latest, stable, v2.1.0-hotfix, and build metadata like v1.0.0+build.1 are all accepted. This is looser than “SemVer only” and is the right choice when an image publishes a mix of tag formats — multi-arch aliases, dated tags, named channel aliases — and you want to filter out the common pre-release and nightly suffixes without constraining the overall tag format.

Tag Equals “stable” — ^stable$

An exact match for the literal string stable. Some upstream projects — Traefik being a common example — maintain a named stable pointer that always resolves to the latest production-ready image. Stashboard will list all tags, discard everything except stable, and compare the digest of that one tag. It is the simplest possible filter. If the registry has no stable tag, the watch returns Up to date with an explanatory error string rather than crashing or raising a false alert.

Writing Your Own Custom Regex Pattern

The free-text override field accepts any valid .NET regex up to 200 characters. A few practical examples:

# Require the v prefix — useful when the image publishes both "1.2.3" and "v1.2.3"
^v\d+\.\d+\.\d+$
# Block only -rc and -beta; allow -alpha, -hotfix, or anything else through
^(?!.*-(rc|beta)).*$
# Match MAJOR.MINOR only — no patch component required
^v?\d+\.\d+$
# Exact match for a named channel
^stable$

Four things to keep in mind when writing custom patterns. First, these are .NET regexes, not JavaScript. The syntax is close but not identical; the most important difference is that .NET supports negative lookahead natively, which is what the “Stable only” preset relies on. Second, the pattern must match the entire tag, not just part of it — Stashboard enforces a full-string match. An un-anchored pattern like v\d+\.\d+\.\d+ still will not accept v1.2.3-rc1, because the pattern has to cover the whole tag rather than merely a prefix. Third, your own custom patterns are case-sensitive by default. Writing ^(?!.*-(RC|BETA)).*$ in uppercase will not block lowercase -rc and -beta suffixes because the match is exact. Add (?i) at the start if you need case-insensitive matching (the built-in “Stable only” preset already does this for you). Fourth, the pattern is validated server-side before being stored — an invalid regex returns an HTTP 400 error immediately and is never persisted to the database. You will see the error in the UI right away, not silently on the next scheduled check run.

⚠️ Risks: When a Too-Strict Filter Silently Hides Real Updates

The most dangerous failure mode for a Docker tag pattern filter homelab setup is not a wrong alert — it is a missing alert. When the filter matches zero tags, Stashboard returns a status of Up to date, not an error. The error string No tags matched the filter '{pattern}'. is attached to the watch result and shown in the UI status tooltip, but the badge stays green. This is a deliberate design decision — zero candidates means there is nothing to upgrade to, which is technically correct — but it creates a situation where a misconfigured filter can quietly ignore all available versions while appearing healthy.

Common causes in practice:

  • Tag format mismatch. You set ^v\d+\.\d+\.\d+$ but the image publishes tags without the v prefix — 1.2.3 instead of v1.2.3. The built-in “SemVer only” preset uses v? to make the prefix optional, handling this case. The same issue occurs with date-based tags (20250401): the “SemVer only” preset never matches them, so a date-tagged image needs a custom pattern. The “Stable only” preset would let them through, but then ranks them as plain strings rather than versions.
  • Case sensitivity. A pattern like ^(?!.*-(RC|BETA)).*$ will not block lowercase -rc and -beta tags because the match is case-sensitive. The result is that pre-releases pass the filter when they should not — the opposite of the intended behavior.
  • The page-walk cap. Stashboard now pages through the registry by following the OCI Link: rel="next" header, but it stops after 20 pages or 5000 tags as a safety limit. For the overwhelming majority of repositories that captures every published tag; only a repository with many thousands of tags could push the newest stable tag beyond the cap, in which case the filter compares against the highest tag seen within the cap.
  • Regex timeout. Each evaluation has a 250 ms hard ceiling. A catastrophically backtracking pattern on a large tag list will surface as Tag pattern timed out evaluating tags: {message} in the status tooltip. Simplify the pattern — in practice this limit is only hit by deeply nested quantifiers on pathological inputs.

Diagnosing Filter Issues with “Check Now”

The fastest way to debug a suspicious Docker tag pattern filter homelab watch is the Check now button in the watch form. It triggers a full orchestrator run — list tags, apply the filter, sort, compare digest — and updates the last-check timestamp and status immediately. If the filter matched nothing, you will see the “No tags matched” message in the status tooltip right away, without waiting for the next background scan (which defaults to every 24 hours in Stashboard V2.2+).

The Test connection button is complementary but distinct. It verifies three things independently: Docker host reachability, whether the named container exists, and registry reachability using the pinned tag — not the filtered one. It validates the filter pattern server-side, so a syntax error in your regex surfaces here. But it does not run the full list-and-filter pipeline. Use Test connection to confirm your credentials and network path; use Check now to confirm your filter logic is matching what you expect.

Error messages to watch for in the status tooltip:

  • No tags matched the filter '{pattern}'. — The regex is valid but nothing in the registry matches it. Check the actual tag format the image publishes on Docker Hub or GHCR.
  • Invalid tag pattern: {message} — The regex failed to compile. Fix the syntax and save; the old pattern will stay active until you save a valid replacement.
  • Tag pattern timed out evaluating tags: {message} — The regex ran for more than 250 ms. Simplify the pattern, particularly if it uses deeply nested quantifiers or alternation on long strings.

Which Tag Pattern Filter Preset Should You Use?

As a starting point for any Docker tag pattern filter homelab deployment: choose SemVer only if the image follows three-part versioning consistently; switch to Stable only if the image mixes versioned tags with named aliases or date stamps and you want to block only the known pre-release suffixes; use Tag equals “stable” only after confirming the image actually publishes a stable tag — checking the Docker Hub tag list takes thirty seconds and prevents a debugging session later.

For the Stashboard container itself (vahac/stashboard), the SemVer only preset is the right choice. Releases follow strict vMAJOR.MINOR.PATCH versioning, and release candidates are tagged with a -rc suffix that the ^v?\d+\.\d+\.\d+$ pattern automatically excludes. If you run Stashboard to monitor other containers in your homelab and also want to monitor Stashboard itself with a second watch, this gives you a clean, noise-free update signal for both.

If you are setting up Stashboard for the first time or want to understand the broader update monitoring workflow it provides, the Stashboard — Self-Hosted Homelab Service Dashboard introductory post covers installation, Docker Compose configuration, and the core digest-comparison logic that tag filtering builds on top of.

For broader context on why well-structured Docker image tags matter for self-hosted security and stability, Docker Hardened Images for Self-Hosting is worth reading alongside this post. And if you are newer to Docker and want to understand the relationship between images, tags, and digests before diving into filter patterns, Docker for Beginners: Containers, Images, Volumes, Compose is the right starting point.

Conclusion

Docker tag pattern filter in Stashboard V2.1 solves a specific and common homelab problem: upstream pre-release builds creating constant false alerts that erode trust in your container update monitoring. The implementation is transparent — list tags, regex-filter, sort with a semver-aware comparer that applies the prerelease penalty from semver §11, compare the digest of the winner — and the three built-in presets cover the most common tag publishing conventions without writing a line of regex yourself. When you do need a custom pattern, the .NET engine’s negative lookahead support gives you the power to match almost any naming scheme, including the blocklist approach the “Stable only” preset uses.

The one discipline this feature requires is verification. After setting any filter, click Check now to confirm the pattern matches what you expect before the next scheduled background scan runs. A filter that matches nothing looks identical to a healthy watch from the outside — a green badge with no alerts — and that false confidence is exactly the noise that good homelab monitoring is supposed to eliminate. 🎯

Stashboard is open source — you can find the full source code on GitHub and pull the ready-to-run image directly from Docker Hub. Issues, PRs, and stars are always welcome. 🙌

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.