npm package: @pablozaiden/devbox
Description: CLI to run and expose a devcontainer with SSH agent sharing and a forwarded ssh-server-runner port.
Latest version: 0.1.27

# devbox

`devbox` is a CLI that turns the devcontainer definition in the current directory into a repeatable "start my workspace and expose an SSH entrypoint" workflow.

It does not modify the original `devcontainer.json`. Instead, it generates a derived config next to it, ignores that file locally when possible, and manages the resulting container with stable labels.

## What it does

- Discovers `.devcontainer/devcontainer.json` or `.devcontainer.json` in the current directory, and can target `.devcontainer/<subpath>/devcontainer.json` with a flag.
- Reuses or creates the devcontainer with Docker + Dev Container CLI.
- Names the managed container as `devbox-<project>-<port>`.
- Publishes the same TCP port on host and container.
- Mounts the current directory into the container as the workspace.
- Shares a usable SSH agent socket with the container and copies a validated, non-empty `known_hosts` snapshot into the container.
- Exposes the SSH service on the chosen host port and, when a host public key is available, installs it for key-based SSH login inside the devcontainer.
- Seeds the container user's global Git `user.name` and `user.email` from the host when available.
- Runs the [`ssh-server-runner`](https://github.com/PabloZaiden/ssh-server-runner) one-liner inside the devcontainer.
- Persists the runner password on the mounted workspace as a local `.sshcred` file, stores devbox-owned SSH metadata in `.devbox-ssh.json`, and keeps SSH host keys in `.devbox-ssh-host-keys/`, so they survive `down` / `rebuild`.

## Installation

Install globally with Bun:

```bash
bun install -g @pablozaiden/devbox
```

Or install globally with npm:

```bash
npm install -g @pablozaiden/devbox
```

After either install, `devbox` is available in any directory.

Run `devbox` with no arguments to see the CLI help.

## Requirements

- macOS or Linux
- [Node.js](https://nodejs.org/) or [Bun](https://bun.sh/) to run the installed CLI
- Docker
- Dev Container CLI available as `devcontainer` in whatever environment runs `devbox`
- For SSH agent sharing: either a valid host `SSH_AUTH_SOCK`, or Docker Desktop host services
- A devcontainer using `image` or `Dockerfile`

`dockerComposeFile`-based devcontainers are intentionally out of scope for v1.

## Commands

```bash
# Show CLI help
devbox

# Start or reuse the devcontainer on a chosen port
devbox up <port>

# Start or reuse the devcontainer, reusing the stored workspace port when available
# or auto-assigning the first free port from 5001 when none has been stored yet
devbox up

# Continue even if SSH agent sharing is unavailable
devbox up <port> --allow-missing-ssh

# Override the public key installed for SSH login inside the container
devbox up <port> --ssh-public-key ~/.ssh/work-devbox.pub

# Use a specific devcontainer under .devcontainer/services/api
devbox up <port> --devcontainer-subpath services/api

# Rebuild/recreate the managed devcontainer
devbox rebuild <port>

# Open an interactive shell in the running managed devcontainer for this workspace
devbox shell

# Print machine-readable JSON describing the managed devbox for this workspace
devbox status

# Find stopped managed devbox containers and rerun `devbox up` for each recoverable workspace
devbox arise

# Stop and remove the managed container while preserving the workspace-mounted SSH password, metadata, and host keys
devbox down
```

When you run `devbox up`, the port precedence is:

1. the explicit port you passed,
2. the last stored port for the current workspace, or
3. the first free port starting at `5001`.

When you run `devbox rebuild`, omitting the port reuses the last stored port for the current workspace.

`devbox shell` requires an already running managed container for the current workspace. If none is running, use `devbox up` first.

`devbox status` always prints JSON so it can be used directly from scripts and automation.

`devbox arise` is a global recovery command. It scans existing stopped devbox-managed containers, recovers each host workspace from the bind mount targeting `/workspaces/...`, checks that the workspace still has devbox leftovers from a previous `up` run, and then reruns `devbox up` for that workspace. It logs each discovery, skip, restart, and failure, and continues with the remaining workspaces if one restart fails.

Example:

```json
{
  "running": true,
  "port": 5001,
  "password": "password",
  "workdir": "/workspaces/my-project",
  "workspacePath": "/host/path/to/my-project",
  "containerId": "abcdef123456",
  "containerName": "devbox-my-project-5001",
  "containerState": "running",
  "containerCount": 1,
  "sshUser": "root",
  "sshPort": 5001,
  "remoteUser": "vscode",
  "hasStateFile": true,
  "hasCredentialFile": true,
  "hasSshMetadataFile": true,
  "warnings": []
}
```

The full payload also includes useful diagnostic fields such as `workspaceHash`, `labels`, `publishedPorts`, `statePath`, `credentialPath`, `sshMetadataPath`, `updatedAt`, and the stored/generated config paths.

When available, the status payload also includes `publicKeyConfigured` and `publicKeySource` so automation can tell whether devbox installed a host SSH public key for key-based login.

## Development

The root project devcontainer installs a pinned `@devcontainers/cli` with Bun during container setup and exposes Bun's bin directory on `PATH`, so if you develop `devbox` inside this repository's devcontainer you do not need to install the Dev Containers CLI separately inside the container.

If you run the project directly on the host instead of inside the repository devcontainer, you still need Docker plus a host `devcontainer` binary available on `PATH`.

```bash
bun install
bun test
bun run test:fast
bun run build
```

Pull requests run the same test-and-build checks automatically through GitHub Actions.

`bun test` now includes the real example-workspace devcontainer integration tests by default, so it requires Docker and the Dev Container CLI to be available in the environment where the tests run.

Use `bun test tests/examples.live.test.ts` to run only the live example-devcontainer suite.

Use `bun run test:fast` if you want the non-live suite only, or run `DEVBOX_SKIP_LIVE_EXAMPLE_TESTS=1 bun test` directly.

The build step emits a bundled executable JS entrypoint at `dist/devbox.js`.

For local development from this repository:

- use `bun run src/cli.ts` while iterating on source changes
- use `./dist/devbox.js` after `bun run build` to exercise the packaged artifact

For a quick smoke test, this repository includes `examples/smoke-workspace/.devcontainer/devcontainer.json`, which also exercises the Docker-in-Docker path:

```bash
cd examples/smoke-workspace
../../dist/devbox.js up --allow-missing-ssh
```

For a more realistic feature-heavy example, this repository also includes `examples/complex-workspace/.devcontainer/devcontainer.json`:

```bash
cd examples/complex-workspace
../../dist/devbox.js up
```

The complex example uses several devcontainer features, so the first `up` or `rebuild` can take a while. `devbox` prints periodic elapsed-time progress lines while the devcontainer image/features are still being prepared and while the SSH runner is being installed.

## Notes

- The generated config is written next to the original devcontainer config, using the alternate accepted devcontainer filename so relative Dockerfile paths keep working.
- `--devcontainer-subpath services/api` tells `devbox` to use `.devcontainer/services/api/devcontainer.json`.
- `devbox shell` opens an interactive shell inside the running managed container for the current workspace.
- `devbox status` reports live container state when available and falls back to saved workspace state plus the persisted `.sshcred` password file and `.devbox-ssh.json` metadata when the container is stopped or Docker is unavailable.
- `devbox arise` only attempts workspaces it can recover from stopped managed containers and that still have at least one persisted devbox leftover, such as saved state, `.sshcred`, `.devbox-ssh.json`, or `.devbox-ssh-host-keys/`.
- For workspaces that pass the restart-readiness checks and are actually attempted, if there is more than one stopped managed container, `devbox arise` keeps the newest stopped container as the source of truth, removes the older stopped duplicates, and then reruns `devbox up`. Skipped or unrecoverable workspaces may retain older stopped duplicates.
- `devbox up` prints the chosen port near the start of execution, before the longer devcontainer setup steps.
- `down` removes managed containers but does not delete the workspace `.sshcred`, `.devbox-ssh.json`, or `.devbox-ssh-host-keys/`, so the SSH password, SSH metadata, and SSH host identity survive rebuilds.
- Re-running `devbox up` after a host restart recreates the desired state: container up, port published, SSH runner started again.
- When Docker Desktop host services are available, `devbox` can share the SSH agent without relying on a host-shell `SSH_AUTH_SOCK`.
- On Docker Desktop, `devbox` prefers the Docker-provided SSH agent socket over the host `SSH_AUTH_SOCK`, which avoids macOS launchd socket mount issues.
- `--allow-missing-ssh` starts the workspace without mounting an SSH agent and prints a warning instead of failing.
- `devbox` stages a snapshot of the host `~/.ssh/known_hosts` before startup and skips injection with a warning when that file is missing, unreadable, empty, symlinked, or not a regular file.
- `devbox` tries to install the host public key from `~/.ssh/id_rsa.pub` for SSH key-based login inside the container; if that default file is missing, it simply skips that step.
- `--ssh-public-key /path/to/key.pub` overrides the default public key source. The override is validated and must point to a readable SSH public key file.
- When the host already has Git author identity configured, `devbox` copies it into the container user's global Git config if the container does not already define those values.