Remote Docker
Agent
We built Portwing to give Drydock a secure foothold on every host. Pair it with sockguard and it never touches the raw Docker socket. Ed25519 auth proves every request, and a structured audit log records everything it touches for export to immutable storage, so you control your fleet without exposing it.
In the real CLI
See it work
A looping recreation of the real CLI — boot the agent in standard and edge mode and watch it stream structured audit logs.
A hand-rendered recreation of the real CLI — every frame mirrors what portwing actually prints. Use the controls above to pause, restart, or change speed.
What it does
14 reasons your Docker hosts are safer
Security isn't a checkbox — it's every layer. Here's what we built.
Default-Deny Socket
Pair us with sockguard and we never touch the raw Docker socket. The agent talks to a filtered unix socket instead, so even a fully compromised agent is constrained to an explicit Docker API allowlist enforced at the socket level.
Ed25519 Per-Client Auth
Signed requests with nonce-based replay protection and no shared secrets on the wire. Enroll client public keys into an authorized_keys file, rotate with SIGHUP, and every enrollment is audited.
Argon2id Token Hashing
Prefer a shared secret? Store only its Argon2id hash via TOKEN_HASH. Generate one with `portwing hash-token`; comparison is constant-time. Plaintext TOKEN is supported for evaluation but the hash is the recommended posture.
Signed & Verifiable
Every release ships cosign signatures, per-archive SBOMs, an image SBOM attestation, and SLSA build provenance. Verify the image before you ever trust it with your host — the supply chain is auditable end to end.
Hardened Runtime
We ship hardened by default: `read_only`, `cap_drop: ALL`, `no-new-privileges`, and tokens mounted as secrets instead of env vars. The image runs as non-root (UID 65532) out of the box; running as root (`user: "0:0"`) is an explicit opt-in for socket-group compatibility. No security theater.
Drydock-Native
We speak Drydock's wire protocol (portwing/1.0 over WebSocket in edge mode; dd:* SSE events in standard mode) with full watcher-snapshot streaming, letting one Drydock controller manage containers across every host we're deployed on.
Standard & Edge Modes
Run in standard mode behind a TLS reverse proxy (the controller dials in), or use production-supported outbound edge mode for hosts with no inbound port. The stable portwing/1.0 path is gated by real multi-agent reconnect, exec, log-stream, and backpressure soak tests.
Generic REST Adapter
Don't run Drydock? We work standalone too. Our REST + Bearer-auth surface lets any client list, inspect, start, stop, and stream logs from containers. No controller required.
Read-Only MCP Server
We ship a built-in Model Context Protocol server that exposes host and container state to MCP-aware agents. Read-only by design, so your assistant can see everything and change nothing.
Prometheus Metrics
Our zero-dependency `/metrics` endpoint exports host/container telemetry plus edge connection, reconnect, backpressure, and audit health. Explicit `/health` and `/ready` probes keep dependency outages from becoming restart loops.
Structured Audit Log
Every action is recorded with a stable structured schema: who asked, what ran, and what the daemon answered. A cursor-based NDJSON path detects overwritten history and restarts before exporters ship records to immutable storage.
Tiny & Multi-Arch
We ship as a single static Go binary, ~10 MB, with zero runtime dependencies. amd64, arm64, arm/v7 — runs on the Raspberry Pi at the edge as happily as the server in the rack.
OpenAPI 3.1 Spec
We publish the full agent API as a machine-readable OpenAPI 3.1 spec. Generate clients, drive contract tests, and never guess at the wire format.
Watchtower Migration
Coming from Watchtower? We wrote a migration guide that maps the update-on-a-host workflow onto Portwing + Drydock, so you trade a cron-like updater for an auditable, controllable agent.
Get running
Get started in minutes
Add Portwing to your compose file and point Drydock at it — one agent per host.
$ docker run -d \
--name portwing \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
-e TOKEN=$(openssl rand -hex 24) \
ghcr.io/codeswhat/portwing:latestTOKEN is visible in docker inspect — fine for a local try, not for production.
On the horizon
Roadmap
Where we've been and where we're headed. Direction, not commitments.
│Stable HTTP API and environment-variable surface under semantic-versioning guarantees
│Stable agent ↔ controller wire protocol — no breaking changes without a major bump
│+1 moreshow less
│Threat model review, CVE-clean base image, published security policy at security@codeswhat.com
│Drydock watcher/update execution runs controller-side through Portwing's authenticated Docker proxy in Standard and Edge modes
│Watcher descriptor declares transport=docker-api, execution=controller, and events=portwing; no remote trigger is advertised
│+3 moreshow less
│Edge sends component ownership before raw inventory; full feature compatibility requires Drydock v1.6.0-rc.11+ while DrydockCompat remains 1.4.0
│Edge mode shares the cursor-based NDJSON audit exporter on its explicitly private, unauthenticated operations listener
│Exec and Docker proxy failures preserve bounded daemon/sockguard error details through the controller path
│Edge mode graduated to production support — enabled by default in Drydock 1.6 and gated by a real cross-repo multi-agent load/soak
│Published stability policy — semver guarantees for the HTTP API, env-var surface, MCP tools, and DrydockCompat wire contract
│+3 moreshow less
│Package-manager distribution — a Homebrew tap and apt/rpm packages via the existing GoReleaser pipeline
│Continuous edge live-tail under the dd: namespace, with cancellation, stdout/stderr framing, and bounded backpressure
│Mode-aware liveness/readiness, edge/backpressure metrics, stable cursor-based NDJSON audit export, and runnable observability examples
│Standard mode fails closed without credentials — unauthenticated development requires explicit loopback and remote opt-ins
│Ed25519 HTTP signature v2 binds the escaped path and exact raw query; legacy signatures are query-free only
│+5 moreshow less
│Cold Argon2id work is capped agent-wide and per IP; raw-token verifiers retain fixed-size digests
│Credential files require regular-file descriptors and safe Unix permissions, checked after open
│Enrollment bodies are capped and abuse-limited; Compose uploads use root-confined, symlink-resistant writes
│Edge request correlation, bounded log-follow options, and terminal hello-rejection classification
│Generated CSP and browser security headers cover every exported website and docs route
│Agent info surfaced in the Drydock UI — memoryGb, logLevel, and pollInterval reported on every dd:ack
│Edge-mode container deletion — dd:container_delete_request/response over the WebSocket tunnel
│+4 moreshow less
│Container environment variables in RuntimeDetails, parsed from Docker's Config.Env
│COMPATIBILITY.md — canonical cross-repo version matrix for Portwing × Drydock × sockguard
│BREAKING: container image now runs as non-root (UID 65532) by default — root is an explicit opt-in
│CI egress lockdown — harden-runner egress-policy: block across all jobs in every workflow
│Pre-auth body limits tightened — oversized requests blocked before auth runs
│Registry-auth and proxy query parameters validated before reaching the daemon
│+5 moreshow less
│Private-key file-permission parity — startup fails on world-readable keys
│Consistent outbound TLS posture across all HTTP client paths
│Goroutine-lifecycle cleanup on shutdown — no leaks under SIGTERM
│Benchmark tracking: hot-path ns/op + allocs/op kept as a 90-day artifact
│Cosign-signed images, SBOMs, and build provenance on every release
│Edge mode: outbound WebSocket tunnel to Drydock — no inbound port required
│Ed25519 per-request signatures with nonce LRU replay protection
│+5 moreshow less
│Ordered exec I/O — input that arrives before the exec is up is buffered and replayed
│Outbound backpressure: bounded write queue, per-frame deadline, slow-consumer eviction
│Reproducible base images: Dockerfile and Dockerfile.release pinned by digest
│Three-tier fuzz harness: 60s PR smoke, 5m nightly, 1h monthly deep pass
│Weekly soak test: resident-set budget under sustained SSE+proxy+exec load
│Standard mode: HTTP/SSE inbound server, Docker Engine API proxy
│Token authentication — plaintext, file-based, and Argon2id hash-at-rest
│+5 moreshow less
│Docker Compose stack lifecycle (up / down / pull / ps)
│Exec tunnel: interactive container sessions streamed over HTTP
│Structured JSON audit log with in-memory ring buffer and pull endpoint
│Health endpoint (/_portwing/health) for Docker HEALTHCHECK directives
│Multi-arch image: linux/amd64 + linux/arm64, read_only + cap_drop:ALL runtime defaults
Why Portwing
How we compare
A quick look at what Portwing ships that Portainer Agent, Hawser, and Komodo Periphery don't.
| Feature | Portwing | Portainer | Hawser | Komodo |
|---|---|---|---|---|
| Remote container control | Yes | Yes | Yes | Yes |
| Per-client key auth (Ed25519) | Yes | Partial | Partial | Partial |
| Default-deny socket filter | Yes | No | No | No |
| Structured audit log | Yes | Partial | No | No |
| Signed images + SBOM | Yes | No | No | No |
| MCP server (AI-native) | Yes | No | No | No |
Ecosystem
Part of the CodesWhat stack
Portwing is one piece of a small, focused toolkit — each tool does one job, and they compose.

Container update manager — orchestrates across your hosts

Security-first remote Docker agent

Default-deny Docker socket firewall
FAQ
Frequently asked questions
Common questions about how Portwing works and how to deploy it securely.
What is Portwing and how does it relate to Drydock?
Portwing is a lightweight Go agent that gives Drydock a secure foothold on a Docker host. Drydock is the orchestrator — it manages container update policies, schedules checks, and sends update actions. Portwing is the agent it talks to on each host: it sits next to the Docker socket, proxies the Docker Engine API to Drydock, and handles container lifecycle actions on request. Without an agent on each host, Drydock can only reach hosts it has a direct network path to. Portwing adds two connection options: standard mode (Drydock connects inbound to the agent's HTTP port) and edge mode (the agent dials out to Drydock over a WebSocket, so NAT and firewalled hosts work too).
What are standard mode and edge mode, and which should I use?
Standard mode runs an HTTP/SSE server on port 3000. The Drydock controller connects inbound, calls the agent's inventory endpoints, and holds a long-lived SSE stream for real-time events. Use it when the host is reachable from your Drydock instance. Edge mode flips the control direction: the agent dials out to Drydock over the stable portwing/1.0 WebSocket tunnel, so no inbound control port is published. Its unauthenticated local operations listener must remain private. Use Drydock v1.6.0-rc.11+ with Portwing v0.9.0 for the complete controller-owned watcher/update feature path.
How does Ed25519 authentication work and why use it over a shared token?
Ed25519 gives each client a distinct cryptographic identity. Every version 2 request carries a detached signature over the HTTP method, escaped path plus exact raw query string, a SHA-256 body hash, a Unix timestamp, and a 128-bit random nonce. The agent verifies the signature, rejects requests with a timestamp skew greater than 60 seconds, and tracks nonces in an in-memory LRU to block replays within the window. No shared secret is transmitted. Compromising one client's private key leaves all other enrolled clients unaffected. By contrast, a shared token (TOKEN or TOKEN_HASH) is a single credential — rotate or revoke it and every caller is disrupted at once. Ed25519 is the recommended production option; token auth is still supported for evaluation and staged migration. Portwing ships a keygen subcommand: run portwing keygen to generate a keypair and get the authorized_keys line to drop on the agent host.
Is it safe to expose Docker control through Portwing? What's the security model?
Portwing is not a firewall — it is an authenticated transport. Out of the box it authenticates callers and proxies Docker API requests to the daemon. If you want a socket-level allowlist that blocks individual Docker API paths and request body properties (privileged mode, host mounts, capability additions), pair Portwing with sockguard: sockguard writes a filtered unix socket into a shared volume, and Portwing talks to that filtered socket instead of the raw /var/run/docker.sock. The recommended compose file ships with this two-layer configuration pre-wired. All official images run with read_only: true, cap_drop: ALL, no-new-privileges, and Docker secrets instead of inline tokens. Edge mode publishes no inbound control port, but its local operations listener exposes metrics and audit export without inbound auth and must stay on a trusted network.
What does the audit log record and how do I enable it?
Set AUDIT_LOG to a file path, stdout, or stderr. The agent writes one JSON record per line for every security-relevant event: authenticated API calls (api_request), failed auth attempts (auth_failure), rate-limit blocks (rate_limited), Compose lifecycle operations (compose_op), exec tunnel opens (exec_start), and Ed25519 key enrollments (enrollment). Every record includes a UTC nanosecond timestamp, the client IP, HTTP method and path, and an outcome (allowed / denied / error). Portwing also keeps an in-memory ring buffer (default 256) with an authenticated Standard-mode JSON view and cursor-based NDJSON export. Edge mode exposes only the NDJSON export on its private operations listener without inbound auth, so restrict that listener to the trusted exporter or monitoring workload.
What platforms does Portwing run on, and how do I install it?
Portwing ships as a multi-arch container image for linux/amd64, linux/arm64, and linux/arm/v7; as signed deb and rpm packages for those architectures; and as a Homebrew cask for Intel and Apple Silicon Macs. Docker Compose with sockguard remains the recommended hardened deployment, while native packages are convenient for host-managed services and local CLI use. Stable releases update codeswhat/tap/portwing; prereleases do not move that channel. See https://portwing.codeswhat.com/docs/installation for verification, configuration, upgrade, and uninstall instructions.
Is Portwing production-ready?
Portwing v0.9.x is a supported pre-v1 release, so operators should pin versions and review the changelog and published stability policy. Both standard and edge modes are production supported: edge is gated by a real multi-agent reconnect/load soak with concurrent exec, continuous logs, and controller backpressure. Use Drydock v1.6.0-rc.11+ for full v0.9 watcher/update feature compatibility; the stable wire contract remains DrydockCompat 1.4.0. The public roadmap still targets v1.0 for the first fully stable release. For the strongest deployment posture, pair either mode with sockguard. Report issues at https://github.com/CodesWhat/portwing/issues and security concerns at security@codeswhat.com.
Do I need sockguard? What does adding it actually change?
Sockguard is optional but recommended for production. Without it, Portwing proxies any Docker API request that passes authentication — which includes container creates with privileged mode, arbitrary bind mounts, or capability additions if a caller requests them. Sockguard sits between Portwing and the Docker socket and applies a default-deny allowlist: every Docker API call is blocked unless an explicit rule in sockguard.yaml permits it (method, path, and request body). The bundled sockguard.yaml mirrors the Drydock preset, which covers container lifecycle, image operations, events, and narrow network and volume reads. Even if Portwing were fully compromised, sockguard constrains it to that explicit allowlist. The two-layer docker-compose example in the repo pre-wires both containers with a shared filtered socket.
