Changelog
Version v2.0.0
What’s new
BridgePort 2.0 is a major release built around a redesigned service model, six new operator features, and a large performance wave.
- Service is now a template — one
Service(image, base compose, base env, deploy strategy) fans out to per-serverServiceDeployments, each with its own env/file overrides, deployed sequentially or in parallel. - One-click server bootstrap — install Docker + Compose, deploy the agent, tune sysctl, add swap.
- Reusable config fragments, atomic batched syncs with rollback, and dry-run previews.
- Self-describing HTTP API — OpenAPI spec, Swagger UI, and a standardized error envelope.
- Richer topology diagram — external entities, server clustering, resizable boxes, stable anchors.
- Performance — worst production transactions cut 5–12x; agent metrics-ingest throughput up ~5x.
Schema changes apply automatically on container start — existing 1:1 Service→Server records are backfilled into ServiceDeployments with no data loss and no manual steps. Programmatic API and CLI users should read Action required below before upgrading.
Performance
A sustained optimization wave, anchored by an 18-scenario stress suite (target: RPS ≥ 500, p99 ≤ 50 ms per endpoint) and validated against the worst production transactions in Sentry.
Hotpath before → after
| Path | Before | After | PR |
|---|---|---|---|
| Agent metrics ingest (25 conns) | 115 RPS · p50 208 ms · p99 635 ms | 639 RPS · p50 38 ms · p99 47 ms | #183 |
metrics-summary (worst prod txn) | p99 8.2 s in production | p99 17–46 ms | #138, #181 |
health-status (was N+1) | p99 224 ms · 184 RPS | p99 42 ms · 789 RPS | #138, #147 |
server-metrics-history | p99 395 ms · 1012 RPS | p99 12 ms · 4200 RPS (+315%) | #163, #181 |
service-metrics-history | p99 57 ms · 622 RPS | p99 12 ms · 3585 RPS (+476%) | #181 |
container-images-list | p50/p99 56/81 ms · 529 RPS | p50/p99 11/21 ms · 669 RPS | #163 |
| Dashboard first paint | ~6 s | <500 ms | #174 |
What changed
- Metrics ingest batched into one transaction (#183) — each agent push was a serial chain of ~10–26 individual SQLite writes, each grabbing the single writer lock. Now:
- one
prisma.$transactionper push,createManyfor service metrics, merged per-deployment updates, O(1) container→deployment map, throttled heartbeat.
- one
- Single-flight metrics cache (#181) — bursts of identical dashboard polls collapse to one compute:
server-metrics-history+315% RPS,service-metrics-history+476%,metrics-summary+423%.- cache sits behind existing auth/scope enforcement and is keyed by environment.
- Monitoring pages — per-card loading (#178) — dropped the top-level render-gate; header/sidebar/lists paint immediately while each chart owns its own state. History endpoints gained
?maxPoints(LTTB downsample) and?since=delta mode. - N+1 and hydration fixes (#138, #147, #145, #146, #148, #149, #150):
- rewrote
getEnvironmentMetricsSummary(Sentry’s worst transaction, p99 8.2 s in prod). health-statusreads denormalized cache columns instead of scanningHealthCheckLog.- moved
sendSystemNotificationfan-out to an in-process queue so request handlers no longer block on per-user writes. - added
SecretUsage/VarUsagejoin tables, columnar metrics-history responses, compound indexes.
- rewrote
- Dashboard (#174) — ungated per-section rendering; replaced the per-DB backup-info fan-out with a single batched
databases/backup-summaryendpoint.
Action required before upgrading
Skip any item whose feature you don’t use through the API or CLI. The web UI handles all of these transparently.
1. Service is now a template with per-server deployments (#137)
- Runtime fields (
containerName,status, health/check results, discovery) and per-serverenvOverridesmoved to a newServiceDeploymententity. - Image-update fields (
autoUpdate,latestAvailableTag,lastUpdateCheckAt) moved toContainerImage. - Existing data auto-migrates — each legacy 1:1 Service→Server record becomes one
ServiceDeployment. No manual steps. - CLI users: update your binary — the
Servicestruct changed shape; older builds will misread responses. - API clients reading
service.server: a flattenedservicesarray and aservice.serveraccessor are kept for back-compat this release, but new code should readservice.serviceDeployments[].
2. PATCH of read-only fields is now rejected (#159)
- A PATCH whose body contains a derived/read-only field (
status,exposedPorts,lastCheckedAt, …) fails atomically with422+code: READONLY_FIELD, naming the field. - Affects services, servers, config-files, container-images, databases, registries, secrets.
- Fix: send only the fields you intend to change — don’t echo back computed fields.
3. Standardized error envelope (#155)
- Every non-2xx response now returns:
{ "code": "VALIDATION_ERROR", "message": "...", "field": "...", "hint": "...", "requestId": "..." }codeis a documented enum (VALIDATION_ERROR,READONLY_FIELD,UNAUTHORIZED,FORBIDDEN_SCOPE,NOT_FOUND,CONFLICT,IDEMPOTENCY_KEY_REUSED,RATE_LIMITED,INTERNAL).- 5xx messages are redacted from clients but still logged + sent to Sentry.
- Clients that parse
errormust readmessage/codeinstead.
4. Sync responses return an envelope (#159)
/config-files/:id/sync-all,/services/:id/sync-files,/servers/:serverId/sync-all-filesnow return{ status, targetsAttempted, targetsSucceeded, targetsFailed, results }.statusis'ok' | 'no_targets' | 'partial' | 'failed'; zero-target syncs now return200 + 'no_targets'(was400).- The old
success: booleanfield is a deprecated alias for this release only — migrate off it.
Features
Service templates + per-server deployments (#137)
- New
ServiceDeploymententity owns per-server runtime, status, agent check results, andenvOverrides. - Deploy strategy chosen at deploy time:
- sequential — halts on first failure with rollback context.
- parallel —
Promise.allSettled, aggregated statuses.
ServiceDetailgains Overview / Deployments / Config tabs; per-deployment env overrides editable inline.
Server bootstrap (#156)
- One Bootstrap action installs Docker + Compose plugin, deploys the agent, applies container-tuned sysctl defaults, and optionally creates a swap file.
- Ubuntu/Debian only; idempotent (skip-if-present, no version upgrades); requires passwordless sudo / root SSH (preflighted).
- Live progress streams over SSE into a
BootstrapModal; separate Add swap recovery action logs before/afterfree -m. fstabbacked up once before any change; idempotent exact-line append (neversed -i).
Reusable config fragments (#162)
- Env-scoped, named text blocks that multiple config files include via an ordered list.
- Rendered as
concat(fragment1, …, file.content)with placeholder substitution over the result (dotenv / composeenv_filelast-wins semantics). - Purely additive — files with no fragments attached render byte-for-byte unchanged.
- Editing a fragment auto-resyncs every file that includes it; deleting an in-use fragment returns
409listing dependents. - New Fragments CRUD page, ordered selector with reorder + preview pane, and
POST /api/config-files/:id/preview.
Atomic multi-resource sync (#161)
POST /api/sync/batchruns config-file syncs as an all-or-nothing batch with optional rollback.Idempotency-Keyreplays an identical body and returns409on body conflicts.- Per-op snapshots enable reverse-order restore + re-sync;
GET /api/sync/batch/:batchIdinspects past batches; audit rows link the batch ID. - v1 scope: config-file syncs only, single environment per batch (service-deploy deferred).
Dry-run previews (#160)
- Preview a mutating call without performing it via
?dryRun=trueorX-Dry-Run: true. - Supported on deploy, deployment-plan execute, config-file sync-all, and per-service sync-files.
- Stops before any host write / container cycle / history row; image digests resolved from the registry (no
docker pull); secret values redacted to***(references preserved). - Sync dry-runs return a per-target unified diff vs the current host file.
Topology diagram upgrades (#154)
- External entities — model off-platform sources/sinks as pill nodes with handles on all four sides; included in Mermaid export.
- Resizable server boxes and server clustering (collapsible dashed containers that aggregate child edges).
- Stable connection anchors —
left/right/top/bottomhandle IDs preserved verbatim on connect.
Self-describing HTTP API (#155)
- OpenAPI 3 spec at
GET /openapi.jsonand Swagger UI at/api/docs(generated from registered routes). GET /api/auth/menow returns top-levelrole,environments[], andscopes[]so clients can check authorization before making a call.- (Error-envelope change is under Action required.)
No-silent-success guarantees (#159)
- Read-only-field PATCHes are rejected; zero-target syncs are surfaced explicitly rather than masquerading as success.
- Client-visible details under Action required.
Server iteration in config templates (#134)
- Go-style
{{range servers ...}}{{end}}withtag/name(glob) /environmentfilters. - Per-server fields in the body:
.name,.hostname,.publicIp,.privateIp,.id,.tags. - Two-stage render (iteration first, then
${KEY}), alphabetical by name for deterministic checksums; no new dependency.
Auto-resync config files on value change (#133)
PATCH /api/secrets/:idandPATCH /api/vars/:idre-render every config file in that env that references${KEY}.- Fires only when the value actually changes; runs fire-and-forget, never blocks the PATCH.
- New
ConfigFile.autoResyncflag lets operators pin a file’s content; triggered syncs are tagged in the audit log.
Free-form service type tagging (#153)
- Group services by workload type (django, postgres, redis, …) via a free-form
typeTag. - Configure-modal input with autocomplete from existing tags; filter chip row on the Services list persisted via
?type=.
2.0 quality-of-life bundle (#136)
- Restart bug fix — services with a compose path now run
compose down+compose up --force-recreateso config changes are picked up (falls back todocker restartonly when no compose path is set). - Per-env Slack channel override — env Settings → Notifications tab; explicit routing rules still win.
- Config file syntax highlighting — CodeMirror editor, language auto-detected from filename.
- Config-file “Attached to services” entries now link to the service detail page.
Docker logs visibility (#135)
- Per-container
docker logscaptured during each deploy step and shown in the deployment plan view (no SSH). - Service logs modal gains a Load older button with scroll preservation;
defaultLogLinesnow drives both capture and the viewer.
Fixes
- Service→ServiceDeployment split fallout (#163, #152) — fixed crashes from the stale
service.servershape on the Config Files page (#152), the Registries linked-services list, and the Add Dependency picker (all now readserviceDeployments[]). - Compose host port dropped (#132) — auto-managed compose no longer drops the host port when
exposedPortshas no explicit host mapping. - Fragments sidebar icon (#173) — the Fragments page got its own icon instead of sharing one.
Security
- CodeQL #34 (
js/clear-text-logging, high) (#182) —getRegistryCredentials()wraps decryption in try/catch and re-throws a sanitized, secret-free error, severing the path by which a decryption failure could log encrypted material / nonce. The scheduler now skips a single unreadable credential instead of aborting the whole update run. - Supply-chain hardening (#176):
- root +
ui/.npmrcsetmin-release-age=1andignore-scripts=trueto match the Dockerfile. - Dependabot publish cooldown per ecosystem (npm 5-day, others 3-day).
- cleared the
autocannon → hyperid → uuid@8.3.2advisory chain via auuid@^11override (npm audit: 0 vulns).
- root +
- Heredoc →
writeFile(#163) — config-file sync paths no longer write rendered content through a shell heredoc, so content can’t terminate it early or be reparsed.
Under the hood
- Stress-test framework + CI (#138) — 18-scenario suite, soft-mode GitHub Actions workflow, sticky PR comments, thresholds checked in.
/buildskill (#131) — drives a GitHub issue end-to-end to a review-clean PR.- Dependency bumps —
@fastify/cors→ 11,pino-pretty→ 13 (#177); TypeScript → ^6.0.3 across both workspaces (#175, #167); grouped Dependabot bumps: #120, #121, #122, #165.
Documentation
- Rewritten
services.mdfor the template/deployment split. - New
server-bootstrap.mdandsupply-chain.mdguides. - Expanded
config-files.md(fragments, server iteration, syntax highlighting). - Refreshed architecture, API, monitoring, and environment-settings references.