diff --git a/README.md b/README.md index a29de7b5..6b8708d3 100644 --- a/README.md +++ b/README.md @@ -368,7 +368,7 @@ flowchart LR | 📡 | **SSE Compatibility** | Drop-in replacement for existing Drydock agents, including `dd:watcher-snapshot` full inventory on connect. | | âœī¸ | **Signed Supply Chain** | Cosign keyless signatures, per-archive CycloneDX SBOMs, an image SBOM attestation, and SLSA Build L2 provenance on every release. Verifiable without managing signing keys. | | đŸ›Ąī¸ | **Two-Layer Defense** | Pair with [sockguard](https://github.com/codeswhat/sockguard) so the agent never touches the raw Docker socket directly. | -| đŸĒļ | **Minimal Footprint** | Static Go binary, ~47 MB Wolfi (Chainguard) container image. CGO disabled, stripped, no external runtime dependencies. | +| đŸĒļ | **Minimal Footprint** | Static Go binary (~10 MB). Compressed container image: ~45 MB amd64 and ~41 MB arm64 (Wolfi, Chainguard), ~33 MB arm/v7 (Alpine). CGO disabled, stripped, no external runtime dependencies. | | 🧩 | **Standalone Mode** | `ADAPTER=generic` provides a clean REST + SSE API on `/api/v1/*` backed by the local Docker daemon — no Drydock account required. |
@@ -403,7 +403,7 @@ flowchart LR Agent-level structured audit logâœ…âš ī¸âš ī¸âš ī¸âš ī¸ Prometheus scrape endpoint on the agent✅???? Read-only MCP server✅???? -Signed release evidence (cosign, SBOM, provenance)✅??âš ī¸âš ī¸ +Signed release evidence (cosign, SBOM, provenance)âœ…âš ī¸âš ī¸âš ī¸âš ī¸ Fleet UI and controller workflows❌✅✅✅? LicenseAGPL-3.0Zlib (agent) / proprietary Business featuresGPL-3.0BSD-3-ClauseMIT @@ -437,7 +437,7 @@ flowchart LR > Watchtower's upstream project is archived. Diun and Watchtower do update detection; Portwing is the access agent and leaves update decisions to Drydock. > -> The remote-agent table is compiled from the published [competitive landscape](https://portwing.codeswhat.com/docs/competitive-landscape), which lists its primary sources and records unknown competitor behavior as "not documented" rather than guessing it absent. Compared versions: Portainer 2.39.5, Komodo Periphery v2.3.2, Arcane Agent v2.10.1, Hawser v0.2.46. Reviewed 2026-08-29; Arcane re-checked 2026-09-02. The update-tools table follows the Diun and Watchtower comparison pages, which pin no version or review date. +> The remote-agent table is compiled from the published [competitive landscape](https://portwing.codeswhat.com/docs/competitive-landscape), which lists its primary sources and records unknown competitor behavior as "not documented" rather than guessing it absent. Compared versions: Portainer 2.39.5, Komodo Periphery v2.3.2, Arcane Agent v2.10.1, Hawser v0.2.46. Reviewed 2026-08-29; Arcane re-checked 2026-09-02. Portainer and Komodo release evidence checked 2026-10-08. The update-tools table follows the Diun and Watchtower comparison pages, which pin no version or review date. > Contributions welcome if any information is inaccurate. @@ -464,7 +464,7 @@ High-level themes only; see [ROADMAP.md](ROADMAP.md) for direction and non-goals | **v0.8.x** ✅ | Operations & Distribution | Mode-aware `/health` and `/ready`, cursor-based NDJSON audit export, runnable Compose and Kubernetes observability examples, continuous edge logs, Homebrew cask and signed `deb`/`rpm` packages, published stability policy, edge mode production supported | | **v0.9.x** ✅ | Controller-Owned Updates | Controller-owned Drydock watcher and update execution (Drydock `v1.6.0-rc.11+`), edge audit export, loopback default for the edge operations listener, MCP revision 2026-07-28, `ALLOWED_ORIGINS` and `ALLOWED_HOSTS` browser origin and Host checks (v0.9.23) | | **v1.0.0** | Binding Stability | `STABILITY.md` guarantees become binding semver commitments, final re-verify of the competitive review against primary sources, decision on a versioned docs archive. Gated on verifiable items, not a date | -| **Post-v1** | Demand-Driven | Controller-managed Portwing upgrade and rollback waves, optional client-certificate authentication, polling/intermittent edge transport, controller-assisted two-key rotation, BuildKit-aware Sockguard profile | +| **Post-v1** | Demand-Driven | Controller-managed Portwing upgrade and rollback waves, optional client-certificate authentication, polling/intermittent edge transport, controller-assisted two-key rotation | SLSA Build L3 isn't tied to a version. It follows an org-shared reusable release workflow landing. diff --git a/ROADMAP.md b/ROADMAP.md index 703f2944..16e8abd8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -104,9 +104,9 @@ calendar date: Post-v1 candidates are intentionally demand-driven: controller-managed Portwing upgrade/rollback waves, optional client-certificate authentication, -polling/intermittent edge transport, controller-assisted two-key rotation, and -a BuildKit-aware Sockguard profile. None should expand the base socket policy -or add an unaudited self-update path merely for feature-table parity. +polling/intermittent edge transport, and controller-assisted two-key rotation. +None should expand the base socket policy or add an unaudited +self-update path merely for feature-table parity. ## Non-goals diff --git a/docs/content/docs/competitive-landscape.mdx b/docs/content/docs/competitive-landscape.mdx index 4e876fcb..d6e6ea56 100644 --- a/docs/content/docs/competitive-landscape.mdx +++ b/docs/content/docs/competitive-landscape.mdx @@ -4,7 +4,7 @@ description: An evidence-backed comparison of Portwing with current remote Docke --- -This review is a snapshot from **August 29, 2026**, with Arcane re-checked on **September 2, 2026**. Competitor behavior changes independently of Portwing. This page is the canonical matrix and decision record, with the primary sources listed under Primary evidence. +This review is a snapshot from **August 29, 2026**, with Arcane re-checked on **September 2, 2026** and the Portainer and Komodo supply-chain evidence checked on **October 8, 2026**. Competitor behavior changes independently of Portwing. This page is the canonical matrix and decision record, with the primary sources listed under Primary evidence. Behavior a competitor's own docs or repo don't cover is recorded as "not documented", never guessed absent. Portwing is the host agent in a three-layer system: @@ -46,7 +46,7 @@ products rather than direct general-purpose agent peers. | Controller-managed agent upgrades | Not shipped | Portainer and Arcane provide fleet upgrade workflows. This belongs in Drydock plus Portwing packaging, not in an unaudited self-update endpoint. | | Builds behind the hardened socket profile | Denied by default, opt-in via preset | The base and compose presets deny builds; `portwing-with-build.yaml` allows classic `POST /build`, and `portwing-with-mediated-build.yaml` allows a stock BuildKit `docker compose build` by inspecting the `/session` and `/grpc` traffic in Sockguard. | | Host shell, file browser, and Swarm | Not supported | Intentional agent non-goals even though broader competitors provide them. | -| Verifiable release artifacts | Cosign signatures, CycloneDX SBOM, and SLSA Build L2 provenance | Arcane ships Cosign-verifiable artifacts and images. Portainer and Komodo were not evaluated for this. Hawser publishes checksummed releases with no equivalent set documented. | +| Verifiable release artifacts | Cosign signatures, CycloneDX SBOM, and SLSA Build L2 provenance | Arcane ships Cosign-verifiable artifacts and images. Portainer 2.39.5 publishes checksum files with its release archives and its images carry BuildKit SPDX SBOM and SLSA v0.2 provenance attestations, but no cosign signature was found. Komodo v2.3.2 publishes bare binaries, and its Periphery image carries a BuildKit SLSA v1 provenance attestation with no SBOM or cosign signature found. The cosign check looked only for the legacy `.sig` tag, and only the first platform's attestation was read. Hawser publishes checksummed releases with no equivalent set documented. | | Credential rotation | Multiple keys; file update plus SIGHUP; manual operational flow | Portainer has a revolving Edge password and Edge key lifecycle. Komodo automatically rotates Periphery keys. Arcane automatically renews certificates and can regenerate its environment token. Hawser requires manual token replacement. | ## What changed since the older comparison @@ -129,4 +129,8 @@ parity. Those capabilities belong in Drydock or a specialized service. - Arcane [remote environments](https://getarcane.app/docs/features/environments), [edge mTLS](https://getarcane.app/docs/security/edge-mtls), and [socket proxy](https://getarcane.app/docs/setup/socket-proxy) - Arcane ML-DSA-87 migration: [v2.10.0 release notes](https://github.com/getarcaneapp/arcane/releases/tag/v2.10.0), [PR #3785](https://github.com/getarcaneapp/arcane/pull/3785), and [v2.10.1 release notes](https://github.com/getarcaneapp/arcane/releases/tag/v2.10.1) - [Hawser repository](https://github.com/Finsys/hawser) and [edge-mode bind-address issue](https://github.com/Finsys/hawser/issues/71) +- Portainer [2.39.5 release assets](https://github.com/portainer/portainer/releases/tag/2.39.5) and the `portainer/agent:2.39.5` image manifest and attestations, checked October 8, 2026 +- Komodo [v2.3.2 release assets](https://github.com/moghtech/komodo/releases/tag/v2.3.2) and the `ghcr.io/moghtech/komodo-periphery:2.3.2` image manifest and attestations, checked October 8, 2026 - Komodo outbound-leg issues: [proxy env vars ignored](https://github.com/moghtech/komodo/issues/1473) and [hardcoded handshake timeout](https://github.com/moghtech/komodo/issues/1518) +- Watchtower [metrics](https://containrrr.dev/watchtower/metrics/) and [arguments](https://containrrr.dev/watchtower/arguments/) (opt-in, experimental `/v1/metrics`), checked October 8, 2026; `containrrr/watchtower:1.7.1` linux/amd64 image manifest (`sha256:f9086bfda061â€Ļ`, 5.63 MB of compressed layers), measured October 8, 2026 +- Diun [metrics configuration](https://crazymax.dev/diun/config/metrics/) (opt-in metrics server, `:9090/metrics`), checked October 8, 2026 diff --git a/docs/content/docs/migrating-from-watchtower.mdx b/docs/content/docs/migrating-from-watchtower.mdx index ccdee13d..65d73b83 100644 --- a/docs/content/docs/migrating-from-watchtower.mdx +++ b/docs/content/docs/migrating-from-watchtower.mdx @@ -29,7 +29,7 @@ Drydock + Portwing invert the model. Portwing runs on each host and exposes the | Container exec / terminal | No | Yes (WebSocket) | | Container filtering | Label `com.centurylinklabs.watchtower.enable` | Label `dd.watch=true` | | Self-hosted server required | No | Yes (Drydock) | -| Image size | ~50 MB | ~10 MB (Portwing) + Drydock server | +| Image size (compressed) | ~6 MB (v1.7.1, linux/amd64) | ~45 MB (Portwing, ~10 MB binary) + Drydock server | Portwing does **not** automatically pull and recreate containers on a schedule. That decision is made by the Drydock controller through its UI and notification pipeline — not by the agent. If you relied on Watchtower's fully autonomous update loop, you will need to configure update policies in Drydock. @@ -211,7 +211,7 @@ For production deployments, upgrade to Ed25519 per-request signing after the ini | Compose support | Recreate only | Full lifecycle (`up`/`down`/`pull`/`ps`/`logs`) | | Exec / terminal | No | Yes (WebSocket) | | Self-hosted required | No | Yes (Drydock server) | -| Resource footprint | ~50 MB image | ~10 MB Portwing + Drydock server | +| Resource footprint | ~6 MB compressed image | ~45 MB compressed Portwing image (~10 MB binary) + Drydock server | See [Configuration](/configuration) for the full environment-variable reference and [Security Model](/security-model) for Portwing's default-deny socket filtering and rate-limiting details. diff --git a/docs/migrating-from-watchtower.md b/docs/migrating-from-watchtower.md index 51a894b8..830e3b2a 100644 --- a/docs/migrating-from-watchtower.md +++ b/docs/migrating-from-watchtower.md @@ -197,7 +197,7 @@ checked for updates by Drydock. | Compose support | Recreate only | Full lifecycle (`up`/`down`/`pull`/`ps`/`logs`) | | Exec / terminal | No | Yes (WebSocket) | | Self-hosted required | No (just Docker) | Yes (Drydock server) | -| Resource footprint | ~50 MB image | ~10 MB Portwing image + Drydock server | +| Resource footprint | ~6 MB compressed image | ~45 MB compressed Portwing image (~10 MB binary) + Drydock server | --- diff --git a/website/src/components/compare-matrix.tsx b/website/src/components/compare-matrix.tsx index 6441b74d..aea91eea 100644 --- a/website/src/components/compare-matrix.tsx +++ b/website/src/components/compare-matrix.tsx @@ -1,13 +1,13 @@ -import { ArrowUpRight, Check, Minus, X } from "lucide-react"; +import { ArrowUpRight, Check, CircleQuestionMark, Minus, X } from "lucide-react"; import Link from "next/link"; -import { ComparisonCellIcon } from "@/components/comparison-cell-icon"; +import { type CellValue, ComparisonCellIcon } from "@/components/comparison-cell-icon"; import { SITE_CONFIG } from "@/lib/site-config"; // Full comparison matrix for /compare. Each competitor row links to its // dedicated deep-dive page. Cell values are derived from comparison-rows.ts // and the per-competitor data in lib/comparison-route-data/. -type Cell = "yes" | "partial" | "no"; +type Cell = CellValue; const FEATURES = [ { key: "remoteControl", label: "Remote control" }, @@ -55,8 +55,8 @@ const TOOLS: Tool[] = [ leastPrivilege: "no", agentAudit: "partial", transparentApi: "yes", - metrics: "no", - mcpServer: "no", + metrics: "undocumented", + mcpServer: "undocumented", }, }, { @@ -70,7 +70,7 @@ const TOOLS: Tool[] = [ agentAudit: "partial", transparentApi: "no", metrics: "partial", - mcpServer: "no", + mcpServer: "undocumented", }, }, { @@ -84,7 +84,7 @@ const TOOLS: Tool[] = [ agentAudit: "partial", transparentApi: "no", metrics: "partial", - mcpServer: "no", + mcpServer: "undocumented", }, }, { @@ -98,7 +98,7 @@ const TOOLS: Tool[] = [ agentAudit: "partial", transparentApi: "yes", metrics: "partial", - mcpServer: "no", + mcpServer: "undocumented", }, }, { @@ -111,8 +111,8 @@ const TOOLS: Tool[] = [ leastPrivilege: "no", agentAudit: "no", transparentApi: "no", - metrics: "no", - mcpServer: "no", + metrics: "yes", + mcpServer: "undocumented", }, }, { @@ -126,7 +126,7 @@ const TOOLS: Tool[] = [ agentAudit: "no", transparentApi: "no", metrics: "yes", - mcpServer: "no", + mcpServer: "undocumented", }, }, ]; @@ -196,6 +196,10 @@ export function CompareMatrix() { No + + Not + documented + ¡ Click a tool for the full breakdown diff --git a/website/src/components/compare-section.tsx b/website/src/components/compare-section.tsx index ce177cc6..c3e0f0f8 100644 --- a/website/src/components/compare-section.tsx +++ b/website/src/components/compare-section.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, Check, Minus, X } from "lucide-react"; +import { ArrowRight, Check, CircleQuestionMark, Minus, X } from "lucide-react"; import Link from "next/link"; import { type CellValue, ComparisonCellIcon } from "@/components/comparison-cell-icon"; import { SectionHeading } from "@/components/section-heading"; @@ -59,10 +59,10 @@ const featureRows: FeatureRow[] = [ { label: "Read-only MCP server", portwing: "yes", - portainer: "no", - komodo: "no", - arcane: "no", - hawser: "no", + portainer: "undocumented", + komodo: "undocumented", + arcane: "undocumented", + hawser: "undocumented", }, ]; @@ -162,9 +162,13 @@ export function CompareSection() { Partial - + No + + Not + documented + diff --git a/website/src/components/comparison-cell-icon.tsx b/website/src/components/comparison-cell-icon.tsx index 5375f144..e62fd26f 100644 --- a/website/src/components/comparison-cell-icon.tsx +++ b/website/src/components/comparison-cell-icon.tsx @@ -1,6 +1,6 @@ -import { Check, Minus, X } from "lucide-react"; +import { Check, CircleQuestionMark, Minus, X } from "lucide-react"; -export type CellValue = "yes" | "partial" | "no"; +export type CellValue = "yes" | "partial" | "no" | "undocumented"; export function ComparisonCellIcon({ value }: { value: CellValue }) { if (value === "yes") { @@ -19,6 +19,17 @@ export function ComparisonCellIcon({ value }: { value: CellValue }) { ); } + if (value === "undocumented") { + return ( + <> +