Huddle is a security gateway that shields devcontainers from the external network through a per-domain firewall. Every devcontainer runs in a DMZ: all outbound traffic is forced through Huddle, and only domains on the allowlist are let through. Operators manage firewall rules, Docker access, and network logs through a central web UI.
Your IDE (JetBrains or VS Code) feels normal, but code, tools, and AI run in a shielded environment. Execution is isolated; the portal stays in control of what goes in and out.
Huddle addresses two major risks of modern, AI-assisted development:
- Developing safely with AI — AI should be able to help, but not modify your system unchecked or exfiltrate data.
- Developing safely against AI-amplified attacks — supply-chain attacks are getting smarter, faster, and multi-stage. Huddle intercepts outbound traffic and blocks anything that isn't explicitly allowed.
Devcontainer
└─ HTTP/HTTPS traffic → Huddle proxy (port 80)
└─ rules engine → allow / deny / request
└─ Docker socket → /tmp/dc-sockets/<name>/docker.sock (per-container proxy)
└─ label isolation + time-limited grant check
Browser
└─ Angular SPA (port 3000) + WebSocket live push
└─ Fastify REST API (/api/...)
Two servers run in the same process:
| Server | Port | Purpose |
|---|---|---|
| HTTP proxy | 80 | Forward/intercept all outbound container traffic |
| API + UI | 3000 | REST API, Angular frontend, WebSocket push |
| Principle | What it means |
|---|---|
| No direct internet | Traffic flows through the Huddle Gateway with firewall approvals — no container talks to the external network directly. |
| Docker proxy socket | No full Docker socket, but controlled Docker actions through a per-container proxy with a label policy. |
| No root user | A safer default user; sudo is only possible in a controlled way through Huddle. |
- Per-container and global allow/deny rules stored in SQLite
- Rules can be permanent or time-bound (with an expiry date)
- Containers can request access; operators approve or reject via the UI
- HTTP: full request/response logged in the network log
- HTTPS: tunneled through CONNECT (contents not intercepted)
- Every devcontainer gets its own Unix socket at
/tmp/dc-sockets/<name>/docker.sock; the per-container directory is mounted into the container (at/var/run/huddle) andDOCKER_HOSTpoints to the socket. A file mount of the socket itself would keep seeing the dead old inode after a Huddle restart; a directory mount does not. The old flat path/tmp/dc-sockets/<name>.sockremains as a symlink for containers created before this change. - Fine-grained permissions per devcontainer, in two classes:
- Temporary actions (mutations: container create/start/stop/restart/remove/update/exec, image pull/build/push/remove/tag, volume create/remove/prune, network create/remove/connect/disconnect) — only effective while the time-bound grant (1–120 minutes) is active and the action toggle is enabled in the portal
- Always-allowed actions (read-only: list/inspect/logs/stats, ping/version/events) — independent of the timer, enabled per action
- Secure by default: all actions are off by default; the operator explicitly enables what each devcontainer may do. Be extra cautious with
image.push: pushing goes through the host daemon and does not pass the egress firewall
- Policy is enforced per request:
docker ps→ filtered to the container's own started containersdocker run→ allowed; labelhuddle.parentadded automaticallydocker exec→ only the container's own child containers, never the devcontainer itselfdocker rm/docker rmi→ only resources the container created itselfdocker volume rm/ network delete → only the container's own (labeled) resources;dc-net-*networks are untouchabledocker volume prune→ limited to the container's own volumes via an injected label filterdocker push→ only self-built (labeled) imagesdocker images→ all images (read-only)
- Grants and action toggles survive a Huddle restart; proxy sockets are recreated on restart
- Overview of all devcontainers with status, image, uptime, and pending rule requests
- Start a new devcontainer from a snapshot or base image (IntelliJ / Rider / VS Code)
- Commit a running container to a snapshot image
- Force-remove a container including network cleanup
- A per-container Docker socket proxy is created automatically on start
- Every proxied HTTP request is logged (container, domain, method, path, status, headers, body — truncated at 20 KB)
- Admin actions (rule changes, grant changes, container operations) are logged
- Filterable by container, domain, and action prefix
- Angular 21 SPA on port 3000
- WebSocket connection pushes a
reloadevent on every state change - Unified icon system (
app-icon) backed by a central SVG registry - Pie-action menus in the firewall and container views (approve / snooze / reject)
| Layer | Technology |
|---|---|
| Runtime | Node.js 24 LTS (Alpine) |
| Backend | Fastify 5, TypeScript 5 |
| Database | SQLite via better-sqlite3 (WAL mode) |
| WebSocket | ws |
| Frontend | Angular 21 (standalone components, signals) |
| Build | Angular CLI, esbuild |
| Container | Docker multi-stage build |
Requirements: Docker or Podman, Node.js 18+
Huddle's packages are public, so no GitHub token or registry login is needed.
npm install -g @infosupport/huddle-clihuddle inithuddle init pulls the latest Huddle image and starts the container. It automatically detects whether Docker or Podman is available; use huddle init --runtime <docker|podman> (or the HUDDLE_RUNTIME env var) to pick a runtime explicitly. The web UI is available at http://localhost:3000.
After that, you start devcontainers directly from a project directory:
huddleHuddle builds base images automatically when you start a devcontainer. To speed this up, you can build them ahead of time:
docker build -t base-devimage-vscode -f base-devimage-vscode/Dockerfile .
docker build -t base-devimage-intellij -f base-devimage-intellij/Dockerfile .
docker build -t base-devimage-rider -f base-devimage-rider/Dockerfile .You can start devcontainers via the CLI or via the web UI at http://localhost:3000.
From a project directory you start a devcontainer with a single command:
huddle # IntelliJ (default), current directory
huddle --ide rider # Rider
huddle --ide vscode # VS Code
huddle ./my-project # a different directory
huddle --ide vscode ./my-projectOther options:
huddle --name my-container # custom container name
huddle --empty # container without a workspaceAfter starting, the CLI shows the container name and how to open it in your IDE.
- Open JetBrains Gateway
- Go to Remote Development → Dev Containers
- Select the started container
- Click Open Project and choose the project directory in the container
The CLI also prints a direct gateway link once the JetBrains backend has started (this can take a few seconds).
- Open VS Code
- Open the command palette (
Ctrl+Shift+P/Cmd+Shift+P) - Choose Dev Containers: Attach to Running Container
- Select the container name the CLI printed
Blocked requests are visible in the web UI under Firewall. Via the CLI:
huddle fw list # list of recent requests
huddle firewall list -i # interactive modeWhen a devcontainer tries to reach a blocked domain, the request appears on the Firewall page. From there you can allow the domain (permanently or temporarily) or reject it — per container or globally.
When building a base image, Huddle can automatically bake AI CLI configurations (such as CLAUDE.md, settings.json, agents, and skills) into the container. You manage this through the Huddle settings: set the path to your own AI config directory there. Huddle mounts that directory when building the image.
| AI tool | Source path (host) | Target path (container) |
|---|---|---|
| claude | <your-ai-config-dir>/claude/ |
/home/vscode/.claude |
Huddle has a runtime extension platform. Extensions are .zip files you upload through the UI — no restart needed. After uploading, the extension appears as a sub-item in the sidebar.
my-extension.zip
├── manifest.json ← required: id, name, version, settings
├── index.js ← backend (CommonJS, Node.js)
└── frontend/
└── component.js ← UI as a Web Component (optional)
manifest.json:
{
"id": "my-extension",
"name": "My Extension",
"version": "1.0.0",
"settings": [
{ "key": "apiKey", "label": "API key", "secret": true }
]
}index.js — export a register(ctx) function:
exports.register = async function(ctx) {
ctx.app.get('/api/ext/my-extension/data', async (req, reply) => {
const key = ctx.getSetting('apiKey');
return { data: '...' };
});
};frontend/component.js — Web Component for the in-app UI:
class MyExtension extends HTMLElement {
connectedCallback() {
this.innerHTML = '<h1>Hello from the extension</h1>';
}
}
customElements.define('ext-my-extension', MyExtension);ctx.app.get/post/put/delete(path, handler) |
Register a route under /api/ext/<id>/ |
ctx.getSetting(key) / ctx.setSetting(key, value) |
Read/write settings (SQLite) |
ctx.fetch(url, opts) |
HTTP call through the Huddle proxy — appears as ext:<id> in the network log |
ctx.runInContainer(name, cmd) |
Run a shell command in a running devcontainer |
ctx.events |
Listen to Huddle events |
ctx.db |
Direct SQLite access |
ctx.log(msg) |
Log to the Huddle console |
External calls via ctx.fetch() go through the Huddle proxy. The domain must be on the allowlist (Firewall → find the domain → Allow). Requests appear in the network log as ext:<id>.
The built-in Aikido extension lives in gateway/extensions/aikido/. After loading (automatically on start), Aikido Security appears in the sidebar. Functionality:
- Fetch open security issues per workspace from the Aikido API
- Inject issues as context into a running devcontainer (
aikido/AIKIDO_CLAUDE.md,AIKIDO_CONTEXT.md) - Write an MCP server (
aikido-mcp-server.js) into the container so Claude can fetch issues and trigger scans directly - Install an
aikido-fixscript that starts Claude with the right context
You configure credentials (Client ID + Secret) through the UI under Aikido Security → Settings.
| Method | Path | Description |
|---|---|---|
| GET | /api/rules |
List rules (filter: ?status=, ?container=) |
| POST | /api/rules |
Create a rule |
| PUT | /api/rules/:id |
Update a rule's status or expiry |
| POST | /api/rules/:id/resolve |
Resolve a requested rule as allow/deny (per container or global) |
| DELETE | /api/rules/:id |
Delete a rule |
| GET | /api/docker/containers |
List devcontainers with pending rule requests |
| GET | /api/docker/containers/:name |
Container detail + associated rules |
| POST | /api/docker/start |
Start a new devcontainer |
| POST | /api/docker/containers/:name/snapshot |
Commit a container to an image |
| DELETE | /api/docker/containers/:name |
Force-remove a container |
| GET | /api/docker/images |
List snapshot images |
| GET | /api/authz/grants |
List active Docker socket grants |
| PUT | /api/authz/grants/:container |
Grant Docker access (body: { minutes }) |
| DELETE | /api/authz/grants/:container |
Revoke Docker access |
| GET | /api/authz/docker-actions |
Action catalog (kind, group, label, default) |
| GET | /api/authz/docker-actions/:container |
Effective action toggles + grant per container |
| PUT | /api/authz/docker-actions/:container/:action |
Enable/disable an action (body: { enabled }) |
| GET | /api/audit |
Network log (filter: ?container=, ?domain=, ?action=) |
All state-mutating endpoints send a WebSocket { type: "reload" } event to connected clients.
.
├── gateway/ ← Huddle gateway (Fastify API + Angular UI)
│ ├── src/
│ │ ├── index.ts # Init DB, start proxy + API, restore socket proxies
│ │ ├── proxy.ts # HTTP/HTTPS proxy (port 80), rule enforcement, audit
│ │ ├── api.ts # Fastify REST API + WebSocket push (port 3000)
│ │ ├── docker.ts # Docker API helpers, container lifecycle
│ │ ├── socket-proxy.ts # Per-container Docker socket proxy with label policy
│ │ ├── rules.ts # Rule lookup with per-container + global fallback
│ │ ├── db.ts # SQLite schema, network log, Docker grants
│ │ └── events.ts # In-process event bus for state-change notifications
│ └── frontend/src/app/
│ ├── pages/ # dashboard, containers, firewall, docker-access, audit
│ ├── shared/
│ │ ├── icons/ # Central SVG icon registry (icons.ts)
│ │ └── components/ # <app-icon>, pie-menu
│ └── core/
│ ├── models/ # Rule, Container, Grant, AuditLog types
│ └── services/ # ApiService, StateService, ModalService
│ └── extensions/aikido/ ← Built-in Aikido Security extension
├── cli/ ← Cross-platform CLI (`huddle`)
├── .devcontainer/ ← Devcontainer setup for the Huddle repo itself
├── base-devimage-rider/ ← Dockerfile for Rider devcontainers
├── base-devimage-intellij/ ← Dockerfile for IntelliJ devcontainers
└── base-devimage-vscode/ ← Dockerfile for VS Code devcontainers
Want to work on Huddle itself? Huddle is a monorepo with two parts: the gateway (Fastify API + Angular UI + proxy) and the CLI.
Requirements: Node.js 20+ (24 LTS recommended), Docker or Podman, Git.
git clone https://github.com/infosupport/huddle.git
cd huddle
npm install # installs dependencies for gateway and cli
npm run build # builds the gateway (API + frontend)
npm run cli:build # builds the CLI
npm run cli:typecheck # type-checks the CLI
npm start # runs the gateway locally (UI at http://localhost:3000)Running tests:
npm --prefix gateway test # unit + e2e tests (vitest)See CONTRIBUTING.md for the full workflow, branching
strategy, commit conventions, and coding standards.
| Problem | Solution |
|---|---|
docker login ghcr.io or npm install fails with 401/403 |
Your token expired or lacks the read:packages scope. Create a new token and log in again (see Getting Started). |
huddle init finds no runtime |
Make sure Docker or Podman is running. Force it explicitly with huddle init --runtime docker (or podman), or set HUDDLE_RUNTIME. |
Web UI not reachable at http://localhost:3000 |
Check that the Huddle container is running (docker ps). The management API binds to 127.0.0.1 by default — reach it locally, not from another host. |
| Devcontainer can't reach a domain | Expected behavior: all traffic goes through the firewall. Allow the domain via Firewall in the UI or huddle fw list. |
| JetBrains Gateway doesn't see the container right away | The JetBrains backend needs a moment to start; the CLI prints the gateway link once it's ready. |
docker inside the devcontainer gives permission denied |
Docker access runs through a time-bound grant. Grant access via Docker Access in the UI (or PUT /api/authz/grants/:container). |
| Port 80 already in use | Another proxy/web server is using port 80. Stop that service or adjust the port mapping when starting the Huddle container. |
Need more logs? The network log and admin actions are in the UI under
Network log; view container logs with docker logs <container>.
Is Huddle a replacement for a corporate firewall or VPN? No. Huddle shields devcontainers at the application level (per-domain allowlist, controlled Docker actions). It complements, but does not replace, network infrastructure.
Does Huddle work with Podman?
Yes. huddle init automatically detects Docker or Podman; you can force it with
--runtime or the HUDDLE_RUNTIME env var.
Does Huddle intercept HTTPS traffic?
HTTP requests are logged in full. HTTPS is tunneled through CONNECT; its
contents are not intercepted, only the target domain is checked against the
allowlist.
Where is state stored? In a local SQLite database (WAL mode) inside the Huddle container. Firewall rules and Docker grants survive a restart.
Do I have to build the base images myself? No. Huddle builds them automatically when you start a devcontainer. Building them ahead of time can speed this up (see Building base images).
Can I use Huddle in production? Huddle is provided "AS IS" without warranty (see below). Use at your own risk.
Contributions are welcome! Please read first:
CONTRIBUTING.md— how to report bugs, propose features, and open a pull request.CODE_OF_CONDUCT.md— community behavior guidelines.SECURITY.md— report security issues privately, not as a public issue.
Report bugs and ideas via GitHub Issues: github.com/infosupport/huddle/issues
Huddle is free, open source software, provided "AS IS", without any warranty (see the GPL v3, sections 15–17).
- There is no SLA and no guaranteed response time.
- Support is provided on a volunteer basis by the community.
- Issues and pull requests are welcome, but there is no guarantee that a report will be picked up or implemented.
See SUPPORT.md for details.
Huddle is licensed under the GNU General Public License v3.0 (or later).
See the LICENSE file for the full text.
Copyright (C) 2026 Info Support B.V.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.


