Deep audit additions:
P2.5 Security: route validation gap (151/160 unvalidated), cmd injection
surface in ca.js, 30 untested source files, no .dockerignore, Math.random IDs
P3.5 Ops: error codes, SDK/types, log rotation, license rate limit, Node
version pin, Dependabot, dependency health checks, workflow retry, audit trail
P4 Advanced: multi-user RBAC, API keys, Prometheus/Grafana, changelog,
migration system, service auto-discovery
28 KiB
28 KiB
DashCaddy Production-Grade Backlog (v2)
Generated 2026-08-12 from a full codebase audit. v1 items (P0-1 through P2-7) are ALL DONE. Current state: 1539 tests, 86.55% statement coverage, 0 ESLint errors, 173 warnings.
Current Health Snapshot
- Tests: 1539 passing across 63 suites
- Coverage: Statements 86.55% | Branches 72.14% (below 80% gate) | Functions 80.8% | Lines 90.67%
- ESLint: 0 errors, 173 warnings (all pre-existing)
- Remaining console. calls in src/:* 21 across 10 files
- Dockerfile: Runs as root (documented — needs Docker socket), no resource limits
- OpenAPI spec: Present but stale (says v1.0.0, actual is v1.15.0)
- Unhandled rejection/exception handlers: Present in server.js ✓
- Rate limiting: Present on auth + general routes ✓
- npm audit: 4 remaining vulns (semver-major transitive deps, deferred)
P0 — Must Fix (blocks public release)
DC-062: OpenAPI spec is stale — update to match actual v1.15.0 API surface
- status: pending
- details:
openapi.yamlsaysversion: 1.0.0and describes only a fraction of the API. Since DC-046/047 (auth providers), DC-053 (share), DC-055 (billing), DC-058 (share UI), and the tailscale-admin routes were added, the spec is significantly out of date. A stale spec is worse than no spec — it misleads API consumers and breaks any code generation from it. Fix: audit all route files (grep -rn 'router\.\(get\|post\|put\|delete\|patch\)' routes/), update openapi.yaml with every endpoint, bump version to 1.15.0, add it to the test suite (DC-017-style source-of-truth test that fails if a route exists but has no spec entry). Effort: ~3 hr. - impact: Public API trust. No paying customer can integrate against an undocumented API.
DC-063: Branch coverage at 72% — below the 80% gate
- status: pending
- details: Jest coverage report shows branches at 72.14% (303/420), failing the 80% threshold. The uncovered branches are concentrated in error-handling paths (catch blocks, fallback returns, edge-case conditionals). Fix: run
npx jest --coverage --coverageReporters=textto identify the files with the lowest branch coverage, then add targeted tests for the uncovered conditional paths. Priority files: backup-manager.js (multiple catch blocks), health-checker.js (timeout/retry branches), tailscale-coord.js (API error branches). Effort: ~2 hr. - impact: Error paths are where production incidents hide. Every untested catch block is a potential crash.
DC-064: Dockerfile runs as root with no resource limits
- status: pending
- details: The Dockerfile has no
USERdirective andstart.shhas no--memoryor--cpusflags. While root is needed for Docker socket access, the container can still OOM the host. Fix: (1) Add--memory=512m --memory-swap=1g --cpus=1.5to thedocker runin start.sh. (2) Create a non-root userdashcaddyfor the application process, and use a Docker socket proxy (liketecnativa/docker-socket-proxy) that exposes a limited subset of Docker API endpoints — the app only needs read access for monitoring + controlled container lifecycle. (3) Add--restart=unless-stoppedif not already present. Effort: ~2 hr. Risk: medium — socket proxy may break some Docker API calls, needs testing. - impact: Without limits, a memory leak in the API can take down the entire host. This is a production safety issue.
P1 — Code Quality & Reliability
DC-065: Remaining 21 console.* calls — sweep to structured logger
- status: pending
- details: After DC-060 (update-manager) and P1-3 through P1-8, 21 console calls remain across 10 files:
error-handler.js(2),email.js(1),dns-providers/registry.js(2),audit-logger.js(3),csrf-protection.js(3),config-drift-detector.js(1),auto-restart-manager.js(1),http.js(1),logging.js(6 intentional — the logger itself),routes/backups.js(1). The logging.js calls are fine (the logger IS console internally). The rest should route throughlog.info/warn/error. Some are fallbacks:ctx.logError || ((_c, err) => console.error(err))— these fire when ctx isn't available, which is exactly when structured logging matters most. Effort: ~45 min. - impact: Consistency. The logger write to error.log and supports structured JSON — console does not.
DC-066: No API integration test for the billing flow end-to-end
- status: pending
- details: DC-057 shipped contract tests and unit tests for the Stripe bridge, but there is no test that exercises the full flow: pricing page → Stripe Checkout → webhook → license-key delivery → license activation → Pro unlock. Build a single integration test that mocks Stripe's API, walks the complete flow, and asserts the license works at the end. This is the revenue path — it must be tested as a chain, not just individual pieces. Effort: ~2 hr.
- impact: Confidence in the revenue pipeline. A broken webhook or catalog mismatch silently loses sales.
DC-067: No graceful shutdown — SIGTERM kills in-flight requests
- status: pending
- details: server.js handles
uncaughtExceptionandunhandledRejection, but there is noSIGTERMhandler that callsserver.close()to drain connections. Docker stop sends SIGTERM (the Dockerfile hasSTOPSIGNAL SIGTERM), but without a handler the process exits immediately, dropping any in-flight API calls. Fix: add aSIGTERMhandler in server.js that (1) stops accepting new connections viaserver.close(), (2) waits up to 10s for in-flight requests, (3) closes DB/file handles, (4) exits cleanly. Also emit ashutdownevent so managers (health checker, SSL monitor, workflow engine) can stop their timers. Effort: ~1 hr. - impact: Zero-downtime deployments. Currently, every
docker stopdrops active requests.
DC-068: ESLint warnings sweep — 173 pre-existing warnings
- status: pending
- details: While there are 0 ESLint errors, 173 warnings remain. Top files:
dns-providers/base.js(27),update-manager.js(14),backup-manager.js(10),keychain-manager.js(10),bundled-workflows.js(10),auth/providers/base.js(9),log-digest.js(8). Most areno-unused-vars,require-await,no-nested-ternary. Fix: sweep through the top 10 files, fix what's actionable (unused vars → remove, nested ternaries → extract to named variables, false-positive require-await → mark_or restructure). Set a ceiling: warnings should never increase. Effort: ~2 hr. - impact: Clean codebase. 173 warnings is noise that hides real issues when new ones are added.
DC-069: Health check notification spam — add failure threshold + cooldown
- status: pending
- details: The workflow engine sends a notification on EVERY health check failure (every 15 min). If a service is down for a day, that's 96 identical notifications. There is no backoff, no deduplication, no "service recovered" message. Fix: (1) Only notify on state TRANSITIONS (up→down, down→up), not every failure. (2) Add a
consecutiveFailuresthreshold (e.g., 2 failures before first alert) to avoid flapping noise. (3) Send a recovery notification when a service comes back up. (4) Optional: daily digest of uptime stats instead of per-failure alerts. Effort: ~1.5 hr. - impact: Operator sanity. The current notification volume is exactly why people mute alerting channels — and then miss real incidents.
P2 — Polish & Developer Experience
DC-070: No CI/CD pipeline — tests run manually
- status: pending
- details: There is no GitHub Actions / CI configuration. Tests are run manually before push. This means a bad commit can reach main if someone forgets to test. Fix: add
.github/workflows/test.yml(or Gitea Actions equivalent) that runsnpm ci && npx jest --coverageon every PR and push to main. Cache node_modules. Upload coverage report as artifact. Block merge on test failure or coverage decrease. Effort: ~1 hr. - impact: Automated quality gate. No bad commit reaches production.
DC-071: No error tracking / Sentry integration
- status: pending
- details: Errors go to
error.loginside the container. If the container is recreated (DC-050 migration), the error log is lost. There is no external error tracking. Fix: add an optional Sentry (or GlitchTip for self-hosted) integration. IfSENTRY_DSNenv var is set, initialize Sentry before Express. Wrap async handlers to capture exceptions. The error-handler.js middleware should forward to Sentry before returning the generic error response. Make it opt-in (no DSN = no Sentry, zero behavior change). Effort: ~1 hr. - impact: Production visibility. Right now, errors are invisible unless someone SSHs in and reads the log.
DC-072: Frontend bundle has no source maps in production
- status: pending
- details:
status/build.jsuses esbuild but the production build doesn't emit source maps. When a frontend error occurs in production, the stack trace points to minified bundle lines — useless for debugging. Fix: addsourcemap: trueto the esbuild production config. Serve.mapfiles from Caddy (they're already indist/). Optionally upload source maps to Sentry (DC-071). Effort: ~30 min. - impact: Frontend bug reports become actionable instead of "line 1 of core.js".
DC-073: No API request/response logging middleware for debugging
- status: pending
- details: While there is an audit logger for POST/PUT/DELETE, there's no request/response logging middleware for debugging purposes (like morgan or a custom equivalent). When an operator reports "the dashboard is slow" or "this endpoint returns 500 sometimes", there's no way to trace the request through the system. Fix: add an optional debug-level request logger that logs method, path, status, duration, and request ID. Gated behind
LOG_LEVEL=debugso it's off in production by default. Effort: ~45 min. - impact: Drastically reduces time-to-resolution for production issues.
DC-074: Docker image is not multi-stage — build artifacts bloat the image
- status: pending
- details: The Dockerfile copies source files into a single stage based on
node:20-alpine. The image includesdevDependenciesbecausenpm install --productionstill installs some optional deps, and there's no.dockerignore(so__tests__/,.git/,node_modules/from the host can leak in). Fix: (1) Add a.dockerignorefile excluding__tests__/,.git/,node_modules/,*.md,coverage/. (2) Convert to multi-stage: build stage installs all deps, production stage copies onlynode_modules/(production) + source. (3) Pin Node.js version:FROM node:20.10-alpineinstead ofnode:20-alpine(floating). Effort: ~1 hr. - impact: Smaller image = faster pulls = faster deploys. Current image size carries unnecessary weight.
DC-075: No health check dashboard endpoint for operators
- status: pending
- details: The
/api/v1/monitoring/statsendpoint returns container stats, but there's no single "is everything OK" endpoint that returns a human-readable system health summary. Fix: addGET /api/v1/system/healththat returns{ status: "healthy"|"degraded"|"unhealthy", checks: { database: "ok", diskSpace: "ok", memory: "ok", uptime: ..., activeServices: N/M, lastError: "..." } }. This is useful for uptime monitoring services (UptimeRobot, BetterStack) and for a quick operator glance. Effort: ~1 hr. - impact: Operators can plug DashCaddy into external monitoring without parsing container stats.
P3 — Future & Nice-to-Have
DC-076: WebSocket support for real-time dashboard updates
- status: pending
- details: The dashboard polls the API every N seconds for service status updates. For a "live" dashboard experience, WebSocket (or SSE) push would be better — status changes appear instantly without polling overhead. Fix: add a WebSocket server (using
wslibrary) that pushes service status changes, health check results, and container events to connected dashboard clients. Keep polling as fallback for clients without WS support. Effort: ~3 hr. - impact: Dashboard feels "live". Reduces API load from polling.
DC-077: Multi-language (i18n) support
- status: pending
- details: All UI text is hardcoded English. For a public product, internationalization is a step toward wider reach. Fix: extract all user-facing strings into a locale file, add an i18n library (like i18next), provide at minimum an English + Arabic locale (Sami's audience). Effort: ~4 hr.
- impact: Market expansion. Arabic-speaking homelab community is underserved.
DC-078: Backup and restore of DashCaddy's own configuration
- status: pending
- details: While DashCaddy can backup app data, there's no one-click "backup my entire DashCaddy setup" (services.json, config.json, health-config.json, credentials, Caddyfile, license) that could be restored on a fresh install. Fix: add
GET /api/v1/system/export(returns a signed JSON bundle) andPOST /api/v1/system/import(restores from bundle). The credentials file should be encrypted with a user-provided passphrase. Effort: ~2 hr. - impact: Migration story. "Moving DashCaddy to a new host" is currently a multi-hour manual process.
DC-079: Mobile-responsive dashboard improvements
- status: pending
- details: While the dashboard is somewhat responsive, it's not optimized for mobile use. For operators checking services on their phone, the experience should be touch-first. Fix: audit all dashboard pages on mobile viewport, fix any horizontal scroll, ensure buttons are touch-target sized (min 44px), add a mobile-specific layout for the service grid. Effort: ~3 hr.
- impact: Operators check services on their phone. Current mobile experience is usable but not polished.
DC-080: Plugin/extension system for custom services
- status: pending
- details: DashCaddy supports a fixed set of service templates. A plugin system would allow community-contributed service definitions (e.g., "Home Assistant", "Vaultwarden", "Nextcloud") without modifying core code. Fix: define a plugin manifest schema (name, logo, health check URL pattern, config fields), load plugins from
/data/plugins/, add a community plugin registry page. Effort: ~4 hr. - impact: Community growth. Extensibility is what makes a tool ecosystem vs. a product.
P2.5 — Security Hardening (Deep Audit Findings)
DC-081: 151 of 160 mutating routes have NO Joi input validation
- status: pending
- details: P1-1 added Joi validation to 8 routes, but a scan shows 151 out of 160 POST/PUT/PATCH/DELETE routes still accept raw
req.bodywithout schema validation. That's 94% of the mutation surface unvalidated. Routes likePOST /api/v1/services/:id,PUT /api/v1/config,POST /api/v1/tailscale/*,POST /api/v1/health/config/:idall accept arbitrary input. Fix: extendsrc/utilities/validate.jswith schemas for every mutating route, wire them in. This is the single highest-impact security improvement. Effort: ~4 hr (batch by route file). - impact: Input validation is the #1 defense against injection, abuse, and crashes. 94% gap is a P0 hiding as a P2.
DC-082: Command injection surface in ca.js — 5 execSync calls with interpolation
- status: pending
- details:
routes/ca.jshas 5execSync()calls with template-string interpolation: lines 164, 175, 180, 203, 263. P0-2 fixed the password injection (execFileSync), but the remaining calls interpolate file paths and subjects (${certFile},${keyFile},${subject},${configFile}). If any of these contain user input (e.g., a service name with;or backticks), it's command injection. Fix: convert ALLexecSync(\...`)calls toexecFileSync('openssl', [...args])with no shell interpolation. Also fixsrc/docker/self-updater.js:717(execSync(`tar xzf "${tarballPath}"...)) andsrc/utilities/backup-manager.js:8(imported execSync). Effort: ~2 hr. - impact: Any execSync with interpolation is a potential RCE. This is the same class of bug P0-2 already fixed — finish the job.
DC-083: 30 source files have zero test coverage
- status: pending
- details: The test gap scan found 30 source files with NO corresponding test file, including critical paths:
license-manager.js(534 lines, the entire revenue validation path),config-schema.js,middleware.js(the auth/rate-limit/CORS stack),startup-validator.js, all 7 DNS provider modules (technitium.js,cloudflare.js,rfc2136.js,manual.js,base.js,registry.js,email.js),docker-maintenance.js,config/migrations.js,event-workers.js,keychain-manager.js,event-store.js,host-registry.js. Fix: prioritize license-manager.js (revenue path) and middleware.js (security stack) first, then work through the rest. Effort: ~8 hr (can be done incrementally, 2-3 files per PR). - impact: license-manager.js validates Pro licenses — an untested bug there could silently break activation for every paying customer.
DC-084: No .dockerignore — test files and .git leak into Docker image
- status: pending
- details: There is no
.dockerignorefile. The Docker build context includes__tests__/(hundreds of test files), any.git/directory,coverage/,node_modules/from the host, and markdown files. This bloats the image (currently 249MB) and can leak sensitive test fixtures. Fix: create.dockerignorewith:__tests__/,.git/,node_modules/,coverage/,*.md,.eslintrc.js,jest.config.js,npm-debug.log*,.env*,openapi.yaml(only needed at build time if at all). Also add.dockerignoreto the git repo. Effort: ~15 min. - impact: Faster builds, smaller images, no test fixture leaks.
DC-085: Math.random() used for security-sensitive IDs
- status: pending
- details:
health-checker.js:352generates incident IDs withMath.random().toString(36).resource-monitor.js:143usesMath.random()for sampling.rfc2136.js:120generates temp filenames withMath.random(). While these aren't crypto-level secrets,Math.random()is not collision-resistant and is predictable. Fix: usecrypto.randomUUID()for incident IDs,crypto.randomBytes()for temp filenames, and a simple counter for sampling. Effort: ~30 min. - impact: Defense in depth. Predictable IDs can be exploited if they ever become user-facing.
P3.5 — Operational Maturity
DC-086: No structured error codes — errors are ad-hoc strings
- status: pending
- details: The HTTP status code audit shows only 6 distinct status codes used across routes (200, 201, 400, 401, 404, 429). Error responses are plain strings like
"Invalid input"or"Unauthorized". There is no error code system (likeINVALID_CONFIG,SERVICE_NOT_FOUND,LICENSE_EXPIRED). Fix: define a canonical error code enum insrc/utilities/errors.js, return{ error: { code: "SERVICE_NOT_FOUND", message: "..." } }in all error responses. This makes API integration programmable (consumers switch oncode, not parsemessage). Effort: ~3 hr. - impact: API consumers can handle errors programmatically. Required for SDK generation and good DX.
DC-087: No API client SDK / type definitions
- status: pending
- details: There is no TypeScript definitions file (
.d.ts) or client SDK. Anyone integrating against the API has to read the source code to understand request/response shapes. Fix: (1) Generate TypeScript types from the OpenAPI spec (once DC-062 updates it) usingopenapi-typescript. (2) Ship a@dashcaddy/api-typesnpm package or include atypes/index.d.tsin the repo. (3) Optionally, a thin JS client wrapper. Effort: ~2 hr (after DC-062). - impact: Developer adoption. A typed SDK lowers the barrier to integration.
DC-088: No log rotation — error.log grows forever
- status: pending
- details: The logger has basic rotation (rename to
.1when it hits a size limit), but only keeps ONE rotated file. In production, error.log can grow rapidly during incident bursts. There's no retention policy, no compression, no date-based rotation. Fix: (1) Add a max-size threshold (e.g., 10MB) and keep N rotated files (e.g., 5). (2) Compress rotated files with gzip. (3) Add date-based naming so logs are greppable by date. (4) Add aGET /api/v1/system/logsendpoint so operators can view recent logs without SSH. Effort: ~1.5 hr. - impact: Prevents disk fill during incident storms. Makes logs accessible without SSH access.
DC-089: No rate limit on public license activation endpoint
- status: pending
- details: The rate limiter
skiplist includesreq.path === '/api/v1/license/status'andreq.path.startsWith('/api/v1/license/feature/')— meaning license checks bypass rate limiting. While these are GET endpoints, the license activation endpoint (POST /api/v1/license/activate) should have its own dedicated rate limit to prevent brute-force license key guessing. Fix: add a dedicatedlicenseLimiterwith tighter limits (e.g., 10 attempts per 15 min per IP) on POST /license/activate. Effort: ~30 min. - impact: Prevents license key brute-forcing. Pro keys follow a predictable format (DC-XXX-XXXXX-XXXXXX) making them guessable without rate limiting.
DC-090: Node.js version drift — Dockerfile says 20, host runs 22
- status: pending
- details: Dockerfile uses
FROM node:20-alpine(floating). The development machine runs Node v22.22.3. The container uses whatevernode:20-alpineresolves to at build time. This version drift can cause "works on my machine" bugs (especially aroundfetch(),crypto, andstructuredClonewhich changed between 20 and 22). Fix: (1) Pin the exact version:FROM node:20.10.0-alpine3.19. (2) Add.nvmrcorenginesfield to package.json specifying the minimum version. (3) Optionally upgrade to Node 22 across the board. Effort: ~30 min. - impact: Reproducible builds. No surprise behavior from Node version drift.
DC-091: No dependency update automation (Dependabot/Renovate)
- status: pending
- details: Dependencies are updated manually. The 21 production dependencies and 4 dev dependencies can fall behind silently. There's no automated PR for security patches or major version bumps. Fix: add either GitHub Dependabot config (
.github/dependabot.yml) or Renovate config (renovate.json). Schedule weekly checks. Group minor/patch updates into one PR. Keep major updates separate for review. Effort: ~30 min. - impact: Security patches arrive automatically. No more manual
npm auditsessions.
DC-092: No health check for DashCaddy's own dependencies (disk space, memory)
- status: pending
- details: The Dockerfile has a HEALTHCHECK that hits
/health, but that endpoint only checks if the Express server responds. It doesn't check: disk space (if/app/datais on a full disk), memory pressure (Node heap near limit), Docker socket connectivity (if Docker daemon is down), Caddy admin API reachability. Fix: extend the health endpoint to include dependency checks:{ diskSpace: { free: ..., total: ... }, memory: { heapUsed: ..., heapTotal: ..., rss: ... }, docker: { reachable: true/false }, caddy: { reachable: true/false } }. Return 503 if any critical dependency is down. Effort: ~1.5 hr. - impact: Catch systemic issues before they become outages. External monitoring can alert on
503.
DC-093: Workflow engine has no retry/backoff for failed actions
- status: pending
- details: When the workflow engine's health-check action fails, it logs the failure and moves on — no retry. If a service is temporarily down and recovers in 30s, the workflow reports it as failed for the entire 15-min cycle. Fix: add configurable retry logic to workflow actions (e.g., retry 2 times with 30s backoff before reporting failure). Also add a
maxRetriesconfig to the health-check workflow. Effort: ~1.5 hr. - impact: Fewer false-positive alerts. More resilient monitoring.
DC-094: No audit trail for config changes (who changed what, when)
- status: pending
- details: The audit logger (
src/security/audit-logger.js) captures POST/PUT/DELETE events, but config changes (services.json, health-config.json, config.json) are made via file writes, not API calls. There's no record of who changed a service URL, disabled a health check, or modified a workflow. Fix: (1) Route all config mutations through API endpoints that log to the audit trail. (2) Add aGET /api/v1/system/audit-logendpoint for viewing the trail. (3) Include a diff of what changed in each audit entry. Effort: ~2 hr. - impact: Accountability. When something breaks, you can trace who changed the config and when.
P4 — Advanced Features
DC-095: No multi-user support — single-admin only
- status: pending
- details: DashCaddy has one admin user. For teams or homelab groups, there's no way to add a second admin or a read-only viewer. Fix: (1) Add a
users.jsonwith role-based access (admin, editor, viewer). (2) Add user management endpoints. (3) Add per-service permissions (editor can manage services but not billing). This is a significant feature, not a quick fix. Effort: ~6 hr. - impact: Multi-admin is a requirement for team/enterprise adoption.
DC-096: No API key management (create/revoke/scoped keys)
- status: pending
- details: API authentication uses session cookies or TOTP. There's no way to create scoped API keys for automation (e.g., a read-only key for monitoring, a key that can only manage one service). Fix: add
POST /api/v1/api-keys(create with scopes),GET /api/v1/api-keys(list),DELETE /api/v1/api-keys/:id(revoke). Store hashed in credentials.json. Effort: ~2 hr. - impact: Enables automation and third-party integrations without sharing the admin password.
DC-097: No Prometheus / Grafana metrics export
- status: pending
- details: There's a basic
/metricsendpoint, but it returns JSON, not Prometheus format. Fix: (1) Addprom-clientdependency. (2) Instrument key metrics: HTTP request duration histogram, active WebSocket connections, health check pass/fail counter, container count gauge, API error rate. (3) ExposeGET /metricsin Prometheus exposition format alongside the existing JSON endpoint. (4) Ship a Grafana dashboard JSON as a reference. Effort: ~2 hr. - impact: Industry-standard observability. Drop-in Grafana dashboard for operators.
DC-098: No changelog / release notes generation
- status: pending
- details: Releases are tracked via git commits and VERSION file, but there's no user-facing changelog. For a public product, customers need to know what changed between versions. Fix: (1) Add a
CHANGELOG.mdfollowing Keep a Changelog format. (2) Auto-generate from conventional commits (if adopted) or git log. (3) Display "What's new" on the dashboard after updates. Effort: ~1.5 hr. - impact: Customer trust. Users won't update without knowing what changed.
DC-099: No automated database migration system
- status: pending
- details: Config migrations exist (
src/config/migrations.js) but are ad-hoc. As the data schema evolves (new fields in services.json, config.json), there's no versioned migration system. Fix: (1) Add aschemaVersionfield to config files. (2) Create a migration runner that applies migrations sequentially on startup. (3) Log each migration. (4) Support rollback on failure. Effort: ~2 hr. - impact: Safe upgrades. No more manual config patching after updates.
DC-100: No service discovery / auto-detect running containers
- status: pending
- details: Services are added manually by specifying URLs. DashCaddy doesn't auto-detect running Docker containers and suggest adding them as services. Fix: (1) Scan
docker psfor containers with exposed ports. (2) Match against known app templates (Plex, Sonarr, etc.). (3) Show a "Detected services" panel with one-click add. (4) Periodically re-scan for new containers. Effort: ~3 hr. - impact: Zero-config onboarding. New users see their services auto-discovered.
Summary by Priority
| Priority | Count | Effort | Theme |
|---|---|---|---|
| P0 | 3 (DC-062–064) | ~7 hr | Public release blockers |
| P1 | 5 (DC-065–069) | ~7 hr | Reliability & code quality |
| P2 | 6 (DC-070–075) | ~5.5 hr | Polish & DX |
| P2.5 | 5 (DC-081–085) | ~15 hr | Security hardening (deep audit) |
| P3 | 5 (DC-076–080) | ~16 hr | Future growth |
| P3.5 | 9 (DC-086–094) | ~14.5 hr | Operational maturity |
| P4 | 6 (DC-095–100) | ~16.5 hr | Advanced features |
| Total | 39 | ~81.5 hr |