gdkbox

Spin up a GitLab Development Kit (GDK) "in a box" with a single command, then connect to it from VS Code and run Claude Code agents against a real GitLab development environment.

gdkbox runs GitLab's official GDK image in a Docker container, enables SSH so VS Code Remote-SSH can attach, and installs Claude Code inside the box for you.

gdkbox up myenv         # pull GDK image, start container, wire SSH + Claude Code
gdkbox code myenv       # open the box in VS Code (Remote-SSH)
gdkbox ssh myenv        # drop into a shell inside the box
gdkbox ls               # list your boxes and their status

How it works

            gdkbox up demo
                  │
   ┌──────────────┼─────────────────────────────────────────────┐
   │              ▼                                               │
   │   1. docker pull  <official GDK image>                       │
   │   2. docker run   -p 127.0.0.1:2222:22  -p 127.0.0.1:3000:3000
   │   3. provision    install + start sshd, authorize gdkbox key │
   │   4. provision    npm install -g @anthropic-ai/claude-code   │
   │   5. register     Host gdkbox-demo  ->  ~/.gdkbox/ssh_config  │
   └──────────────────────────────────────────────────────────────┘
                  │
      ┌───────────┴────────────┐
      ▼                        ▼
  VS Code Remote-SSH       ssh gdkbox-demo -t claude
  (ssh-remote+gdkbox-demo)  (run agents in the box)
  • Infra: a Docker container per box, ports published to 127.0.0.1.
  • Image: GitLab's official GDK-in-a-box image (overridable).
  • Editor: VS Code Remote-SSH, via a generated ~/.gdkbox/ssh_config that is Included from your ~/.ssh/config.
  • Agents: Claude Code is installed inside each box.

Requirements

  • Ruby >= 3.0
  • Docker installed and running
  • ssh and ssh-keygen (standard on macOS/Linux)
  • VS Code with the Remote - SSH extension, and the code command on your PATH (optional, only for gdkbox code)

Install

From this directory:

gem build gdkbox.gemspec
gem install ./gdkbox-0.1.0.gem

Or run it straight from a checkout without installing:

./bin/gdkbox up myenv

Commands

Command Description
gdkbox up NAME Pull the GDK image, start a box, enable SSH, install Claude Code, register VS Code host.
gdkbox ls List boxes and their container status (--json for orchestrators).
gdkbox status NAME Show container state and connection details (--json).
gdkbox dispatch NAME Run a Claude Code agent task headlessly in the box (--task/--task-file).
gdkbox code NAME Open the box in VS Code via Remote-SSH.
gdkbox ssh NAME Open an interactive SSH session into the box.
gdkbox claude NAME Install Claude Code inside the box.
gdkbox set-key NAME Seed/rotate the Anthropic API key in the box for unattended dispatch.
gdkbox start NAME Start a stopped box (and re-enable SSH).
gdkbox stop NAME Stop a running box.
gdkbox rm NAME Remove a box: container, metadata, and SSH entry.
gdkbox install-skill [NAME] Install the agent skill(s) bundled in this repo into your host Claude Code (--project, --force).
gdkbox skills List agent skills available to install (bundled + ~/.claude/skills + ./.claude/skills).
gdkbox add-skill BOX [SKILL...] Install skill(s) into a box so dispatched agents can use them; interactive picker if no SKILL (--force).
gdkbox completion SHELL Print a bash or zsh completion script (completes subcommands, flags, live box and skill names).
gdkbox harnesses List the agent harnesses gdkbox can install (claude, codex, opencode, pi).
gdkbox install-agent NAME (Re)install the box's agent harness inside it.

gdkbox up options

Option Default Description
--image official GDK image Override the container image.
--ssh-port next free from 2222 Host port to publish SSH on.
--web-port next free from 3000 Host port to publish the GDK web UI on.
--harness ID claude (or config.yml) Agent harness to install: claude, codex, opencode, pi.
--no-agent (agent installed) Skip installing the agent harness.
--skill NAME [NAME...] (none) Seed skill(s) into the box at creation for dispatched agents.

Typical workflow

# 1. Create a box (first run pulls a large image, so give it a few minutes)
gdkbox up demo

# 2. Edit the code with VS Code Remote-SSH
gdkbox code demo

# 3. Inside the box, start GDK and run an agent
gdkbox ssh demo
#   gdk start          # boot the GitLab services
#   claude             # launch a Claude Code agent against the repo

# 4. The GitLab web UI is published locally
open http://127.0.0.1:3000

# 5. Stop or remove the box when you're done
gdkbox stop demo
gdkbox rm demo

Orchestrating a fleet of agents

The end goal of gdkbox is to back an orchestrator that runs a pool of reusable boxes and dispatches a Claude Code agent into each to execute tasks in parallel. Two primitives make this possible:

  • Headless agent runs: gdkbox dispatch NAME --task "..." runs claude -p non-interactively inside the box and streams the agent's output. Exit status mirrors the agent's. Add --json for structured output and --timeout N to bound a run.
  • Machine-readable state: gdkbox ls --json / gdkbox status NAME --json emit JSON descriptors so an orchestrator can see which boxes exist and their state.

For unattended dispatch the agents need Anthropic credentials. Seed an API key into each box (kept only inside the container, in a 0600 file owned by the GDK user — never in host-side metadata). gdkbox up and gdkbox set-key both default to the ANTHROPIC_API_KEY environment variable:

export ANTHROPIC_API_KEY=sk-ant-...

# Warm a pool of 3 boxes (in parallel; first run pulls a large image).
# The key is seeded automatically because ANTHROPIC_API_KEY is set.
for i in 1 2 3; do gdkbox up "pool-$i" --json & done; wait

# Or seed/rotate the key on existing boxes:
gdkbox set-key pool-1

# Fan three tasks out, one per box. Agents authenticate with the seeded key.
gdkbox dispatch pool-1 --task "Run the test suite and fix the first failure" --json &
gdkbox dispatch pool-2 --task "Update the README install section" --json &
gdkbox dispatch pool-3 --task "Add a changelog entry" --json &
wait

gdkbox ls --json reports "api_key_set": true|false per box so an orchestrator can tell which boxes are ready for unattended work. Prefer the ANTHROPIC_API_KEY env var over --anthropic-api-key, which can leak into shell history.

In this repo the orchestrator is meant to be another Claude Code session, guided by the bundled gdkbox-fleet skill at skills/gdkbox-fleet/SKILL.md, which documents the pool model, dispatch loop, state-reset-between-tasks caveat, and guardrails.

Installing the skill

The skills shipped with this repo live in the top-level skills/ directory. Install them into Claude Code with:

# Install every bundled skill into ~/.claude/skills (available everywhere)
gdkbox install-skill

# Install one skill, into the current project only
gdkbox install-skill gdkbox-fleet --project

# Re-install over an existing copy
gdkbox install-skill gdkbox-fleet --force

This copies each skill verbatim into ~/.claude/skills/<name> (or ./.claude/skills/<name> with --project). Start a new Claude Code session afterwards to pick it up.

Giving skills to agents in a box

install-skill targets your host Claude Code. Agents dispatched into a box (gdkbox dispatch) run inside the container and read the box's own ~/.claude/skills, so they need the skill installed there. Skills can come from this repo (skills/), your host (~/.claude/skills), your project (./.claude/skills), or any directory path.

# See everything gdkbox can install (bundled + host + project), with summaries
gdkbox skills

# Install skill(s) into a running box; agents dispatched there can then use them
gdkbox add-skill pool-1 gdkbox-fleet
gdkbox add-skill pool-1 ./path/to/my-skill --force

# No skill given → list and pick interactively
gdkbox add-skill pool-1

# Or seed skills when the box is first created
gdkbox up pool-1 --skill gdkbox-fleet --skill code-history

Each skill is copied to ~/.claude/skills/<name> inside the box and owned by the GDK user, so a dispatched claude agent discovers it automatically.

Agent harnesses

Each box runs one agent harness, chosen at gdkbox up --harness (default claude, or the harness: in config.yml). gdkbox dispatch then runs whatever harness the box was created with.

Harness CLI Headless dispatch Default key env Skills dir (in box)
claude Claude Code claude -p ANTHROPIC_API_KEY ~/.claude/skills
codex OpenAI Codex codex exec OPENAI_API_KEY ~/.codex/skills
opencode opencode (sst) opencode run OPENAI_API_KEY ~/.config/opencode/skills
pi pi (earendil-works) pi -p ANTHROPIC_API_KEY ~/.pi/skills
gdkbox up reviewer --harness codex          # a Codex box
gdkbox set-key reviewer                      # seeds $OPENAI_API_KEY
gdkbox add-skill reviewer my-skill           # installs into ~/.codex/skills
gdkbox dispatch reviewer --task "review the diff"
gdkbox harnesses                             # list available harnesses

Skills use the same SKILL.md format across all four, so a skill installed by add-skill/--skill works regardless of harness. The key env / provider for opencode and pi are configurable defaults — adjust per your provider.

Shell completion

gdkbox completion <shell> prints a completion script for bash or zsh. It completes subcommands and flags, and — dynamically, by calling back into gdkbox — live box names (for ssh, dispatch, rm, …) and skill names (for add-skill, install-skill, and --skill).

# bash — add to ~/.bashrc
eval "$(gdkbox completion bash)"

# zsh — add to ~/.zshrc (after `compinit` has run)
eval "$(gdkbox completion zsh)"

Or write it to your completions directory, e.g. gdkbox completion bash > $(brew --prefix)/etc/bash_completion.d/gdkbox.

Configuration

Environment variable Purpose
GDKBOX_HOME Where gdkbox stores keys, box metadata, and the SSH config (default ~/.gdkbox).
GDKBOX_IMAGE Default image used by gdkbox up when --image is not given.

config.yml

Defaults for gdkbox up can be set in ~/.gdkbox/config.yml (or $GDKBOX_HOME/config.yml). Most usefully, list skills to transfer into every new box so dispatched agents always have them:

# ~/.gdkbox/config.yml

# Default image for `gdkbox up` (overridden by --image or $GDKBOX_IMAGE).
# image: registry.gitlab.com/gitlab-org/gitlab-development-kit/gitlab-gdk-in-a-box:main

# Default agent harness for `gdkbox up` (overridden by --harness).
# harness: claude

# Skills transferred into every new box. Each entry is a discovered skill
# name (see `gdkbox skills`) or a path to a skill directory.
skills:
  - gdkbox-fleet
  # - code-history
  # - ./path/to/a/local/skill

# API keys seeded into boxes for unattended `gdkbox dispatch`, per provider.
# anthropic_api_key: sk-ant-...   # used by the claude / pi harnesses
# openai_api_key: sk-...          # used by the codex / opencode harnesses

gdkbox up installs the skills on top of any --skill you pass; use --no-default-skills to skip them for one run. Precedence for the image is --image > $GDKBOX_IMAGE > config.yml > built-in default; for the API key it is --api-key > the harness's provider env var (e.g. $ANTHROPIC_API_KEY, $OPENAI_API_KEY) > config.yml.

API keys in config.yml are plaintext secrets on disk. Prefer the provider env var; if you do put a key in config.yml, keep the file mode 600 (chmod 600 ~/.gdkbox/config.yml).

State on disk:

~/.gdkbox/
├── config.yml            # optional; defaults for `gdkbox up`
├── boxes/<name>.json     # metadata for each box
├── keys/id_ed25519(.pub) # dedicated keypair authorized into every box
└── ssh_config            # generated; Included from ~/.ssh/config

gdkbox never touches your personal SSH keys: it creates and uses its own id_ed25519 under ~/.gdkbox/keys.

Development

bundle install
bundle exec rspec        # or: ruby -e "require 'rspec/core'; RSpec::Core::Runner.run(['spec'])"

The Docker, SSH, and filesystem boundaries are isolated behind small wrapper classes (Docker, Shell, Store, SSHConfig, SSHKey, Provisioner, VSCode), so the orchestration logic in Box is fully unit-tested without needing a running Docker daemon.

Notes & limitations

  • Ports are published to 127.0.0.1 only, so boxes are reachable from your machine but not the wider network.
  • Processes started via docker exec (like sshd) do not survive a container restart, so gdkbox start re-runs the SSH provisioning step to bring it back.
  • Claude Code is installed in the box but still needs your Anthropic credentials at runtime — sign in the first time you run claude inside it.