diff --git a/CHANGELOG.md b/CHANGELOG.md
index 396727384..ccc7a675f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [1.6.0-rc.2] — 2026-07-18
+
### Added
- **"Version update" container filter** ([#538](https://github.com/CodesWhat/drydock/issues/538)). The Containers filter bar gains a **Version Update** kind option that shows only real semver upgrades (major, minor, patch) and hides digest-only churn — the noise that dominates the dashboard on fleets that rebuild images daily in CI. Like every other container filter, it round-trips through the URL (`?filterKind=version`), so the filtered view can be saved as a bookmark for one-click access.
@@ -2155,7 +2157,8 @@ Remaining upstream-only changes (not ported — not applicable to drydock):
| Fix codeberg tests | Covered by drydock's own tests |
| Update changelog | Upstream-specific |
-[Unreleased]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.1...HEAD
+[Unreleased]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.2...HEAD
+[1.6.0-rc.2]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.1...v1.6.0-rc.2
[1.6.0-rc.1]: https://github.com/CodesWhat/drydock/compare/v1.5.2...v1.6.0-rc.1
[1.5.2]: https://github.com/CodesWhat/drydock/compare/v1.5.2-rc.5...v1.5.2
[1.5.2-rc.5]: https://github.com/CodesWhat/drydock/compare/v1.5.2-rc.4...v1.5.2-rc.5
diff --git a/README.md b/README.md
index 7ff7ef918..9f6a265b3 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -171,7 +171,7 @@ See the [Quick Start guide](https://getdrydock.com/docs/quickstart) for Docker C
🆕 Recent Updates
-v1.6.0-rc.1 highlights
+v1.6.0-rc.2 highlights
- **Notifications** — Per-rule/per-provider title and body templates with live preview, plus audit-backed in-app bell categories and update severity thresholds.
- **Dashboard** — Zero-dependency CSS Grid replacement with mouse/touch reorder, bounded resize, responsive layouts, widget visibility, reset, and optional cross-device preference sync.
diff --git a/apps/demo/src/mocks/data/agents.ts b/apps/demo/src/mocks/data/agents.ts
index 08dc96d5c..f2b60f218 100644
--- a/apps/demo/src/mocks/data/agents.ts
+++ b/apps/demo/src/mocks/data/agents.ts
@@ -4,7 +4,7 @@ export const agents = [
host: '192.168.1.50',
port: 3001,
connected: true,
- version: '1.6.0-rc.1',
+ version: '1.6.0-rc.2',
os: 'linux',
arch: 'amd64',
cpus: 4,
diff --git a/apps/demo/src/mocks/data/audit.ts b/apps/demo/src/mocks/data/audit.ts
index 2ecd513c3..c69337686 100644
--- a/apps/demo/src/mocks/data/audit.ts
+++ b/apps/demo/src/mocks/data/audit.ts
@@ -3,7 +3,7 @@ export const auditEntries = [
id: 'aud-001',
timestamp: '2026-03-10T08:00:00.000Z',
action: 'system:start',
- details: 'Drydock v1.6.0-rc.1 started',
+ details: 'Drydock v1.6.0-rc.2 started',
},
{
id: 'aud-002',
@@ -207,6 +207,6 @@ export const auditEntries = [
timestamp: '2026-03-03T18:00:00.000Z',
action: 'container:watch',
container: 'drydock',
- details: 'Started watching ghcr.io/codeswhat/drydock:1.6.0-rc.1',
+ details: 'Started watching ghcr.io/codeswhat/drydock:1.6.0-rc.2',
},
];
diff --git a/apps/demo/src/mocks/data/containers.ts b/apps/demo/src/mocks/data/containers.ts
index 231e31176..da777b2d6 100644
--- a/apps/demo/src/mocks/data/containers.ts
+++ b/apps/demo/src/mocks/data/containers.ts
@@ -330,7 +330,7 @@ export const containers = [
displayName: 'Drydock',
displayIcon: 'sh-drydock',
image: 'codeswhat/drydock',
- tag: '1.6.0-rc.1',
+ tag: '1.6.0-rc.2',
registryType: 'ghcr',
registryUrl: 'https://ghcr.io',
scanStatus: 'scanned',
diff --git a/apps/demo/src/mocks/data/server.ts b/apps/demo/src/mocks/data/server.ts
index df70b4564..a70aaf44d 100644
--- a/apps/demo/src/mocks/data/server.ts
+++ b/apps/demo/src/mocks/data/server.ts
@@ -1,5 +1,5 @@
export const serverInfo = {
- version: '1.6.0-rc.1',
+ version: '1.6.0-rc.2',
uptime: 864000,
hostname: 'drydock-demo',
platform: 'linux',
diff --git a/apps/demo/src/mocks/handlers/app.ts b/apps/demo/src/mocks/handlers/app.ts
index 8e9a0b1d8..692386b09 100644
--- a/apps/demo/src/mocks/handlers/app.ts
+++ b/apps/demo/src/mocks/handlers/app.ts
@@ -4,7 +4,7 @@ export const appHandlers = [
http.get('/api/v1/app', () =>
HttpResponse.json({
name: 'Drydock',
- version: '1.6.0-rc.1',
+ version: '1.6.0-rc.2',
description: 'Docker container update manager',
repository: 'https://github.com/CodesWhat/drydock',
documentation: 'https://getdrydock.com/docs',
@@ -16,7 +16,7 @@ export const appHandlers = [
return HttpResponse.json(
{
generatedAt: new Date().toISOString(),
- server: { version: '1.6.0-rc.1', mode: 'demo' },
+ server: { version: '1.6.0-rc.2', mode: 'demo' },
summary: {
containers: 25,
watchers: 2,
diff --git a/apps/web/src/lib/site-config.ts b/apps/web/src/lib/site-config.ts
index f0ec455f3..a00d1ab08 100644
--- a/apps/web/src/lib/site-config.ts
+++ b/apps/web/src/lib/site-config.ts
@@ -15,7 +15,7 @@ export const SITE_CONFIG = {
/** Brand name shown in the header, footer, and metadata. */
name: "Drydock",
/** Current release version shown in the hero badge. */
- version: "1.6.0-rc.1",
+ version: "1.6.0-rc.2",
/** Short product tagline used in page titles and OG metadata. */
tagline: "Container Update Monitoring",
/** Default meta / OpenGraph / Twitter description. */
diff --git a/apps/web/src/lib/site-content.ts b/apps/web/src/lib/site-content.ts
index 9fbf4b0ff..e9d82e249 100644
--- a/apps/web/src/lib/site-content.ts
+++ b/apps/web/src/lib/site-content.ts
@@ -281,7 +281,7 @@ export const roadmap: Milestone[] = [
],
},
{
- version: "v1.6.0-rc.1",
+ version: "v1.6.0-rc.2",
title: "Notifications, Policy & Release Intel",
emoji: "\u{1F4E8}",
status: "next",
diff --git a/content/docs/current/api/agent.mdx b/content/docs/current/api/agent.mdx
index 3776eb16f..54743a937 100644
--- a/content/docs/current/api/agent.mdx
+++ b/content/docs/current/api/agent.mdx
@@ -23,7 +23,7 @@ curl http://drydock:3000/api/v1/agents
"host": "192.168.1.50",
"port": 3000,
"connected": true,
- "version": "1.6.0-rc.1",
+ "version": "1.6.0-rc.2",
"os": "linux",
"arch": "amd64",
"cpus": 4,
@@ -153,7 +153,7 @@ Sent immediately upon connection to confirm the handshake.
{
"type": "dd:ack",
"data": {
- "version": "1.6.0-rc.1",
+ "version": "1.6.0-rc.2",
"os": "linux",
"arch": "amd64",
"cpus": 4,
diff --git a/content/docs/current/api/app.mdx b/content/docs/current/api/app.mdx
index 6849cfa91..91b780bf4 100644
--- a/content/docs/current/api/app.mdx
+++ b/content/docs/current/api/app.mdx
@@ -12,7 +12,7 @@ curl http://drydock:3000/api/v1/app
{
"name":"drydock",
- "version":"1.6.0-rc.1"
+ "version":"1.6.0-rc.2"
}
```
diff --git a/content/docs/current/api/portwing.mdx b/content/docs/current/api/portwing.mdx
index 7d8e16562..b1ac969cd 100644
--- a/content/docs/current/api/portwing.mdx
+++ b/content/docs/current/api/portwing.mdx
@@ -162,7 +162,7 @@ A versioned alias `/api/v1/portwing/ws` is also accepted and is signature-equiva
"agentId": "edge-host-01",
"agentName": "edge-host-01",
"protocol": "portwing/1.0",
- "version": "1.6.0-rc.1",
+ "version": "1.6.0-rc.2",
"pubKeyId": "3f8a1c2e9b047d56",
"timestamp": 1780329600,
"nonce": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
@@ -185,7 +185,7 @@ A name collision with an already-connected agent under the same key (or the in-f
"data": {
"pollInterval": 300,
"config": {
- "drydockVersion": "1.6.0-rc.1",
+ "drydockVersion": "1.6.0-rc.2",
"supportedProtocols": "portwing/1.0",
"serverCompatLevel": "1.4.0"
}
diff --git a/content/docs/current/quickstart/index.mdx b/content/docs/current/quickstart/index.mdx
index cd4fbcaab..72ff99fa8 100644
--- a/content/docs/current/quickstart/index.mdx
+++ b/content/docs/current/quickstart/index.mdx
@@ -102,7 +102,7 @@ Release tags use the same channel names in every registry:
| Tag | Behavior |
| --- | --- |
-| `1.6.0-rc.1` | Immutable release candidate; best for reproducible testing |
+| `1.6.0-rc.2` | Immutable release candidate; best for reproducible testing |
| `1.6-rc` | Rolling release-candidate channel; moves to the newest `1.6.0-rc.N` |
| `1.6` | Rolling stable minor channel; published only for GA releases |
| `1` | Rolling stable major channel; published only for GA releases |
diff --git a/content/docs/current/updates/index.mdx b/content/docs/current/updates/index.mdx
index 7e714c8bf..417a71db1 100644
--- a/content/docs/current/updates/index.mdx
+++ b/content/docs/current/updates/index.mdx
@@ -3,6 +3,12 @@ title: "Updates"
description: "Release update notes and feature highlights, with direct links to current configuration and API docs."
---
+## v1.6.0-rc.2 Highlights — July 18, 2026
+
+- **Watch errors no longer erase update state** — a container whose registry check fails keeps its last successful comparison alongside the recorded error instead of appearing never-compared, errored containers reuse the same fast refresh path as healthy ones, and repair rebuilds preserve the registry-reconciled digest that keys security scan grouping.
+- **"Version Update" container filter** — a new filter kind shows only real semver upgrades (major/minor/patch) and hides digest-only churn; bookmarkable via `?filterKind=version`.
+- **Docs aligned with shipped behavior** — FAQ and README corrections (registry list, scanner parity, runnable `config migrate` commands), the removed `WS /api/log/stream` alias documented in Deprecations, and the quickstart registry/tag matrix refreshed.
+
## v1.6.0-rc.1 Highlights — July 15, 2026
- **Notification rules are now a complete control surface** — configure delivery routing, audit-backed in-app bell categories, update severity threshold, and per-provider simple/batch templates in one detail panel; preview drafts before saving. Container-unhealthy events are bell-capable, and agent status changes refresh the bell promptly.
diff --git a/scripts/changelog-links.test.mjs b/scripts/changelog-links.test.mjs
index aa6603930..24c0c31cd 100644
--- a/scripts/changelog-links.test.mjs
+++ b/scripts/changelog-links.test.mjs
@@ -56,7 +56,8 @@ test('every linked changelog heading has exactly one link definition', () => {
test('v1.6 RC and v1.5.2 GA have a complete chronological comparison-link chain', () => {
const definitions = new Map(getLinkDefinitions(changelog).map(({ label, url }) => [label, url]));
const expected = new Map([
- ['Unreleased', `${repositoryUrl}/compare/v1.6.0-rc.1...HEAD`],
+ ['Unreleased', `${repositoryUrl}/compare/v1.6.0-rc.2...HEAD`],
+ ['1.6.0-rc.2', `${repositoryUrl}/compare/v1.6.0-rc.1...v1.6.0-rc.2`],
['1.6.0-rc.1', `${repositoryUrl}/compare/v1.5.2...v1.6.0-rc.1`],
['1.5.2', `${repositoryUrl}/compare/v1.5.2-rc.5...v1.5.2`],
['1.5.2-rc.5', `${repositoryUrl}/compare/v1.5.2-rc.4...v1.5.2-rc.5`],
diff --git a/scripts/release-docs-identity.test.mjs b/scripts/release-docs-identity.test.mjs
index 419d5441c..a3993a04b 100644
--- a/scripts/release-docs-identity.test.mjs
+++ b/scripts/release-docs-identity.test.mjs
@@ -2,9 +2,9 @@ import assert from 'node:assert/strict';
import { readdirSync, readFileSync } from 'node:fs';
import test from 'node:test';
-const RC_VERSION = '1.6.0-rc.1';
-const RC_DATE = '2026-07-15';
-const RC_DISPLAY_DATE = 'July 15, 2026';
+const RC_VERSION = '1.6.0-rc.2';
+const RC_DATE = '2026-07-18';
+const RC_DISPLAY_DATE = 'July 18, 2026';
const DOC_ROOTS = ['content/docs/current', 'content/docs/v1.5'];
function read(path) {
@@ -21,16 +21,16 @@ test('public release surfaces identify the v1.6 release candidate', () => {
const quickstart = read('content/docs/current/quickstart/index.mdx');
const changelog = read('CHANGELOG.md');
- assert.match(readme, /version-1\.6\.0--rc\.1-blue/u);
- assert.match(readme, /v1\.6\.0-rc\.1 highlights/u);
+ assert.match(readme, /version-1\.6\.0--rc\.2-blue/u);
+ assert.match(readme, /v1\.6\.0-rc\.2 highlights/u);
assert.match(siteConfig, new RegExp(`version: "${RC_VERSION.replaceAll('.', '\\.')}"`, 'u'));
assert.ok(updates.includes(`## v${RC_VERSION} Highlights — ${RC_DISPLAY_DATE}`));
- assert.match(appApi, /"version":"1\.6\.0-rc\.1"/u);
- assert.match(agentApi, /"version": "1\.6\.0-rc\.1"/u);
- assert.match(portwingApi, /"version": "1\.6\.0-rc\.1"/u);
- assert.match(portwingApi, /"drydockVersion": "1\.6\.0-rc\.1"/u);
- assert.match(quickstart, /\| `1\.6\.0-rc\.1` \| Immutable release candidate/u);
- assert.doesNotMatch(quickstart, /\| `1\.6\.0-rc\.(?!1\b)\d+` \| Immutable release candidate/u);
+ assert.match(appApi, /"version":"1\.6\.0-rc\.2"/u);
+ assert.match(agentApi, /"version": "1\.6\.0-rc\.2"/u);
+ assert.match(portwingApi, /"version": "1\.6\.0-rc\.2"/u);
+ assert.match(portwingApi, /"drydockVersion": "1\.6\.0-rc\.2"/u);
+ assert.match(quickstart, /\| `1\.6\.0-rc\.2` \| Immutable release candidate/u);
+ assert.doesNotMatch(quickstart, /\| `1\.6\.0-rc\.(?!2\b)\d+` \| Immutable release candidate/u);
assert.ok(changelog.includes(`## [${RC_VERSION}] — ${RC_DATE}`));
assert.ok(
changelog.includes(
@@ -39,7 +39,7 @@ test('public release surfaces identify the v1.6 release candidate', () => {
);
assert.ok(
changelog.includes(
- `[${RC_VERSION}]: https://github.com/CodesWhat/drydock/compare/v1.5.2...v${RC_VERSION}`,
+ `[${RC_VERSION}]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.1...v${RC_VERSION}`,
),
);
});
@@ -53,7 +53,7 @@ test('v1.5.2 is archived and public release routing advances to v1.6', () => {
assert.match(readme, /v1\.5\.2 highlights<\/strong><\/summary>/u);
assert.match(siteContent, /version: "v1\.5\.2",[\s\S]{0,500}?status: "released"/u);
- assert.match(siteContent, /version: "v1\.6\.0-rc\.1",[\s\S]{0,500}?status: "next"/u);
+ assert.match(siteContent, /version: "v1\.6\.0-rc\.2",[\s\S]{0,500}?status: "next"/u);
assert.match(
docsVersions,
/\{ slug: "v1\.6", source: "current", title: "v1\.6" \},\s+\{ slug: "v1\.5", source: "v1\.5", title: "v1\.5" \}/u,
diff --git a/scripts/release-identity.test.mjs b/scripts/release-identity.test.mjs
index f1f59f33d..70e08f767 100644
--- a/scripts/release-identity.test.mjs
+++ b/scripts/release-identity.test.mjs
@@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs';
import test from 'node:test';
const BASE_VERSION = '1.6.0';
-const RC_VERSION = '1.6.0-rc.1';
+const RC_VERSION = '1.6.0-rc.2';
const DEMO_RELEASE_FIXTURES = [
{
path: 'apps/demo/src/mocks/data/server.ts',
@@ -52,7 +52,7 @@ test('release-gated workspace packages and locks use the v1.6 base version', ()
}
});
-test('demo runtime fixtures identify the exact v1.6.0-rc.1 candidate', () => {
+test('demo runtime fixtures identify the exact v1.6.0-rc.2 candidate', () => {
for (const { path, valuePattern } of DEMO_RELEASE_FIXTURES) {
const contents = readFileSync(path, 'utf8');
assert.deepEqual(extractVersionValues(contents, valuePattern), [RC_VERSION], path);
@@ -63,18 +63,18 @@ test('release version patterns match exact optionally v-prefixed tokens', () =>
const rcPattern = versionPattern(RC_VERSION);
const legacyPattern = versionPattern('1.5.0');
- assert.match('version: 1.6.0-rc.1', rcPattern);
- assert.match('version: v1.6.0-rc.1', rcPattern);
- assert.doesNotMatch('version: 1.6.0-rc.10', rcPattern);
- assert.doesNotMatch('version: x1.6.0-rc.1', rcPattern);
+ assert.match('version: 1.6.0-rc.2', rcPattern);
+ assert.match('version: v1.6.0-rc.2', rcPattern);
+ assert.doesNotMatch('version: 1.6.0-rc.20', rcPattern);
+ assert.doesNotMatch('version: x1.6.0-rc.2', rcPattern);
assert.match('version: v1.5.0', legacyPattern);
assert.doesNotMatch('version: 1.5.0-rc.1', legacyPattern);
});
test('fixture version extraction retains mixed candidate identities', () => {
- const contents = "version: '1.6.0-rc.1', version: '1.6.0-rc.10'";
+ const contents = "version: '1.6.0-rc.2', version: '1.6.0-rc.20'";
assert.deepEqual(extractVersionValues(contents, /version:\s*["']([^"']+)["']/gu), [
- '1.6.0-rc.1',
- '1.6.0-rc.10',
+ '1.6.0-rc.2',
+ '1.6.0-rc.20',
]);
});