Standalone Mode
Run Portwing as a hardened remote Docker API without a Drydock platform connection, using the generic REST adapter.
Portwing normally operates in one of two connection modes — edge (WebSocket to Drydock) or standard (Drydock-compatible HTTP server). Standalone mode is a third option: set ADAPTER=generic and Portwing becomes a self-contained, security-hardened remote Docker API with no external controller required.
The generic adapter exposes a clean REST and SSE surface under /api/v1/*, backed directly by the local Docker daemon. Everything else — auth enforcement, rate limiting, TLS, the Docker API passthrough, the MCP server, health endpoints — works identically to the other modes.
Enabling the generic adapter
Pass ADAPTER=generic when starting the container. You must also configure at least one auth mechanism unless you are on an isolated private network.
docker run -d \
--name portwing \
--group-add $(stat -c '%g' /var/run/docker.sock) \
-v /var/run/docker.sock:/var/run/docker.sock \
-e ADAPTER=generic \
-e TOKEN=my-secret \
-p 3000:3000 \
ghcr.io/codeswhat/portwing:latestNever expose Portwing directly on a public interface without TLS. Run it behind a reverse proxy that terminates TLS, or supply TLS_CERT and TLS_KEY to enable the built-in TLS listener. See Configuration.
The ADAPTER environment variable defaults to drydock. The only change needed to switch modes is setting it to generic. All other configuration — token auth, Ed25519 keys, TLS, bind address, port — is unchanged.
REST endpoints
The generic adapter registers four authenticated routes. All require a valid token or Ed25519 signature; see Authentication for how to supply credentials.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/version | GET | Agent version, protocol name/version, active adapter |
/api/v1/containers | GET | Cached container inventory |
/api/v1/containers/{id}/logs | GET | Demuxed container logs (tail, time-filter, live follow) |
/api/v1/events | GET | SSE stream of Docker container lifecycle events |
GET /api/v1/version
Returns the running agent version, the protocol name and version, and the adapter name (generic).
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/v1/version | jq .{
"agentVersion": "0.9.1",
"protocolName": "portwing",
"protocolVersion": "1.0",
"adapter": "generic"
}GET /api/v1/containers
Returns the cached container inventory built from the Docker daemon. The response is an array of container objects with id, name, image, state, and label metadata.
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/v1/containers | jq .GET /api/v1/containers/{id}/logs
Returns demuxed container logs. Docker's 8-byte stream-multiplexing frame header is stripped before the response is written. The id parameter accepts a container ID or name.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
tail | integer | Number of lines from the end to return |
since | string | Show logs since timestamp (RFC3339) or relative duration (e.g. 1h) |
until | string | Show logs before timestamp or duration |
follow | 1 or true | Stream logs in real time with chunked transfer encoding |
# Last 50 lines
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/api/v1/containers/my-container/logs?tail=50"
# Live stream
curl -sN -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/api/v1/containers/my-container/logs?follow=1"
# Logs from the last hour
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/api/v1/containers/my-container/logs?since=1h"GET /api/v1/events
Opens a Server-Sent Events stream of Docker container lifecycle events. Each data: frame carries a JSON envelope. The connection stays open until the client disconnects; a comment heartbeat line is written every 30 seconds to keep the connection alive through proxies.
curl -sN -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/v1/eventsEach event has this shape:
{
"ts": "2026-06-11T10:00:00Z",
"type": "container",
"action": "start",
"containerId": "abc123def456",
"name": "my-container",
"image": "nginx:latest",
"labels": { "app": "web" }
}The labels field contains only user-defined container labels; synthetic Docker attributes (name, image, exitCode, etc.) are stripped before the event is written.
Event actions: create, start, stop, die, kill, restart, pause, unpause, destroy, rename, update, oom, health_status.
A comment heartbeat (": heartbeat") is written every 30 seconds. Standard SSE clients ignore comment lines automatically. They are present solely to prevent idle-connection timeouts in load balancers and proxies.
Health endpoints
These two endpoints are unauthenticated and are safe for load-balancer probes and Docker HEALTHCHECK instructions. They behave identically regardless of which adapter is active.
| Endpoint | Description |
|---|---|
GET /health | Returns {"status":"ok"} — HTTP 200 always |
GET /_portwing/health | Returns Docker connectivity status — HTTP 503 if daemon is unreachable |
# Simple liveness probe
curl -sf http://localhost:3000/health
# Readiness probe (includes Docker connectivity)
curl -sf http://localhost:3000/_portwing/health{
"status": "healthy",
"docker": "connected"
}Agent info endpoint
GET /_portwing/info returns agent version, Docker version, connection mode, uptime, hostname, and the capability list. Auth required.
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/_portwing/info | jq .The base capabilities compose, exec, metrics, and events are always present regardless of adapter, followed by the generic adapter's own containers, logs, events, and version. This means the full array is ["compose", "exec", "metrics", "events", "containers", "logs", "events", "version"] — note events appears twice, since it is both a base capability and one the generic adapter reports.
Docker API passthrough
Any path not matched by a named route is transparently proxied to the local Docker daemon over the Unix socket. This covers every endpoint in the Docker Engine API — including streaming endpoints (/logs, /attach, /exec/*/start, /events, /build), binary responses, and interactive exec sessions using Upgrade: tcp.
The agent negotiates the API version on startup; v1.44 is the fallback. You can address the proxy with or without the version prefix — the agent prepends it automatically when omitted.
# List running containers via the Docker proxy
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:3000/v1.44/containers/json?filters=%7B%22status%22%3A%5B%22running%22%5D%7D" | jq .
# Inspect a container
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/v1.44/containers/my-container/json | jq .The Docker proxy exposes the full Docker Engine API, including write operations (create, remove, exec). Always run with authentication configured, and restrict the BIND_ADDRESS or network rules to trusted clients only.
Authentication
All /api/v1/* endpoints and the Docker proxy require a valid credential. Supply it in any of the accepted headers:
| Header | Usage |
|---|---|
Authorization: Bearer TOKEN | Standard bearer token (checked first) |
X-Portwing-Token: TOKEN | Alternate header |
X-Portwing-Signature + three companion headers | Ed25519 per-request signature |
Ten failed attempts per IP per rolling minute result in HTTP 429 until the window expires.
For full details on token options (TOKEN, TOKEN_FILE, TOKEN_HASH) and Ed25519 key setup, see Authentication.
MCP server (read-only alternative)
Portwing also exposes a read-only Model Context Protocol server at POST /_portwing/mcp. It is available in all adapter modes, including generic. AI assistants (Claude, Cursor, Windsurf) can query live container state through this endpoint without issuing raw REST calls.
Available tools: list_containers, inspect_container, container_logs, host_metrics, container_stats.
See MCP Server for configuration and client setup instructions.
Further reading
- Authentication — token options, Ed25519 key enrollment, rate limiting
- Configuration — full environment variable reference including
TLS_CERT,TLS_KEY,BIND_ADDRESS,PORT - API Reference — OpenAPI specification for all endpoints
Drydock Integration
How Portwing serves as a Drydock agent — the Drydock-native container model, REST/SSE endpoints, the adapter that maps Drydock expectations onto Docker, and how a Drydock controller registers and communicates with the agent.
MCP Server
Portwing's built-in read-only Model Context Protocol endpoint lets MCP-aware AI agents query live Docker container and host state.