Skip to content

Repository files navigation

  ██████╗ ██╗    ██╗   ██╗ ██████╗██╗  ██╗██╗  ██╗ ██████╗ ██╗     ███████╗
  ██╔══██╗██║    ██║   ██║██╔════╝██║ ██╔╝██║  ██║██╔═══██╗██║     ██╔════╝
  ██████╔╝██║    ██║   ██║██║     █████╔╝ ███████║██║   ██║██║     █████╗
  ██╔══██╗██║    ╚██╗ ██╔╝██║     ██╔═██╗ ██╔══██║██║   ██║██║     ██╔══╝
  ██████╔╝███████╗╚████╔╝ ╚██████╗██║  ██╗██║  ██║╚██████╔╝███████╗███████╗
  ╚═════╝ ╚══════╝ ╚═══╝   ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚══════╝

Declarative Docker Sandbox manager. One YAML config, one command — sandbox is up with your runtimes, packages, and AI agent.

Sandboxes isolate your development environment from your host. Compromised dependencies can't touch your filesystem or phone home unless you explicitly allowlist the domain. Good default against supply chain attacks.

blvckhole init       # scaffold config
blvckhole start      # build image, create sandbox
blvckhole agent      # launch AI agent inside

Install

Requires Docker Desktop with Docker Sandboxes (sbx CLI).

Download a pre-built binary from GitHub Releases, extract it, and put it somewhere in your PATH.

Or build from source (needs Go 1.26+):

git clone https://github.com/neoighodaro/blvckhole.git
cd blvckhole
go build -o blvckhole .

./blvckhole install                          # copies to ~/.local/bin/
./blvckhole install -d /usr/local/bin        # custom directory
./blvckhole install -s                       # symlink instead of copy (picks up rebuilds)
./blvckhole install -s -d /usr/local/bin     # both

Usage

blvckhole init                # scaffold config
blvckhole start               # build + launch
blvckhole ssh                 # open a shell
blvckhole run "pnpm install"  # execute a command in the sandbox
blvckhole agent               # launch the agent (starts sandbox if needed)
blvckhole agent --rebuild     # force image rebuild first
blvckhole stop                # stop (state is kept)
blvckhole restart             # tear down + recreate
blvckhole status              # show sandbox state, ports, runtimes

blvckhole network allow elevenlabs.io            # allow a domain on the running sandbox
blvckhole network allow elevenlabs.io --persist  # ...and save it to blvckhole.yaml
blvckhole network deny ads.example.com           # block a domain (runtime-only)
blvckhole network remove elevenlabs.io --persist # remove the rule and drop it from config

blvckhole handoff                # run the cross-sandbox handoff broker (foreground)
blvckhole handoff -D             # ...in the background; blvckhole handoff -K to stop it

Config

blvckhole init creates .config/blvckhole/blvckhole.yaml. Also discovered at blvckhole.yaml in the project root.

name: my-project          # default: directory name
agent: claude-code         # or codex, copilot, cursor, docker-agent, droid, gemini, kiro, opencode, shell

# Skip Dockerfile generation — use your own image instead
# template: docker.io/my-org/custom-template:v1

packages:                  # apt-get
  - ripgrep
  - lazygit
  - eza

runtimes:
  node: "24"               # major version (NodeSource) or exact: "24.16.0"
  pnpm: "11.1.3"           # needs node
  # bun: "latest"
  # python: "3.12"
  # go: "1.23"
  # php: "8.4"
  # rust: "stable"

php:                       # additional extensions (added to defaults)
  extensions:
    - sqlite3
    - imagick

ports:
  - 3000
  - "8080:80"

env:
  NODE_ENV: development

env_file:                  # loaded in order, later wins
  - .env
  - .env.sandbox

scripts:
  on_create:               # run once, right after the sandbox is created
    - "composer install"
  on_start:                # run on every container start (survives stop/resume)
    - "./scripts/db-bridge.sh"   # bridges, daemons — things a restart would wipe

shell:
  directory: /home/agent/project/src
  aliases:
    g: "git"
    dc: "docker compose"

network:                   # allowlist — when set, only these domains are reachable
  - "*.npmjs.org"
  - "registry.yarnpkg.com"
  - "api.github.com"

handoff:                   # cross-sandbox question/answer broker
  enabled: true
  # url: http://host.docker.internal:8787   # default

claude:                    # Claude Code-specific
  theme: ./my-theme.json   # Catppuccin Mocha by default
  plugins:
    marketplaces:
      - anthropics/claude-plugins-official
    install:
      - superpowers@claude-plugins-official
  settings:
    alwaysThinkingEnabled: true

zellij:                    # Zellij terminal multiplexer
  display_name: My Project # tab name when launching the agent

memory: |                  # injected into the agent's CLAUDE.md
  Project-specific instructions here.

Shell

Sandboxes use bash with a colored prompt, git branch display, and persistent history. Aliases for bat, eza, lazygit, and please (sudo last command) are available when those tools are installed. Add your own via shell.aliases.

Scripts

scripts.on_create runs once, right after the sandbox is created (migrations, dependency installs) — run via blvckhole start. scripts.on_start runs at the start of every shell and agent session — including after a stop/resume — so use it for state that a restart wipes: port bridges, background daemons, socat tunnels. on_start commands are baked into the sandbox's per-session init hook (/etc/sandbox-persistent.sh) at build time, run in a subshell with output suppressed, so they must be idempotent. (on_start requires a generated Dockerfile; it has no effect with a custom template.)

The old top-level startup: key is deprecated. It still works (it now aliases scripts.on_create) but prints a warning — migrate to scripts.on_create.

Skills

If ~/.claude/skills exists on the host (or is a symlink), its contents are automatically copied into the sandbox at build time. No configuration needed.

Handoff (cross-sandbox)

Let agents in different sandboxes ask each other threaded questions. A small broker runs on the host; sandboxes reach it over host.docker.internal.

Run the broker on the host:

blvckhole handoff             # foreground (stops when you close the terminal)
blvckhole handoff -D          # run in the background (daemon)
blvckhole handoff -K          # stop a background broker
blvckhole handoff -P 9000     # custom port (default 8787)

Then enable it per project so the sandbox can reach the broker:

handoff:
  enabled: true
  # url: http://host.docker.internal:8787   # default

When enabled, blvckhole start injects $BLVCKHOLE_SANDBOX (this sandbox's name) and $BLVCKHOLE_HANDOFF_URL into the sandbox, allowlists the broker port, and auto-installs the sandbox-handoff skill. Agents use it to open threads, post answers, and long-poll for incoming questions — GET /handoff/threads?...&wait=<seconds> blocks until one arrives (capped at 5 min) instead of busy-polling. A live web board for watching threads is served at the broker URL (/handoff).

Agents

Supported: claude-code, codex, copilot, cursor, docker-agent, droid, gemini, kiro, opencode, shell

Runtimes

Runtime Version format Notes
node "24" or "24.16.0" Major version uses NodeSource, exact downloads from nodejs.org
pnpm "11.1.3" Requires node
bun "latest" or "1.2.0"
python "3.12"
go "1.23"
php "8.4"
rust "stable" or "1.80"

Contributing

git clone https://github.com/neoighodaro/blvckhole.git
cd blvckhole
go build ./...
go test ./...

Structure

cmd/                     CLI commands (cobra)
internal/
  config/                YAML config parsing and validation
  embedded/              Embedded assets (Dockerfile template, bashrc, theme)
  envfile/               .env file parser
  kit/                   Kit generator (spec.yaml + files for sbx)
  runtime/               Runtime installers (node, pnpm, bun, python, go, php, rust)
  sandbox/               Wrapper around the sbx CLI
  template/              Dockerfile template renderer and builder
  ui/                    Terminal styling (lipgloss)

Adding a Runtime

  1. Create internal/runtime/<name>.go implementing:
type Runtime interface {
    Name() string
    Validate(version string) error
    RootBlock(version string) string   // Dockerfile commands as root
    AgentBlock(version string) string  // Dockerfile commands as agent user
    EnvBlock(version string) string    // ENV directives
}
  1. Register in internal/runtime/runtime.go
  2. Add to validRuntimes in internal/config/config.go
  3. Add a commented example in the starter config in cmd/init.go

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages