{"id":50854624,"url":"https://github.com/0xorial/hive-containers","last_synced_at":"2026-06-14T17:30:45.371Z","repository":{"id":364751151,"uuid":"1268536035","full_name":"0xorial/hive-containers","owner":"0xorial","description":"Hierarchical Claude Code containers with one curated egress bridge: a root control plane, a sealed node tree, per-node live internet/GitHub switches, and desktop-app SSH access.","archived":false,"fork":false,"pushed_at":"2026-06-14T09:43:55.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T11:22:52.553Z","etag":null,"topics":["ai-agents","claude-code","devcontainer","docker","egress-filtering","sandbox","squid"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0xorial.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T16:44:19.000Z","updated_at":"2026-06-14T09:43:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/0xorial/hive-containers","commit_stats":null,"previous_names":["0xorial/hive-containers"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/0xorial/hive-containers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xorial%2Fhive-containers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xorial%2Fhive-containers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xorial%2Fhive-containers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xorial%2Fhive-containers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xorial","download_url":"https://codeload.github.com/0xorial/hive-containers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xorial%2Fhive-containers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34331806,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude-code","devcontainer","docker","egress-filtering","sandbox","squid"],"created_at":"2026-06-14T17:30:44.862Z","updated_at":"2026-06-14T17:30:45.366Z","avatar_url":"https://github.com/0xorial.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hive\n\nHierarchical Claude Code containers with one curated bridge to your Mac.\n\n```\n MacBook ────────────────────────────────────────────────────────────────\n │  hive CLI · VS Code attach · config/ (the two curated files)\n │\n │   docker         ┌────────────────────────────────────────────┐\n │   socket ──────► │ root        control plane (docker CLI +    │\n │                  │             hive CLI + Claude Code)        │\n │                  │   ├─ node a      ├─ node b                 │\n │                  │   │   └─ node a2 │                         │  hive-net\n │                  │   (any depth, tracked via labels)          │  (internal —\n │                  └───────────────────┬────────────────────────┘   no way out)\n │                                      │ only crossing point\n │                            ┌─────────┴─────────┐\n │   host.docker.internal ◄── │ bridge            │ ──► internet\n │   (only what's listed in   │  squid 3128       │     (allowlisted domains for\n │    config/forwards.conf)   │  socat forwards   │      sealed nodes; everything\n │                            └───────────────────┘      for nodes you `net on`)\n```\n\nThree guarantees, all enforced by Docker networking rather than convention:\n\n1. **Nodes are sealed.** Every node sits on `hive-net`, an `internal: true`\n   network. No internet, no Mac, not even with root inside the container.\n2. **The bridge is the only door, and you curate both sides of it.**\n   Outbound HTTP(S) goes through a domain allowlist\n   ([config/egress-allowlist.txt](config/egress-allowlist.txt)); access to your\n   Mac exists only as the explicit TCP forwards in\n   ([config/forwards.conf](config/forwards.conf)).\n3. **Only `root` holds the Docker socket.** It is the one container that can\n   spawn or destroy others — a Claude instance in `root` can orchestrate the\n   whole tree, while Claude instances in nodes can't touch the hierarchy.\n\n## Quickstart\n\n```sh\nalias hive=\"$PWD/bin/hive\"     # or add bin/ to PATH\n\nhive up                        # build + start root and bridge\nhive claude root               # log in once; credentials are shared hive-wide\nhive ssh setup                 # once: lets the Claude desktop app open sessions in nodes\nhive doctor                    # verify the isolation actually holds\n\nhive new api                   # spawn a node (fresh volume workspace)\nhive new web --bind ~/code/web # ...or mount a Mac directory as /workspace\nhive sh api                    # zsh inside\nhive claude api                # interactive claude inside\nhive claude api -p \"run the tests and summarize failures\"   # headless\nhive tree                      # see the hierarchy\nhive rm api --purge            # remove node + its workspace volume\n```\n\n## Every container knows where it is\n\nOn each start, a container renders its own identity into\n`/etc/claude-code/CLAUDE.md` (Claude Code's Linux managed-policy memory path),\nso the Claude inside it loads — before anything else, every session — who it\nis, that it's sealed behind the bridge, how egress and host forwards work, and\nits role. `root` additionally learns it's the control plane and how to drive\nthe tree; a node learns it's a leaf and to ask you for network changes rather\nthan fight the allowlist. The text is derived from the container's role and\nenvironment (name, parent, whether it has the proxy or `--uplink`), so it's\nalways accurate after any restart or rebuild — nothing to maintain by hand.\nThis lives on the container's own filesystem, so it never collides via the\nshared `~/.claude` volume nor clutters your bind-mounted `/workspace`. Confirm\nit's loaded inside a session with `/memory`.\n\n## The hierarchy\n\n`--parent` builds arbitrary trees; the hierarchy is *logical* (Docker labels),\nwhile spawn *privilege* stays physical (only `root` has the socket):\n\n```sh\nhive new backend\nhive new backend-tests --parent backend\nhive tree\n```\n\nOrchestration pattern: run `hive claude root` and let that Claude drive the\nfleet. **`hive` is the same control plane whether you run it on your Mac or\ninside `root`** — `root` holds the Docker socket and has `hive` on its PATH\n(symlinked to the live `/workspace/bin/hive`), so from a shell in `root` you\ncan `hive new`, `hive \u003cnode\u003e net on`, `hive tree`, and `hive claude \u003cnode\u003e -p\n\"...\"` its children, then collect results through the `/shared` volume. The\none split: lifecycle commands (`build` / `up` / `down`) stay on the Mac,\nbecause they manage the container set that includes `root` itself. Every\ncontainer also shares the `hive-claude` volume, so you authenticate once.\n\n## The bridge — your curation surface\n\nThe bridge runs **Squid**, which applies two layers of \"allow\": the curated\ndomain allowlist (every node) and a per-node open set of source IPs (the nodes\nyou `net on`). Both files below live in [config/](config/) on your Mac, mounted\nread-only into the bridge. Edit, then `hive bridge reload`.\n\n- **[egress-allowlist.txt](config/egress-allowlist.txt)** — which domains the\n  hive may reach. Ships with Anthropic, npm, PyPI, GitHub, and Debian.\n  `hive bridge logs` shows what's being refused when something fails.\n- **[forwards.conf](config/forwards.conf)** — which Mac services the hive may\n  reach. One line per opening, e.g. `postgres 15432 host.docker.internal:5432`;\n  nodes then connect to `bridge:15432`. Nothing is forwarded by default.\n\n`hive bridge conf` prints the generated proxy config and active listeners.\n\n## Per-node switches: internet \u0026 GitHub\n\nEach node has two live switches. They are applied **at the bridge**, not inside\nthe node — so they take effect on the node's very next request, instantly, even\nin a shell that was already open (the node's environment never changes). Flip\nthem from the Mac or from inside `root`:\n\n```sh\nhive \u003cnode\u003e net on        # full, unfiltered internet for this node\nhive \u003cnode\u003e net off       # back to sealed (allowlist only)\nhive \u003cnode\u003e github on     # give this node your GitHub token\nhive \u003cnode\u003e github off    # remove it\nhive \u003cnode\u003e status        # show both\n```\n\n**`net on`** adds the node's source IP to the bridge's \"open\" set in Squid and\nreconfigures it live (`squid -k reconfigure`), so that node bypasses the\nallowlist while every other node stays sealed. **`net off`** removes it. Because\nthe node still reaches the world only through the bridge, nothing about the node\nchanges — there is no proxy to go stale, no reconnect, no recreate. The open\nset is remembered in `config/open-nodes` so it survives a bridge restart.\n\nIf instead you want to open *one domain for the whole hive while keeping every\nnode sealed*, add it to [egress-allowlist.txt](config/egress-allowlist.txt) and\n`hive bridge reload`. `hive bridge logs` shows each request as `TCP_DENIED` or\n`TCP_TUNNEL/200`, so you can see exactly what was refused.\n\n**`github on` does the login for you — no token to paste.** The first time you\nrun it, hive starts GitHub's device login (using `gh` inside `root`, which\nreaches GitHub through the bridge), prints a URL and a one-time code, and waits\nwhile you approve in your browser. It then caches the resulting token at\n`~/.config/hive/github-token` (chmod 600, outside the repo) and installs it in\nthe node — git's credential helper for `git clone https://github.com/...` and\nthe `gh` CLI for `gh pr` / `gh repo clone`. Every later `hive \u003cnode\u003e github on`\nis silent (the cached token is reused). `hive \u003cnode\u003e github off` deletes the\ntoken from that node; `hive github --forget` drops the cached copy. No allowlist\nchange is needed (`github.com` is already permitted), and the token never enters\nan image layer.\n\nGitHub won't mint a token without you authenticating once — that single browser\napproval is unavoidable. After it, hive handles everything. (For scripts, a\ntoken piped in — `echo \"$T\" | hive \u003cnode\u003e github on` — skips the login. You can\nalso pre-seed one with `hive github \u003ctoken\u003e`.)\n\nPrefer SSH keys? Plain `git@github.com` needs port 22, which the bridge doesn't\ntunnel. Use GitHub's SSH-over-443 endpoint: add `ssh.github.com` to the\nallowlist (CONNECT on 443 is already permitted), put your key in the node, and\nuse remotes like `ssh://git@ssh.github.com:443/owner/repo.git`.\n\n\u003e Full internet + published ports from creation: `hive new web --uplink -- -p 3000:3000`.\n\n## Running commands on the Mac from a node (opt-in, live)\n\nFor workflows where a node should drive your Mac — e.g. a node that develops\nhive itself, then asks the Mac to redeploy the running hive — there's a\ndeliberate, opt-in channel:\n\n```sh\nhive hostd start            # Mac: start the daemon + a bridge forward\nhive \u003cnode\u003e host on         # bless a node (live — injects a secret token)\n# then, inside that node:\nhost \"cd ~/hive-containers \u0026\u0026 git pull \u0026\u0026 hive build \u0026\u0026 hive up\"\n```\n\nIt's a **live, per-node switch** like `net`/`github` — `host on`/`off` inject or\nremove the token in the running container, no recreate. `hive new x --host` is\njust sugar for new + host on.\n\nHow it works: `host \u003ccmd\u003e` POSTs the command (with the node's private token) to\nthe Mac daemon via `bridge:8765`; the daemon runs it **as you** and returns the\noutput. The daemon binds **loopback only** (`127.0.0.1`) — Colima's host gateway\nforwards arrive there — so the command port is never exposed on your LAN, only\nthrough the bridge. Every command is logged (`hive hostd logs`). Default working\ndirectory is your hive checkout, so deploy one-liners are short.\n\n\u003e ⚠️ **This is real remote code execution on your Mac.** A `host on` node can run\n\u003e *anything* as your user — combined with `github on`, a compromised node could\n\u003e push code and have your Mac run it. It is **off by default**, gated by a secret\n\u003e token that lives only inside blessed nodes, and bound to loopback — but treat\n\u003e any `host on` node as fully trusted, and `host off` it when you're done.\n\n## Develop hive inside hive (nested Docker)\n\nTo work on container tooling — hive itself, say — inside a node, give it a real\nnested Docker engine:\n\n```sh\nhive new hivedev --bind ~/hive-containers --dind   # a node with its own dockerd\nhive hivedev sh 'docker run --rm hello-world'      # runs in the nested engine\n```\n\n`--dind` uses the `hive/dev` image (Claude tools + a full Docker engine), runs\nthe node `--privileged`, and starts `dockerd` inside it with `/var/lib/docker`\non a dedicated volume so `overlay2` works (no overlay-on-overlay). The nested\nengine pulls through the bridge, so `hive hivedev net on` first if it needs\nimages from outside the allowlist.\n\nLike the other switches it's also a live per-node toggle — but because\n`--privileged` is a creation-time capability, `hive \u003cnode\u003e dind on/off`\n**recreates** the node (transparently preserving its workspace and its\nnet/github/host state). This is the one switch that recreates rather than\nflips in place. It's `--privileged` under runc today; under Kata the micro-VM\nprovides the isolation and the same setup applies unchanged.\n\nThe intended loop: develop hive in the node → `git push` → `host \"cd\n~/hive-containers \u0026\u0026 git pull \u0026\u0026 hive build \u0026\u0026 hive up\"` to redeploy the hive\nrunning on your Mac.\n\n## Claude desktop app\n\nThe desktop app's SSH sessions run entirely inside a node while the app is\nyour interface — file pane, terminal-free permission modes, the lot.\n\n```sh\nhive ssh setup     # once: dedicated key + ~/.ssh/config block + key install\n```\n\nThen in the app: **Code tab → environment dropdown → + Add SSH connection**,\nhost `hive-\u003cnode\u003e` (e.g. `hive-root`), port and identity file empty. Every\nnode you `hive new` afterwards is immediately connectable the same way.\n\nHow it stays sealed: there is no sshd listening anywhere. The `~/.ssh/config`\nblock uses `ProxyCommand docker exec` to spawn `sshd -i` on the connection's\nstdio, so SSH rides the Docker socket — nodes gain no open port, nothing on\nthe hive network changes, and only this Mac can connect. The Docker context\nis baked into the config at setup time, so it keeps working when another\nengine (e.g. colima) holds the default context.\n\n## Two Docker engines (Docker Desktop + colima)\n\nHive lives on one engine — its seal is a Docker network, which can't span\nVMs. Which engine is pinned in [config/docker-context](config/docker-context)\n(currently `desktop-linux`); every `hive` command targets it no matter which\ncontext your shell has active, so `colima start` stealing the default context\nnever breaks or relocates the hive. Plain `docker` keeps following your\nactive context for other projects.\n\nTo let hive nodes reach a service from a colima project: publish its port to\nthe Mac as usual (`-p 5432:5432`), then curate it like any Mac service —\n`colima-pg 15432 host.docker.internal:5432` in forwards.conf. To migrate hive\nitself to another engine: change `config/docker-context`, then\n`hive build \u0026\u0026 hive up \u0026\u0026 hive ssh setup`.\n\n## Devcontainers / IDEs\n\n- `hive devcontainer ~/code/myproject` drops a `.devcontainer/` that joins the\n  project to the hive as a labeled node — then \"Reopen in Container\".\n- Or attach VS Code to any existing node: \"Dev Containers: Attach to Running\n  Container\" → `hive-\u003cname\u003e`.\n- `hive join \u003ccontainer\u003e` connects a container you created some other way to\n  `hive-net` (it appears under *guests* in `hive ls`).\n\n## What's in a node\n\nEvery node (and `root`) is built from `hive/node`, which ships Claude Code, the\nGitHub CLI, Node and Python, and a broad toolset so the agent rarely has to\ninstall anything:\n\n- **network suite** — `nmap`, `tcpdump`, `dig`/`nslookup`, `mtr`, `traceroute`,\n  `nc`/`ncat`, `socat`, `whois`, `iperf3`, `ngrep`, `arp-scan`, `iproute2`,\n  `nftables`/`iptables`, `ethtool`, `openssl`\n- **datastore clients** — `psql`, `mysql`, `redis-cli`, `sqlite3`\n- **build \u0026 dev** — `build-essential`/`gcc`/`make`, `pkg-config`, `git`, `tmux`,\n  `jq`, `ripgrep`, `fd`, `bat`, `httpie`, `htop`, `strace`, `shellcheck`, `rsync`\n\nAdd more in [images/node/Dockerfile](images/node/Dockerfile) and `hive build`.\n\n## Escape hatches\n\n| Need | Do |\n|---|---|\n| Full internet for one node, instantly | `hive \u003cnode\u003e net on` |\n| Direct internet at creation + working `-p` published ports | `hive new x --uplink -- -p 3000:3000` |\n| Extra docker flags (env, ports, gpus…) | everything after `--` goes to `docker run` |\n| A different base image | `hive new x --image myimage` (any image works; Claude tooling comes from `hive/node`) |\n| Open egress entirely | put a single `*` line in the allowlist (deliberate act, on purpose) |\n| New tools in the base image | edit [images/node/Dockerfile](images/node/Dockerfile), `hive build` |\n\n## Prior art \u0026 how hive differs\n\nSandboxing AI coding agents is a well-trodden, active area — the building\nblocks here are not new:\n\n- Anthropic ships a reference **devcontainer firewall**: `iptables` default-DROP\n  plus an `ipset` domain allowlist, per container, which is what makes\n  `--dangerously-skip-permissions` safe.\n- **`@anthropic-ai/sandbox-runtime`** wraps the Claude Code process in\n  Seatbelt/bubblewrap with filesystem + network allowlists (host process, not a\n  container).\n- **Docker AI Sandboxes** route all egress through a host proxy that enforces a\n  network policy (and inject credentials).\n- **agent-sandbox** (mattolson) forces traffic through a sidecar proxy with\n  per-hostname/path egress rules.\n- Conceptually the closest is **Qubes OS**, where AppVMs reach the network only\n  through a shared `sys-net`/`sys-firewall` VM.\n\nSo the egress-allowlist idea is mainstream. What hive assembles — and what I\nhaven't found packaged together — is:\n\n1. a **hierarchy** of agent containers: a `root` control plane that holds the\n   Docker socket and orchestrates an arbitrary-depth tree of nodes;\n2. a **single shared curated bridge** that does *both* egress filtering and\n   host-port forwarding — one auditable crossing point for the whole fleet,\n   rather than a firewall baked into each container;\n3. a **per-node, live egress toggle** (`hive \u003cnode\u003e net on/off`) via Squid\n   source ACLs — instant, with no change to the node or its environment;\n4. **desktop-app / IDE access over socket-tunnelled SSH**, with no open ports;\n5. one CLI tying it together, each container told *where it is* via a managed\n   `CLAUDE.md`.\n\nIn one line: the allowlist is mainstream; hive's contribution is the *shared\nbridge + hierarchy + per-node instant toggle + host forwards + desktop SSH* as\none small, auditable system — roughly \"Anthropic's firewall devcontainer meets\na Qubes-style network VM.\"\n\n## Security model, honestly\n\n- The seal on nodes is the internal network — in-container root/sudo doesn't\n  break it, which is why nodes get passwordless sudo for convenience.\n- The egress allowlist is domain-level filtering, not DPI; an allowlisted\n  domain (e.g. github.com) is still a data channel.\n- `hive \u003cnode\u003e net on` opens *that one node* to the whole internet (Squid keys\n  off its source IP); other nodes stay sealed. It's per-node, instant, and\n  reversible, but while on, that node has no egress restriction at all.\n- `root` + the Docker socket = control of the Docker VM and every container.\n  Treat `root` as trusted infrastructure: it's your orchestrator, not a\n  sandbox for untrusted work — untrusted work belongs in nodes.\n- Claude credentials are shared via the `hive-claude` volume: log in once,\n  every node is authenticated. Delete the volume to revoke.\n\n## Layout\n\n```\nbin/hive                    the CLI (symlinked into root via /workspace)\ncompose.yaml                root + bridge + networks + volumes\nconfig/                     ← the two files you curate\nimages/{node,root,bridge,dev}/  Dockerfiles + entrypoints (dev = --dind)\ntemplates/devcontainer/     what `hive devcontainer` installs\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xorial%2Fhive-containers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xorial%2Fhive-containers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xorial%2Fhive-containers/lists"}