devbox is a CLI that turns the devcontainer definition in the current directory into a repeatable "start my workspace and expose one or more service ports" workflow, with an optional bundled SSH entrypoint and persistent post-start command hook.
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.
- Discovers
.devcontainer/devcontainer.jsonor.devcontainer.jsonin the current directory, can target.devcontainer/<subpath>/devcontainer.jsonwith a flag, and falls back to the built-inubuntutemplate when no repo devcontainer is present. - Reuses or creates the devcontainer with Docker + Dev Container CLI.
- Names the managed container as
devbox-<project>-<port>. - Publishes one or more TCP ports using the same host and container port numbers.
- 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_hostssnapshot into the container. - When enabled, exposes the bundled SSH service on the first published 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.nameanduser.emailfrom the host when available. - Injects
GH_TOKENfrom the GitHub CLI when available, optionally using a persisted per-workspaceghaccount. - Runs devbox's bundled SSH server setup script inside the devcontainer unless SSH is disabled.
- Runs an optional persisted startup command inside the devcontainer after it is ready.
- Stores devbox-owned state, SSH credentials, SSH metadata, and SSH host keys under the workspace-local
.devbox/directory so they survivedown/rebuild.
Install devbox with the generic @pablozaiden/installer script:
curl -fsSL https://raw.githubusercontent.com/pablozaiden/installer/main/install.sh | sh -s -- pablozaiden/devboxThe installer downloads the Linux or macOS binary for your CPU architecture, verifies the published .sha256 checksum, and installs it into $HOME/.local/bin by default. Add that directory to PATH if the installer reports it is not already available.
Run devbox with no arguments to see the CLI help.
To update an installed binary later:
devbox updateUse devbox update --check to check for a newer release without installing it, or devbox update --version <version> to install a specific release.
- macOS or Linux
- Docker
- Dev Container CLI available as
devcontainerin whatever environment runsdevbox - For SSH agent sharing: either a valid host
SSH_AUTH_SOCK, or Docker Desktop host services - For repo-defined environments: a devcontainer using
imageorDockerfile
dockerComposeFile-based devcontainers are intentionally out of scope for v1.
# Show CLI help
devbox
# Start or reuse the devcontainer on a chosen port (bundled SSH is enabled by default)
devbox up <port>
# Start or reuse the devcontainer, reusing stored workspace ports when available
# or auto-assigning the first free port from 5001 when none has been stored yet
devbox up
# Publish three ports, using the first one for bundled SSH
devbox up --ports 3
# Publish two ports without installing or starting bundled SSH
devbox up --ports 2 --no-ssh
# Run a persistent command after every devbox up or rebuild
devbox up --no-ssh --startup-command '.devbox/clanky-worker/start.sh'
# Clear the persistent post-start command
devbox up --no-startup-command
# Re-enable bundled SSH for a workspace previously started with --no-ssh
devbox up --ssh
# 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 GitHub CLI account for GH_TOKEN injection and persist it for this workspace
devbox up --gh-user work-account
# Use an account on a non-default GitHub host
devbox up --gh-user work-account --gh-host github.example.com
# Use a specific devcontainer under .devcontainer/services/api
devbox up <port> --devcontainer-subpath services/api
# Start from a specific built-in template instead of a repo devcontainer
devbox up --template python
# Check for and install newer devbox release binaries
devbox update
# List the built-in templates as JSON
devbox templates
# Rebuild/recreate the managed devcontainer
devbox rebuild <port> [--ports <count>]
# Open an interactive shell in the running managed devcontainer for this workspace
devbox shell
# Run a non-interactive command in the running managed devcontainer
devbox exec -- npm test
# 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 workspace-local devbox state and SSH artifacts
devbox downWhen you run devbox up, the port precedence is:
- the explicit port you passed as the first port,
- the last stored port list for the current workspace, or
- the first free port starting at
5001.
Use --ports <count> to request how many ports should be published. If the first port is explicit, later ports are auto-assigned from the next port, skipping ports already in use. When SSH is enabled, only the first port is used by the bundled SSH server; the remaining ports are available for services in the devcontainer. --no-ssh disables only the bundled SSH server, while SSH agent sharing and the other Git/known-hosts integrations remain unchanged. --ssh re-enables the bundled server.
--startup-command <command> stores a shell command in the workspace state and runs it inside the container after each successful devbox up or devbox rebuild, including runs started by devbox arise. The state is saved before the hook runs, so a failed first attempt remains configured for the next invocation. The command runs independently of the bundled SSH server and is executed through devcontainer exec; it should be idempotent and daemonize any long-running process it starts. Use --no-startup-command to clear the stored command. The hook is invoked by Devbox's CLI lifecycle, so a raw docker restart does not invoke it.
If an older or manually edited state file contains an invalid startupCommand, Devbox ignores that value with a warning so the workspace remains manageable; the next up or rebuild rewrites the state cleanly.
When you run devbox rebuild, omitting the port reuses the last stored port list for the current workspace.
When changing the number of ports for an already running workspace, pass the desired count to rebuild, for example devbox rebuild 6000 --ports 2.
The workspace state file uses schema version 4 and stores the selected ports only as ports; it never contains a singular port field:
{
"version": 4,
"ports": [5001, 5002],
"sshEnabled": false,
"startupCommand": ".devbox/clanky-worker/start.sh"
}State files from older devbox versions are intentionally not migrated. Remove .devbox/state.json and run devbox up again with the desired port options when upgrading an existing workspace.
If no repo devcontainer is found and no previous template source is stored, devbox up automatically starts from the built-in ubuntu template. devbox rebuild <port> does the same when there is enough information to create the devbox but no prior workspace state exists. Devbox prints a message when this automatic fallback is used.
devbox rebuild reuses the previously selected source for the workspace. If the workspace was started from --template or the automatic Ubuntu fallback, rebuild uses that saved template again. rebuild --template ... is intentionally not supported.
GitHub CLI authentication for GH_TOKEN injection can be pinned per workspace with --gh-user <login> and optional --gh-host <host>. Devbox stores only the selected account metadata in .devbox/state.json as githubAuth; it does not store the token. Later up, rebuild, and arise runs reuse that account by calling gh auth token --hostname <host> --user <login>. The selection precedence is: explicit flags, DEVBOX_GH_USER / DEVBOX_GH_HOST, saved .devbox/state.json, then the currently active gh account.
devbox shell requires an already running managed container for the current workspace. If none is running, use devbox up first.
devbox exec -- <command> [args...] runs a non-interactive command in the already running managed container for the current workspace. The -- separator must be the first argument after exec; everything after it is forwarded unchanged. The command's standard input/output/error and exit code are preserved, which makes it suitable for scripts and automation. If no managed container is running, run devbox up first.
devbox status always prints JSON so it can be used directly from scripts and automation.
devbox templates always prints JSON. Each entry includes the template name, description, pinned image/reference, runtime version, language tags, and whether the template is compatible with the bundled devbox SSH runner.
Built-in templates:
ubuntudotnettypescriptpythongorustjava
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:
{
"running": true,
"ports": [5001, 5002],
"sshEnabled": true,
"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",
"githubAuth": {
"host": "github.com",
"user": "work-account"
},
"hasStateFile": true,
"hasCredentialFile": true,
"hasSshMetadataFile": true,
"warnings": []
}The full payload also includes useful diagnostic fields such as workspaceHash, labels, publishedPorts, statePath, credentialPath, sshMetadataPath, updatedAt, githubAuth, and the stored/generated config paths. Devbox-owned state paths point inside the current workspace's .devbox/ directory.
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.
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.
bun install
bun run typecheck
bun test
bun run test:fast
bun run buildPull requests run the same typecheck, 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 standalone Bun-compiled binary at dist/devbox.
For local development from this repository:
- use
bun run src/cli.tswhile iterating on source changes - use
./dist/devboxafterbun run buildto 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:
cd examples/smoke-workspace
../../dist/devbox up --allow-missing-sshFor a more realistic feature-heavy example, this repository also includes examples/complex-workspace/.devcontainer/devcontainer.json:
cd examples/complex-workspace
../../dist/devbox upThe 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.
- When
devboxuses a repo devcontainer, the generated config is written next to the original devcontainer config, using the alternate accepted devcontainer filename so relative Dockerfile paths keep working. - When
devboxuses--template, it writes the generated config to.devbox/.devcontainer.jsoninstead of creating a source devcontainer definition inside the repo. .devbox/contains all devbox-owned local state (state.json,user-data/, template generated configs,ssh/, and any startup-command integration data) and should stay ignored by version control..devbox/state.jsonmay includegithubAuth: { "host": "...", "user": "..." }so tools can detect or preserve the GitHub CLI account devbox will use for futureGH_TOKENinjection.--devcontainer-subpath services/apitellsdevboxto use.devcontainer/services/api/devcontainer.json.--template <name>explicitly chooses a built-in template, even if the repo already has a devcontainer definition. If no repo devcontainer exists and no template was previously saved, omitting--templatefalls back toubuntu.--gh-user <login>and--gh-host <host>select the GitHub CLI account used forGH_TOKENinjection without changing the globally activeghaccount.devbox shellopens an interactive shell inside the running managed container for the current workspace.devbox exec -- <command> [args...]runs an automation command inside the running managed container and forwards its exit code.devbox statusreports live container state when available and falls back to saved workspace state in.devbox/state.jsonplus the persisted.devbox/ssh/credentialspassword file and.devbox/ssh/metadata.jsonmetadata when the container is stopped or Docker is unavailable.devbox ariseonly attempts workspaces it can recover from stopped managed containers and that still have at least one persisted devbox leftover, such as saved state,.devbox/ssh/credentials,.devbox/ssh/metadata.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 arisekeeps the newest stopped container as the source of truth, removes the older stopped duplicates, and then rerunsdevbox up. Skipped or unrecoverable workspaces may retain older stopped duplicates. devbox upprints all selected ports near the start of execution, before the longer devcontainer setup steps.downremoves managed containers but keeps.devbox/, so rebuilds can reuse the last selected ports/config source/template and SSH artifacts.- Re-running
devbox upafter a host restart recreates the desired state: container up, all configured ports published, the SSH runner restarted when it is enabled, and the persisted startup command rerun when configured. - When Docker Desktop host services are available,
devboxcan share the SSH agent without relying on a host-shellSSH_AUTH_SOCK. - On Docker Desktop,
devboxprefers the Docker-provided SSH agent socket over the hostSSH_AUTH_SOCK, which avoids macOS launchd socket mount issues. --allow-missing-sshstarts the workspace without mounting an SSH agent and prints a warning instead of failing.--no-sshskips starting the bundled SSH server but still installs the common container tools, shares the SSH agent, and configures the other host integrations.--sshexplicitly enables the bundled SSH server for a workspace whose saved state has SSH disabled.devboxstages a snapshot of the host~/.ssh/known_hostsbefore startup and skips injection with a warning when that file is missing, unreadable, empty, symlinked, or not a regular file.devboxtries to install the host public key from~/.ssh/id_rsa.pubfor SSH key-based login inside the container; if that default file is missing, it simply skips that step.--ssh-public-key /path/to/key.puboverrides 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,
devboxcopies it into the container user's global Git config if the container does not already define those values.