When more than ~20 Docker containers are running, the WebSocket message containing the full container list can exceed frame/buffer limits imposed by intermediary proxies (Traefik, nginx, Cloudflare tunnels, etc.), causing the Docker Container View in Pangolin to show nothing — even though newt successfully sends the data.
Root cause analysis
The issue is not in the ws library or node-cache — both handle large payloads fine. The problem is the network path between newt and pangolin:
gorilla/websocket.WriteJSON serializes the full container list (55+ containers × 1-5KB each = 55-275KB) into a single WebSocket text frame. When this frame passes through Traefik or another reverse proxy, it can be:
Silently truncated — proxy buffers the frame, hits an internal limit, drops it
Connection reset — proxy closes the connection on oversized frame
Corrupted — partial frame arrives, JSON.parse fails silently in the try/catch
The user's logs confirm this: pangolin logged Docker containers for Newt: 55 on one occasion but the container view was empty — meaning the cache write at line 57 either never executed or the data was already corrupted by that point.
Each chunk message has the same type (newt/socket/containers) with additional fields:
{"containers":[...],// subset of containers for this chunk
"chunkIndex":0,// 0-based index of this chunk
"totalChunks":4,// total number of chunks in this batch
"batchId":"a1b2c3d4"// unique ID for this batch
}
Why batchId?
Without it, two concurrent sends (e.g., a manual fetch + a Docker event) would interleave chunks and corrupt the accumulated data. The batchId ensures:
Each batch is tracked independently
A newer batch automatically supersedes any in-progress older batch
No silent data corruption from race conditions
Why chunk size of 15?
Each Docker container with full metadata (labels, ports, networks, env) serializes to 1-5KB of JSON. 15 containers ≈ 15-75KB per message — well within typical proxy frame limits (most default to 64KB-1MB). The threshold of 15 was chosen to stay safely under common defaults while minimizing the number of round trips.
Changes in this PR (pangolin server)
server/routers/newt/handleSocketMessages.ts:
ChunkAccumulator typed interface for cache data (replaces inline object)
Non-chunked messages pass through unchanged (backward compatible with older newt)
Validates chunkIndex, totalChunks, batchId before processing — rejects malformed chunks
Accumulates chunks in cache keyed by {newtId}:dockerContainersChunks
New batchId supersedes any in-progress batch (handles concurrent sends)
120s TTL on partial chunks prevents stale data accumulation
Processes complete list only after all chunks arrive
sendContainerList() helper splits lists >15 into chunks with batchId
Small lists (≤15) sent as single message — zero behavior change for most users
go build passes cleanly
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/fosrl/pangolin/pull/2817
**Author:** [@jaydeep-pipaliya](https://github.com/jaydeep-pipaliya)
**Created:** 4/9/2026
**Status:** 🔄 Open
**Base:** `main` ← **Head:** `fix/chunk-docker-container-messages`
---
### 📝 Commits (2)
- [`f1ae17e`](https://github.com/fosrl/pangolin/commit/f1ae17e3b1abe4c615d4f6f5f9c33674046c08aa) fix: support chunked Docker container messages from newt
- [`5f37315`](https://github.com/fosrl/pangolin/commit/5f37315c5319d05ff53e8929b37629f596f02ba6) fix: robust chunk reassembly with batchId, validation, and typed accumulator
### 📊 Changes
**1 file changed** (+110 additions, -9 deletions)
<details>
<summary>View changed files</summary>
📝 `server/routers/newt/handleSocketMessages.ts` (+110 -9)
</details>
### 📄 Description
## What does this PR do?
Fixes #2117
When more than ~20 Docker containers are running, the WebSocket message containing the full container list can exceed frame/buffer limits imposed by intermediary proxies (Traefik, nginx, Cloudflare tunnels, etc.), causing the Docker Container View in Pangolin to show nothing — even though newt successfully sends the data.
## Root cause analysis
The issue is **not** in the `ws` library or `node-cache` — both handle large payloads fine. The problem is the network path between newt and pangolin:
```
newt (Go) → gorilla/websocket → [Traefik / reverse proxy] → ws (Node.js) → pangolin
```
`gorilla/websocket.WriteJSON` serializes the full container list (55+ containers × 1-5KB each = 55-275KB) into a single WebSocket text frame. When this frame passes through Traefik or another reverse proxy, it can be:
- **Silently truncated** — proxy buffers the frame, hits an internal limit, drops it
- **Connection reset** — proxy closes the connection on oversized frame
- **Corrupted** — partial frame arrives, `JSON.parse` fails silently in the `try/catch`
The user's logs confirm this: pangolin logged `Docker containers for Newt: 55` on one occasion but the container view was empty — meaning the cache write at line 57 either never executed or the data was already corrupted by that point.
## Solution: chunked message protocol
### Protocol design
```
┌─────────┐ ┌──────────┐
│ newt │ │ pangolin │
│ (Go) │ │ (TS) │
└────┬────┘ └────┬─────┘
│ ≤15 containers: single message │
│──────────────────────────────────────>│ (backward compatible)
│ │
│ >15 containers: chunked │
│ chunk 0/4 (batch=a1b2c3) │
│──────────────────────────────────────>│ accumulate in cache
│ chunk 1/4 (batch=a1b2c3) │
│──────────────────────────────────────>│ accumulate
│ chunk 2/4 (batch=a1b2c3) │
│──────────────────────────────────────>│ accumulate
│ chunk 3/4 (batch=a1b2c3) │
│──────────────────────────────────────>│ all chunks received → process
│ │
```
Each chunk message has the same type (`newt/socket/containers`) with additional fields:
```json
{
"containers": [...], // subset of containers for this chunk
"chunkIndex": 0, // 0-based index of this chunk
"totalChunks": 4, // total number of chunks in this batch
"batchId": "a1b2c3d4" // unique ID for this batch
}
```
### Why `batchId`?
Without it, two concurrent sends (e.g., a manual fetch + a Docker event) would interleave chunks and corrupt the accumulated data. The `batchId` ensures:
- Each batch is tracked independently
- A newer batch automatically supersedes any in-progress older batch
- No silent data corruption from race conditions
### Why chunk size of 15?
Each Docker container with full metadata (labels, ports, networks, env) serializes to 1-5KB of JSON. 15 containers ≈ 15-75KB per message — well within typical proxy frame limits (most default to 64KB-1MB). The threshold of 15 was chosen to stay safely under common defaults while minimizing the number of round trips.
## Changes in this PR (pangolin server)
**`server/routers/newt/handleSocketMessages.ts`**:
- `ChunkAccumulator` typed interface for cache data (replaces inline object)
- Non-chunked messages pass through unchanged (backward compatible with older newt)
- Validates `chunkIndex`, `totalChunks`, `batchId` before processing — rejects malformed chunks
- Accumulates chunks in cache keyed by `{newtId}:dockerContainersChunks`
- New `batchId` supersedes any in-progress batch (handles concurrent sends)
- 120s TTL on partial chunks prevents stale data accumulation
- Processes complete list only after all chunks arrive
## Companion PR
**Newt (Go):** https://github.com/fosrl/newt/pull/304
- `sendContainerList()` helper splits lists >15 into chunks with `batchId`
- Small lists (≤15) sent as single message — zero behavior change for most users
- `go build` passes cleanly
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/fosrl/pangolin/pull/2817
Author: @jaydeep-pipaliya
Created: 4/9/2026
Status: 🔄 Open
Base:
main← Head:fix/chunk-docker-container-messages📝 Commits (2)
f1ae17efix: support chunked Docker container messages from newt5f37315fix: robust chunk reassembly with batchId, validation, and typed accumulator📊 Changes
1 file changed (+110 additions, -9 deletions)
View changed files
📝
server/routers/newt/handleSocketMessages.ts(+110 -9)📄 Description
What does this PR do?
Fixes #2117
When more than ~20 Docker containers are running, the WebSocket message containing the full container list can exceed frame/buffer limits imposed by intermediary proxies (Traefik, nginx, Cloudflare tunnels, etc.), causing the Docker Container View in Pangolin to show nothing — even though newt successfully sends the data.
Root cause analysis
The issue is not in the
wslibrary ornode-cache— both handle large payloads fine. The problem is the network path between newt and pangolin:gorilla/websocket.WriteJSONserializes the full container list (55+ containers × 1-5KB each = 55-275KB) into a single WebSocket text frame. When this frame passes through Traefik or another reverse proxy, it can be:JSON.parsefails silently in thetry/catchThe user's logs confirm this: pangolin logged
Docker containers for Newt: 55on one occasion but the container view was empty — meaning the cache write at line 57 either never executed or the data was already corrupted by that point.Solution: chunked message protocol
Protocol design
Each chunk message has the same type (
newt/socket/containers) with additional fields:Why
batchId?Without it, two concurrent sends (e.g., a manual fetch + a Docker event) would interleave chunks and corrupt the accumulated data. The
batchIdensures:Why chunk size of 15?
Each Docker container with full metadata (labels, ports, networks, env) serializes to 1-5KB of JSON. 15 containers ≈ 15-75KB per message — well within typical proxy frame limits (most default to 64KB-1MB). The threshold of 15 was chosen to stay safely under common defaults while minimizing the number of round trips.
Changes in this PR (pangolin server)
server/routers/newt/handleSocketMessages.ts:ChunkAccumulatortyped interface for cache data (replaces inline object)chunkIndex,totalChunks,batchIdbefore processing — rejects malformed chunks{newtId}:dockerContainersChunksbatchIdsupersedes any in-progress batch (handles concurrent sends)Companion PR
Newt (Go): https://github.com/fosrl/newt/pull/304
sendContainerList()helper splits lists >15 into chunks withbatchIdgo buildpasses cleanly🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.