Restructured as a proper technical architecture document: - Added Observability section (correlation IDs, structured errors, debugging) - Reorganized into logical flow: overview, request flow, contracts, internals - Removed stale rollback/backup references - Updated all references in README, CLAUDE.md, PROJECT.md, STATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
17 KiB
Architecture
Technical reference for the Unraid Docker Manager workflow system.
System Overview
The bot is an n8n workflow that receives Telegram messages, routes them through authentication and keyword matching, dispatches to domain-specific sub-workflows, and sends responses back to the user.
Telegram
|
v
n8n-workflow.json (166 nodes)
|-- Auth check (Telegram user ID)
|-- Correlation ID generation
|-- Text path: Keyword Router -> Parse Command -> Match Container -> Execute
|-- Callback path: Parse Callback Data -> Route -> Execute
|
|-- n8n-update.json (34 nodes) Container image pull + recreate
|-- n8n-actions.json (11 nodes) Start / stop / restart
|-- n8n-logs.json (9 nodes) Log retrieval + formatting
|-- n8n-batch-ui.json (17 nodes) Batch selection keyboard UI
|-- n8n-status.json (11 nodes) Container list + status display
|-- n8n-confirmation.json (16 nodes) Confirmation dialogs
|-- n8n-matching.json (23 nodes) Container name matching
|
v
docker-socket-proxy (tecnativa/docker-socket-proxy)
|
v
Docker Engine
Total: 287 nodes (166 main + 121 across 7 sub-workflows)
Workflow Files
| File | n8n ID | Purpose | Nodes |
|---|---|---|---|
| n8n-workflow.json | HmiXBlJefBRPMS0m4iNYc |
Main orchestrator | 166 |
| n8n-update.json | 7AvTzLtKXM2hZTio92_mC |
Container update (pull, recreate, cleanup) | 34 |
| n8n-actions.json | fYSZS5PkH0VSEaT5 |
Container start/stop/restart | 11 |
| n8n-logs.json | oE7aO2GhbksXDEIw |
Container log retrieval | 9 |
| n8n-batch-ui.json | ZJhnGzJT26UUmW45 |
Batch selection keyboard | 17 |
| n8n-status.json | lqpg2CqesnKE2RJQ |
Container list and status | 11 |
| n8n-confirmation.json | fZ1hu8eiovkCk08G |
Confirmation dialogs | 16 |
| n8n-matching.json | kL4BoI8ITSP9Oxek |
Container name matching | 23 |
Request Flow
Text Commands
- Telegram Trigger receives message
- Auth IF node checks user ID
- Correlation ID generator creates a unique request trace ID (
timestamp-random) - Keyword Router (Switch node) matches command keyword
- Parse Command (Code node) extracts parameters
- Matching sub-workflow resolves container name to Docker ID
- Domain sub-workflow executes the operation
- Result routed to Telegram response node
Callback (Inline Keyboard)
- Telegram Trigger receives callback query
- Auth IF node checks user ID
- Correlation ID generator creates a unique request trace ID
- Parse Callback Data (Code node, 441 lines) decodes callback string
- Route Callback (Switch node) dispatches by prefix
- Domain sub-workflow executes the operation
- Result routed to Telegram response node (editMessageText)
Observability
Correlation IDs
Every request gets a unique correlation ID generated at the entry point of the main workflow. This ID flows through all sub-workflow calls via Prepare Input nodes, enabling request tracing across workflow boundaries in the n8n execution log.
How it works:
- Two generator nodes in the main workflow: one for the text path, one for the callback path
- Format:
timestamp-randomString(no external dependencies) - All 19 Prepare Input nodes include
correlationId: $json.correlationIdin their output - Sub-workflows receive the ID as an input field and can reference it in their logs
Where to find it: Open any execution in the n8n UI, inspect the output of a Prepare Input node — the correlationId field traces back to the original user request.
Limitations: Correlation IDs are only visible in the n8n execution log. There is no persistent storage or user-facing output. n8n's workflow static data is execution-scoped (not workflow-scoped), so ring buffers and cross-execution logging are not possible on this platform.
Structured Error Returns
All 7 sub-workflows return structured error objects on failure:
{
"success": false,
"error": "Container not found: foo",
"correlationId": "1707400000-abc123"
}
This provides a consistent error shape for the main workflow to route and format error messages to the user.
Debugging a Request
- Open the n8n execution list for the main workflow
- Find the execution by timestamp or Telegram message content
- Check the Prepare Input node output for the
correlationId - Search sub-workflow executions for the same
correlationId - Trace the full request path: main workflow -> sub-workflow -> Docker API -> response
Sub-workflow Contracts
Each sub-workflow has a defined input/output contract. The main workflow communicates with sub-workflows through Prepare Input (Code) nodes that build the input object, and Route Result (Code) nodes that interpret the response.
n8n-update.json (Container Update)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| containerId | string | yes* | Docker container ID (empty string to resolve by name) |
| containerName | string | yes | Container display name |
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID (0 for text mode) |
| responseMode | string | yes | "text", "inline", or "batch" |
| correlationId | string | no | Request trace ID |
*containerId can be empty — the sub-workflow resolves by name via its Resolve Container ID node.
Output:
| Outcome | Fields |
|---|---|
| Updated | success: true, updated: true, message, oldDigest, newDigest |
| No update needed | success: true, updated: false, message |
| Error | success: false, updated: false, message |
Callers: Execute Text Update, Execute Callback Update, Execute Batch Update
n8n-actions.json (Container Actions)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| containerId | string | yes | Docker container ID |
| containerName | string | yes | Container display name |
| action | string | yes | "start", "stop", or "restart" |
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID (0 for text mode) |
| responseMode | string | yes | "text", "inline", or "batch" |
| correlationId | string | no | Request trace ID |
Output: success, message, action, containerName, containerId, chatId, messageId, responseMode
HTTP status codes are checked before message content: 204 = success, 304 = already in state (treated as success), others = error.
Callers: Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow
n8n-logs.json (Container Logs)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| containerName | string | yes* | Container name for lookup |
| containerId | string | no | Docker container ID (optional) |
| lineCount | number | no | Lines to retrieve (default: 50, max: 1000) |
| chatId | number | yes | Telegram chat ID |
| messageId | number | no | Telegram message ID (default: 0) |
| responseMode | string | no | "text" or "inline" (default: "text") |
| correlationId | string | no | Request trace ID |
*Either containerId or containerName is required.
Output: success: true, message, containerName, lineCount
Errors (container not found, Docker error) throw exceptions handled by n8n's error system.
Callers: Execute Text Logs, Execute Inline Logs
n8n-batch-ui.json (Batch Selection UI)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID |
| callbackData | string | yes | Raw callback data string |
| queryId | string | yes | Telegram callback query ID |
| action | string | yes | "mode", "toggle", "nav", "exec", "clear", "cancel" |
| batchPage | number | no | Current page number (default: 0) |
| selectedCsv | string | no | Comma-separated selected container names |
| toggleName | string | no | Container name being toggled |
| batchAction | string | no | Action for batch execution (stop/restart/update) |
| correlationId | string | no | Request trace ID |
Output:
| action | Description |
|---|---|
"keyboard" |
Rendered selection keyboard with checkmarks |
"execute" |
User confirmed — includes containerNames and batchAction |
"cancel" |
User cancelled batch selection |
Batch selection uses bitmap encoding (base36 BigInt) to fit container selections within Telegram's 64-byte callback data limit.
Callers: Execute Batch UI
n8n-status.json (Container Status/List)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID |
| action | string | yes | "list", "status", "paginate" |
| containerId | string | no | Docker container ID (for status lookup) |
| containerName | string | no | Container name (for status lookup) |
| page | number | no | Page number for pagination (default: 0) |
| queryId | string | no | Telegram callback query ID |
| searchTerm | string | no | Container name search term |
| correlationId | string | no | Request trace ID |
Output:
| action | Description |
|---|---|
"list" |
Container list with pagination keyboard |
"status" |
Single container detail with action buttons |
"paginate" |
Updated page of container list |
The container list keyboard includes an "Update All :latest" button after the navigation row.
Callers: Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status
n8n-confirmation.json (Confirmation Dialogs)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID |
| action | string | yes | "show_stop", "show_update", "confirm", "cancel", "expired" |
| containerId | string | no | Docker container ID |
| containerName | string | yes | Container display name |
| confirmAction | string | no | "stop" or "update" (for confirm action) |
| confirmationToken | string | no | Timestamp token for 30-second expiry check |
| expired | boolean | no | Whether confirmation has expired |
| responseMode | string | no | "inline" (default) |
| correlationId | string | no | Request trace ID |
This sub-workflow internally calls n8n-actions.json for confirmed stop actions.
Output:
| action | Description |
|---|---|
"show_stop" |
Stop confirmation dialog rendered |
"show_update" |
Update confirmation dialog rendered |
"confirm_stop_result" |
Stop executed, result returned |
"confirm_update" |
Update confirmed, containerId/name returned for update sub-workflow |
"cancel" |
Confirmation cancelled |
"expired" |
Confirmation token expired (30-second timeout) |
Callers: Execute Confirmation (fed by 3 Prepare Input nodes: Prepare Confirm Input, Prepare Show Stop Input, Prepare Show Update Input)
n8n-matching.json (Container Matching/Disambiguation)
Input:
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | yes | "match_action", "match_update", or "match_batch" |
| containerList | string | yes | Raw Docker API JSON output (container list) |
| searchTerm | string | yes | Container name query to match |
| selectedContainers | string | no | Comma-separated names for batch matching |
| chatId | number | yes | Telegram chat ID |
| messageId | number | yes | Telegram message ID |
| correlationId | string | no | Request trace ID |
Matching priority: exact match > single substring match > multiple matches (disambiguation) > no match (suggestion keyboard).
Output (action match):
| action | Description |
|---|---|
"matched" |
Single container matched — includes containerId, containerName |
"multiple" |
Multiple matches — includes matches array for disambiguation |
"no_match" |
No match found |
"suggestion" |
Suggestion keyboard with close matches |
"error" |
Matching error |
Output (update match): Same as action match with _update suffix on action values.
Output (batch match):
| action | Description |
|---|---|
"batch_matched" |
All names resolved — includes matchedContainers array |
"disambiguation" |
Some names ambiguous — disambiguation keyboard |
"not_found" |
Some names not found |
Callers: Execute Action Match, Execute Update Match, Execute Batch Match
Execute Workflow Node Map
17 Execute Workflow nodes in the main workflow dispatch to 7 sub-workflows:
| Target | Execute Nodes |
|---|---|
| n8n-update.json | Execute Text Update, Execute Callback Update, Execute Batch Update |
| n8n-actions.json | Execute Container Action, Execute Inline Action, Execute Batch Action Sub-workflow |
| n8n-logs.json | Execute Text Logs, Execute Inline Logs |
| n8n-batch-ui.json | Execute Batch UI |
| n8n-status.json | Execute Container Status, Execute Select Status, Execute Paginate Status, Execute Batch Cancel Status |
| n8n-confirmation.json | Execute Confirmation |
| n8n-matching.json | Execute Action Match, Execute Update Match, Execute Batch Match |
Main Workflow Internals
Node Breakdown (166 nodes)
| Category | Count | Purpose |
|---|---|---|
| Code | 60 | Command parsing, input preparation, result routing, response building, batch orchestration |
| HTTP Request | 40 | Docker API and Telegram API calls |
| Telegram | 23 | User-facing response nodes |
| Execute Workflow | 17 | Sub-workflow dispatch |
| Switch | 13 | Keyword Router, Route Callback, result routing |
| If | 8 | Auth checks, batch completion, expiry, status routing |
| Execute Command | 6 | Docker CLI (container list, exec) |
| Telegram Trigger | 1 | Entry point |
Code Node Categories
The 60 Code nodes break down into orchestration categories. All are infrastructure — none contain extractable domain logic:
| Category | Count | Role |
|---|---|---|
| prepare-input | 27 | Build input objects for sub-workflow calls |
| route-result | 12 | Interpret sub-workflow responses for routing |
| build-response | 8 | Build Telegram messages and keyboards |
| orchestration | 6 | Batch loop control and state management |
| parse-command | 5 | Parse text commands into structured data |
| domain-logic | 2 | Legacy candidates (net-negative extraction) |
Callback Data Encoding
Telegram limits callback data to 64 bytes. The system uses two encoding schemes:
Colon-delimited for single operations: s:containerId (status), stop:containerId (action), cfm:stop:containerId:token (confirmation)
Bitmap encoding for batch selection: b:0:1a3:5 where the middle segment is a base36 BigInt representing selected container indices. Supports 50+ containers within the 64-byte limit.
Legacy parsers (batch:toggle:, batch:nav:, batch:exec:) are retained for graceful migration of in-flight messages.
Deployment
Redeploying After Changes
- Import the modified sub-workflow JSON into n8n
- If main workflow changed, re-import n8n-workflow.json
- Activate the workflow
Workflow IDs are stable — n8n preserves them across re-imports of the same workflow.
Execute Workflow Node Format
All Execute Workflow nodes use typeVersion 1.2:
"workflowId": { "__rl": true, "mode": "list", "value": "<id>" }
Testing Checklist
After deployment, verify:
status— Shows container list with pagination- Tap container — Shows detail with action buttons
stop <name>— Confirmation dialog, confirm executes stopupdate <name>— Confirmation dialog, confirm pulls image + recreatesrestart <name>— Immediate restartlogs <name>— Shows last 50 linesstop plex sonarr— Batch selection keyboard- Select multiple, execute — Batch processes all selected
update all— Lists :latest containers, confirm updates all- Update All button in keyboard — Same flow via inline keyboard
Known Limitations
Unraid Update Badges
After the bot updates a container, Unraid's Docker tab may show "apply update" on the next check. The bot uses Docker API directly while Unraid tracks containers through its XML template system — it doesn't know the container was updated externally.
Resolution: Click "Apply Update" in Unraid. It completes instantly since the image is already cached.
Why not automated: Clearing the badge would require calling Unraid's internal web API (authentication, template parsing) for a cosmetic issue that takes one click.
n8n Static Data
n8n workflow static data ($getWorkflowStaticData('global')) is execution-scoped, not workflow-scoped. Data written in one execution is not available in the next. This prevents persistent cross-execution features like error ring buffers or debug command history.
Orphan Nodes
3 legacy Code nodes remain in the main workflow (Build Action Command, Build Immediate Action Command, Prepare Cancel Return). They are unreachable dead code from pre-modularization inline action paths. They have no incoming connections and do not affect functionality.