Skip to content

Commit fae72d5

Browse files
committed
release: v6.17.0 — HA mode preview (Redis rate limiter + cluster abstraction)
Opt-in HA via DD_MODE=ha. Closes BACKLOG F30 partially. Standalone users: zero impact (ioredis is optionalDependencies, cluster.js lazy-evaluates to no-ops, no env var changes). New: - src/services/cluster.js — HA abstraction. Standalone mode every method is a cheap no-op; HA mode lazy-connects to Redis (REDIS_URL default redis://redis:6379). - src/services/rate-limiter-memory.js — extracted sliding-window limiter from middleware. Clean delegation contract. - src/middleware/rateLimit.js — rewritten to delegate via cluster.rateLimitTick. Fail-open on Redis errors (warn log). Adds X-RateLimit-Remaining response header. - docker-compose --profile ha with redis:7-alpine (128MB cap, LRU, snapshot persistence, no exposed ports). - docs/features/ha-mode.md — operator reference including "when NOT to use HA mode". Tests: 843/55 → 866/57 (+23). 9 memory-limiter tests, 14 cluster tests via ioredis-mock (no real Redis needed). Deps: - ioredis ^5.10.1 → optionalDependencies - ioredis-mock ^8.13.1 → devDependencies npm audit clean. v6.17.0 preview limitations (loudly documented): - Don't run multi-replica in HA mode yet — every replica runs every cron job, duplicate backups + concurrent VACUUM risk. - WS broadcasts still per-replica. Fixed in v7.0.0: alpha.1 = WS pub/sub, rc.1 = leader election. Single-replica HA today is useful for operational drill (sticky-LB config, Prometheus scrape of Redis) before v7.0 rolls out multi-replica. Lint 0/0. Rollback = single-commit revert.
1 parent 745c171 commit fae72d5

16 files changed

Lines changed: 1001 additions & 68 deletions

BACKLOG.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,24 @@ This is the single source of truth for deferred work. Each item lists WHY it's d
2525

2626
**Enterprise LDAP users:** test on staging before updating production. Confidence is medium (code correct per docs, but unverified against a live server).
2727

28-
### F30 — Distributed rate limiter (Redis-backed)
28+
### F30 — Distributed rate limiter (Redis-backed) — opt-in HA mode
2929

30-
**Why deferred:** Current rate limiter is in-memory per-process. Works perfectly for single-instance deploys (the default). Horizontal-scale (multi-pod) breaks: each pod has its own counter, so a `10 req/min` limit becomes `10 × N pods` effectively.
30+
**Status (updated 2026-04-22):** 🟡 Partial — foundation shipped in v6.17.0 (preview).
3131

32-
**Why not fix now:** Docker Dash's product positioning is single-instance deploy. Horizontal scale requires:
33-
- Redis container in compose
34-
- Shared session store (currently SQLite-backed sessions, same issue)
35-
- Sticky routing OR full session-sharing via Redis
32+
**v6.17.0 (shipped):**
33+
- `src/services/cluster.js` — HA abstraction with `DD_MODE=ha` opt-in. Zero overhead for standalone users.
34+
- Redis-backed rate limiter via `INCR + PEXPIRE` fixed-window (2× looser than standalone sliding-window at bucket boundaries — documented trade-off).
35+
- `docker-compose --profile ha` with `redis:7-alpine` service (optional, off by default).
36+
- `ioredis` as `optionalDependencies` (not `dependencies`). Standalone installs don't pull it.
37+
- 23 new tests via `ioredis-mock` (no real Redis needed to run the suite).
38+
- `docs/features/ha-mode.md` — operator-facing reference.
3639

37-
All of the above = 3-5 days of infra work. Out of scope for a single-box product.
40+
**Still deferred for v7.0.0 (per [`plans/deep-spec-ha-mode.md`](plans/deep-spec-ha-mode.md)):**
41+
- v7.0.0-alpha.1 — WebSocket pub/sub via Redis (fix "user on replica A misses events from replica B")
42+
- v7.0.0-rc.1 — Leader election via `SET NX PX` for the 13 cron jobs, Docker event stream, SSH tunnels, git polling
43+
- v7.0.0 stable — failover runbook, sticky-session LB docs, staging soak
3844

39-
**Estimated effort:** 4-5 days.
40-
**Proposed approach:** release as v7.0 "HA mode" with opt-in `DD_MODE=ha` env var; default stays single-instance.
45+
**Known v6.17.0 limitations (loudly documented):** **don't run multi-replica in HA mode yet.** Every replica runs every cron job → duplicate backups, concurrent `VACUUM` risk. Single-replica HA mode is only useful for operational drill (Prometheus scrape, LB config) before v7.0 rolls out true multi-replica.
4146

4247
---
4348

CHANGELOG.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,102 @@
22

33
All notable changes to Docker Dash are documented here.
44

5+
## [6.17.0] - 2026-04-22 — "HA mode preview — Redis-backed rate limiter + cluster foundation"
6+
7+
**Opt-in HA** — closes BACKLOG F30 partially. `DD_MODE=ha` + Redis unlocks cross-replica rate limiting; the rest of the HA story (WS pub/sub, cron leader election) lands in v7.0.0. Standalone users: **zero impact** — default unchanged, `ioredis` is in `optionalDependencies` (not `dependencies`), no new env vars required.
8+
9+
Full background and architecture: [`plans/research-ha-mode-optional.md`](plans/research-ha-mode-optional.md) + [`plans/deep-spec-ha-mode.md`](plans/deep-spec-ha-mode.md) (local/gitignored).
10+
11+
### Added — `src/services/cluster.js` HA abstraction
12+
13+
New service module ([`src/services/cluster.js`](src/services/cluster.js)) that every HA-eligible subsystem imports. Standalone mode: every method is a cheap no-op or falls through to in-process state (zero runtime overhead). HA mode: lazy-connects to Redis via `REDIS_URL`.
14+
15+
Public API:
16+
- `cluster.isHa()` / `cluster.nodeId()` — mode introspection
17+
- `cluster.redis()` — ioredis client in HA, null in standalone
18+
- `cluster.rateLimitTick(key, maxReqs, windowMs)` — returns `{ allowed, remaining, retryAfterSec }`
19+
- `cluster.publish(ch, payload)` / `cluster.subscribe(ch, handler)` — stubbed in v6.17.0, wired in v7.0.0-alpha.1
20+
- `cluster.isLeader()` — returns `true` in v6.17.0 (stub), real election in v7.0.0-rc.1
21+
22+
### Added — Redis-backed rate limiter
23+
24+
Extracted the existing in-memory `Map`-based limiter into `src/services/rate-limiter-memory.js` (sliding window, same semantics as before). New HA path in `cluster.rateLimitTick` uses Redis `INCR` + `PEXPIRE` (fixed window — 2× looser at bucket boundaries, documented trade-off in `docs/features/ha-mode.md` §"Rate-limiter semantics").
25+
26+
`src/middleware/rateLimit.js` rewritten to delegate. **Fail-open on Redis errors** — a mid-request Redis outage lets the request through with a `warn` log, prioritizing availability over strict quota.
27+
28+
### Added — `docker-compose --profile ha` + `redis:7-alpine` service
29+
30+
Opt-in HA profile in [`docker-compose.yml`](docker-compose.yml):
31+
```bash
32+
docker compose --profile ha up -d
33+
# Then .env: DD_MODE=ha, REDIS_URL=redis://redis:6379
34+
```
35+
36+
Redis configured with:
37+
- `--save 60 1000` — snapshot persistence on ≥1000 writes / 60s
38+
- `--maxmemory 128mb --maxmemory-policy allkeys-lru` — hard cap
39+
- `no-new-privileges:true` — matches the rest of the compose security posture
40+
- No exposed ports — only reachable via the Docker network
41+
42+
### Added — Tests (23 new, all pass via `ioredis-mock` — no real Redis needed)
43+
44+
- [`src/__tests__/rate-limiter-memory.test.js`](src/__tests__/rate-limiter-memory.test.js) — 9 tests covering sliding-window semantics, key isolation, expiration, cleanup
45+
- [`src/__tests__/cluster.test.js`](src/__tests__/cluster.test.js) — 14 tests: 8 standalone (all methods no-op correctly) + 6 HA (Redis path via `jest.doMock('ioredis')``ioredis-mock`)
46+
47+
**Test suite: 843/55 → 866/57.**
48+
49+
### Added — `docs/features/ha-mode.md`
50+
51+
Operator reference. Covers: what HA changes, enabling, architecture, Redis keys, rate-limiter semantics, failure modes, monitoring, when NOT to use HA mode, rollback procedure.
52+
53+
### Changed — Dependencies
54+
55+
- `ioredis ^5.10.1` added as **`optionalDependencies`** (not `dependencies`). Standalone installs don't pull it.
56+
- `ioredis-mock ^8.13.1` added as `devDependencies` for unit tests.
57+
- `npm audit` clean (0 vulnerabilities).
58+
59+
### ⚠️ v6.17.0 Preview Limitations (loudly documented)
60+
61+
**Don't run multi-replica in HA mode yet.** Every replica runs every cron job → duplicate daily backups, concurrent `VACUUM` (DB corruption risk), N× certificate scans, N× secret rotation checks. This is fixed in v7.0.0-rc.1 via leader election.
62+
63+
**WS broadcasts still per-replica.** User connected to replica A misses events emitted by replica B. Fixed in v7.0.0-alpha.1 via Redis pub/sub.
64+
65+
Single-replica HA mode today is only useful for operational drill — wiring sticky-session load balancers, Prometheus scrape of Redis, Grafana dashboards — before rolling out true multi-replica in v7.0.
66+
67+
### BACKLOG F30 — partial close
68+
69+
Shipped: cluster abstraction + Redis rate limiter + `--profile ha` + docs.
70+
Remaining for v7.0: WS pub/sub (v7.0.0-alpha.1), cron leader election (v7.0.0-rc.1), failover runbook (v7.0.0 stable).
71+
72+
### Rollback
73+
74+
Single-commit revert. `ioredis` becomes an unused `optionalDependencies` entry (harmless). `--profile ha` becomes a no-op profile.
75+
76+
### Production readiness
77+
78+
Unchanged at 9.7/10 this release. v6.17.0 is about enabling a new deployment mode for enterprise users, not about closing residual standalone gaps. Scorecard moves only when v7.0 stable lands with real multi-replica support + failover tests.
79+
80+
### Files touched
81+
82+
- `src/services/cluster.js` (new, ~110 LOC)
83+
- `src/services/rate-limiter-memory.js` (new, ~50 LOC — extracted from middleware)
84+
- `src/middleware/rateLimit.js` — rewritten to delegate via cluster (~45 LOC, was ~55)
85+
- `src/__tests__/cluster.test.js` (new, 14 tests)
86+
- `src/__tests__/rate-limiter-memory.test.js` (new, 9 tests)
87+
- `docker-compose.yml``redis:7-alpine` service behind `--profile ha`
88+
- `docs/features/ha-mode.md` (new)
89+
- `package.json``ioredis``optionalDependencies`, `ioredis-mock``devDependencies`
90+
- `BACKLOG.md` — F30 updated with partial-close status
91+
- `README.md` / `SECURITY.md` / `CONTRIBUTING.md` — test counts + new Feature Reference link
92+
93+
### Tests
94+
95+
- **866 passing + 4 skipped / 57 suites**
96+
- Lint: 0 warnings / 0 errors
97+
- `npm audit`: 0 vulnerabilities
98+
99+
---
100+
5101
## [6.16.1] - 2026-04-22 — "Testing 8.5 → 9.5, Documentation 9 → 9.5 (production readiness 9.5 → 9.7)"
6102

7103
Pure test + docs release. No runtime code changes. Closes two of the three remaining gaps to 10/10 production readiness.

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ Looking for where to start? These are great first contributions:
1111
- **Add a language translation** — copy `public/js/i18n/TEMPLATE.js`, translate values, add one `<script>` tag. Currently: 11 languages (EN, RO, DE, IT, FR, ES, PT, ZH, JA, KO, Klingon).
1212
- **Add an app template** — add an entry to `src/routes/templates.js` (JSON object with compose YAML). Currently: 33 templates.
1313
- **Improve i18n coverage** — some pages still have hardcoded English strings (grep for strings not using `i18n.t()`)
14-
- **Add tests**843 tests across 55 suites; more coverage is always welcome, especially integration tests
14+
- **Add tests**866 tests across 57 suites; more coverage is always welcome, especially integration tests
1515
- **Documentation** — improve README, add examples, write tutorials
1616
- **Accessibility** — add ARIA attributes, improve screen reader support, test keyboard navigation
1717

1818
### Project Stats (v5.3.0)
1919

2020
- **24 pages** in the frontend SPA (incl. Swarm, Compare with 8 tools)
2121
- **230+ API endpoints** (see `/api/docs` for full list)
22-
- **843 tests** (55 test suites, 100% passing)
22+
- **866 tests** (57 test suites, 100% passing)
2323
- **33 app templates** (+ custom user templates)
2424
- **37 database migrations** (001-037)
2525
- **11 languages** (EN, RO, DE, IT, FR, ES, PT, ZH, JA, KO, Klingon)

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<a href="https://github.com/bogdanpricop/docker-dash/actions/workflows/ci.yml"><img src="https://github.com/bogdanpricop/docker-dash/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
99
<a href="https://github.com/bogdanpricop/docker-dash/releases/latest"><img src="https://img.shields.io/github/v/release/bogdanpricop/docker-dash?color=blue" alt="Release"></a>
1010
<a href="LICENSE"><img src="https://img.shields.io/github/license/bogdanpricop/docker-dash" alt="License"></a>
11-
<a href="https://github.com/bogdanpricop/docker-dash/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-843%20passing%20(100%25)-brightgreen" alt="Tests"></a>
12-
<img src="https://img.shields.io/badge/version-6.16.1-blue" alt="Version">
11+
<a href="https://github.com/bogdanpricop/docker-dash/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-866%20passing%20(100%25)-brightgreen" alt="Tests"></a>
12+
<img src="https://img.shields.io/badge/version-6.17.0-blue" alt="Version">
1313
<a href="SECURITY.md#security-audit-history"><img src="https://img.shields.io/badge/production%20readiness-9.7%2F10-brightgreen" alt="Production Readiness"></a>
1414
<a href="SECURITY.md"><img src="https://img.shields.io/badge/security-audited-brightgreen" alt="Security Audited"></a>
1515
<img src="https://img.shields.io/badge/Docker-~80MB-blue" alt="Image Size">
@@ -26,7 +26,9 @@
2626
</p>
2727
</p>
2828

29-
**Zero dependencies to deploy** — just Docker. No external database, no Redis, no build step. Current version: **v6.16.1**
29+
**Zero dependencies to deploy** — just Docker. No external database, no Redis, no build step. Current version: **v6.17.0**
30+
31+
**New in v6.17.0:** Optional HA mode via `DD_MODE=ha` + Redis. Preview only — Redis-backed rate limiter + cluster abstraction shipped; WS pub/sub + cron leader election land in v7.0. See [docs/features/ha-mode.md](docs/features/ha-mode.md).
3032

3133
## Screenshots
3234

@@ -212,7 +214,7 @@
212214
- **Self-Reporting Footprint** — Docker Dash memory, uptime, DB size at `/api/footprint`
213215
- **Let's Encrypt Wizard** — 3-step UI for issuing certs via DNS-01 (Cloudflare, Route53, DigitalOcean, Hetzner, Linode) or HTTP-01. Encrypted credential vault, auto-renewal via Caddy, hash-chained audit trail. Open source — no other Docker UI ships this
214216
- **Container Remediation Wizard** — 3-step UI that turns Secrets Audit + CIS Benchmark findings into actionable fixes. 20-entry catalog, 4 live-updatable (zero downtime), 16 with compose-recreate + auto-rollback. Git-PR mode for git-backed stacks. No other OSS Docker UI ships this
215-
- **843 Tests**55 test suites covering auth, RBAC, security, CRUD, services, ACME + remediation orchestrators, platform detection, DMI cloud detection, translations, Prometheus metrics, permissions RBAC, settings CRUD, security alert rule evaluation, event notifier dispatch (100% passing)
217+
- **866 Tests**57 test suites covering auth, RBAC, security, CRUD, services, ACME + remediation orchestrators, platform detection, DMI cloud detection, translations, Prometheus metrics, permissions RBAC, settings CRUD, security alert rule evaluation, event notifier dispatch, cluster abstraction (HA mode), rate-limiter memory + Redis paths (100% passing)
216218

217219
### Feature Reference
218220

@@ -221,6 +223,7 @@ Dedicated reference docs for the deeper features, in [docs/features/](docs/featu
221223
- **[Prometheus Metrics](docs/features/prometheus-metrics.md)**`/api/metrics` endpoint reference, metric names + types + labels, sample Grafana queries, cardinality notes
222224
- **[Platform Detection](docs/features/platform-detection.md)** — NAS + cloud + hypervisor detection logic; complete signature list; how to extend
223225
- **[Translations Tooling](docs/features/translations-tooling.md)** — Google Translate + DeepL integration, quota tracking, review workflow, runtime DB overrides
226+
- **[HA Mode](docs/features/ha-mode.md)** — optional Redis-backed redundancy (v6.17.0 preview, full in v7.0); architecture, trade-offs, when NOT to use it
224227

225228
## Where to start
226229

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ If you discover a security vulnerability in Docker Dash, please report it respon
8888

8989
## Testing
9090

91-
- **843 tests** across 55 test suites (100% passing; 4 skipped are live-Cloudflare integration tests gated on a CI secret)
91+
- **866 tests** across 57 test suites (100% passing; 4 skipped are live-Cloudflare integration tests gated on a CI secret)
9292
- Unit tests: crypto round-trip, input validation, shell sanitization, git patterns
9393
- Integration tests: auth flow (login, session, logout, SSO), API endpoints (supertest), RBAC, security alerts
9494
- **CI pipeline** — GitHub Actions runs tests + syntax check + npm audit on every push

docker-compose.yml

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ services:
44
context: .
55
dockerfile: Dockerfile
66
args:
7-
APP_VERSION: "${APP_VERSION:-6.16.1}"
8-
image: docker-dash:${APP_VERSION:-6.16.1}
7+
APP_VERSION: "${APP_VERSION:-6.17.0}"
8+
image: docker-dash:${APP_VERSION:-6.17.0}
99
container_name: docker-dash
1010
restart: unless-stopped
1111
env_file:
@@ -54,7 +54,7 @@ services:
5454
dd-egress-filter:
5555
build:
5656
context: ./docker/egress-filter
57-
image: docker-dash-egress-filter:${APP_VERSION:-6.16.1}
57+
image: docker-dash-egress-filter:${APP_VERSION:-6.17.0}
5858
container_name: dd-egress-filter
5959
restart: unless-stopped
6060
# Uses the default bridge so target containers on the default bridge can
@@ -78,6 +78,36 @@ services:
7878
profiles:
7979
- egress
8080

81+
# Optional Redis for HA mode — enable with: docker compose --profile ha up -d
82+
# Then on the `app` service (via .env):
83+
# DD_MODE=ha
84+
# REDIS_URL=redis://redis:6379
85+
#
86+
# v6.17.0 ships the foundation (Redis-backed rate limiter + cluster abstraction).
87+
# DO NOT run multi-replica in HA mode yet — WS pub/sub + cron leader election
88+
# land in v7.0.0-alpha.1 / v7.0.0-rc.1. Running v6.17.0 HA with 2+ replicas
89+
# causes duplicate cron execution (duplicate backups, concurrent VACUUM).
90+
#
91+
# Single-instance HA (1 replica + Redis) is useful for warming up operational
92+
# tooling (Prometheus scrape of Redis, sticky-session LB config drill, etc.)
93+
# before rolling out true multi-replica in v7.0.0.
94+
redis:
95+
image: redis:7-alpine
96+
container_name: docker-dash-redis
97+
restart: unless-stopped
98+
command: redis-server --save 60 1000 --maxmemory 128mb --maxmemory-policy allkeys-lru
99+
volumes:
100+
- redis-data:/data
101+
healthcheck:
102+
test: ["CMD", "redis-cli", "ping"]
103+
interval: 10s
104+
timeout: 3s
105+
retries: 3
106+
security_opt:
107+
- no-new-privileges:true
108+
profiles:
109+
- ha
110+
81111
# Optional HTTPS reverse proxy — enable with: docker compose --profile tls up -d
82112
# Configure via Docker Dash UI: System → SSL/TLS → Enable HTTPS
83113
caddy:
@@ -111,3 +141,5 @@ volumes:
111141
name: docker-dash-egress-policy
112142
egress-logs:
113143
name: docker-dash-egress-logs
144+
redis-data:
145+
name: docker-dash-redis-data

0 commit comments

Comments
 (0)