A collection of tools for managing multiple named devcontainers for any project.
This is a multi-package monorepo containing:
π¦ CLI Tool - devs
The main command-line interface for managing devcontainers locally.
# Install the CLI tool
pip install devs
# Start development environments
devs start frontend backend
# Open in VS Code
devs vscode frontend backendπ Webhook Handler
GitHub webhook handler for automated devcontainer operations in response to @mentions in issues and PRs.
π Web Admin - devs-webadmin
A web-based admin UI for managing devcontainers on a server. Start/stop containers, manage VS Code tunnels, all from a browser.
pip install devs-webadmin
devs-webadmin serve
# Open http://localhost:8080π οΈ Common Utilities
Shared utilities between all packages including container management, workspace handling, and devcontainer templates.
# Install just the CLI tool
pip install devs
# Or install from source (development)
cd packages/cli
pip install -e .# Start development environments
devs start sally bob charlie
# Open containers in VS Code (separate windows)
devs vscode sally bob
# Work in a specific container
devs shell sally
# List active containers
devs list
# Clean up when done
devs stop sally bob charlie- Docker: Container runtime
- VS Code: With
codecommand in PATH - DevContainer CLI:
npm install -g @devcontainers/cli - Project Requirements:
.devcontainer/devcontainer.jsonin target projects
- Multiple Named Containers: Start multiple devcontainers with custom names
- VS Code Integration: Open containers in separate VS Code windows with clear titles
- Project Isolation: Containers are prefixed with git repository names
- Workspace Isolation: Each dev environment gets its own workspace copy
- Environment Variable Management: Layered configuration system with user-specific overrides
- GitHub Webhook Integration: Automated CI and Claude Code responses to @mentions
- Cross-Platform: Works on any project with devcontainer configuration
devs supports a powerful layered configuration system for environment variables:
# Basic usage with ~/.devs/envs DEVS.yml
devs start myenv
# CLI overrides for testing
devs start myenv --env DEBUG=true --env API_URL=http://localhost:3000Configuration priority (highest to lowest):
- CLI
--envflags ~/.devs/envs/{org-repo}/DEVS.yml(user-specific project overrides)~/.devs/envs/default/DEVS.yml(user defaults)
A DEVS.yml committed to the repository is not read: these settings say how you
run a repo, which isn't the repo's to decide.
Example ~/.devs/envs/myorg-myrepo/DEVS.yml:
env_vars:
default:
NODE_ENV: development
API_URL: https://api.example.com
myenv: # Container-specific overrides
DEBUG: "true"
SPECIAL_FEATURE: "enabled"User-specific configuration:
# Global defaults for all projects
mkdir -p ~/.devs/envs/default
echo 'env_vars:
default:
GLOBAL_SETTING: "user_preference"' > ~/.devs/envs/default/DEVS.yml
# Project-specific overrides (replace "/" with "-" in repo name)
mkdir -p ~/.devs/envs/myorg-myrepo
echo 'env_vars:
myenv:
SECRET_KEY: "user_secret"' > ~/.devs/envs/myorg-myrepo/DEVS.ymlContainers you don't trust. devs start runs devcontainer up on the host against
the workspace's .devcontainer, and initializeCommand runs there as you. The workspace
is writable from inside the container, so whoever has a shell in it can get code run on
the host at the next start. A read-only mount over that folder doesn't stop root in the
container, which can unmount it. Instead, set this in ~/.devs/envs/{org-repo}/DEVS.yml:
host_devcontainer: truedevs then reads .devcontainer from the project on the host and ignores the workspace's
copy. Mount .devs-env read-only too (...,type=bind,readonly) so the container can't
change that file; with nothing writable above it, that holds even for root.
Place executable scripts in ~/.devs/envs/<project>/autoexec/ to run them in
lexical filename order from the template's postCreateCommand. If a
project-specific environment directory does not exist,
~/.devs/envs/default/autoexec/ is used. The directory's presence is the
opt-in; projects without it are unaffected.
Hooks inherit the mounted .env variables plus DEVS_PROJECT_NAME and
DEVCONTAINER_NAME. A failing hook is reported without preventing later hooks
from running, and makes the post-create step fail after the remaining hooks have
finished. Hooks should tolerate being rerun when a container is rebuilt.
mkdir -p ~/.devs/envs/default/autoexec
cp my-startup-hook.sh ~/.devs/envs/default/autoexec/10-my-startup-hook.sh
chmod +x ~/.devs/envs/default/autoexec/10-my-startup-hook.shautoexec/ runs once, when the container is created. A hook that starts
something long-lived needs to come back after a plain restart too (host reboot,
devs start), which re-runs only postStartCommand: put it β or a symlink to
it, ln -s ../autoexec/10-my-startup-hook.sh β in a sibling autoexec-start/
directory, which the template's post-start.sh runs on every start, after the
tailnet is up. It runs straight after create as well, so such a hook must be
idempotent; its failure is reported but never fails the start.
π See example-usage.md for detailed examples and scenarios
devs/
βββ packages/
β βββ cli/ # Main CLI tool
β βββ webhook/ # GitHub webhook handler
β βββ webadmin/ # Web admin UI
β βββ common/ # Shared utilities
βββ docs/ # Documentation
βββ scripts/ # Development scripts
βββ devs # Legacy zsh script (to be removed)
βββ README.md # This file
# Install CLI package in development mode
cd packages/cli
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black devs tests
# Type checking
mypy devsUse the provided script to bump versions and publish all packages to PyPI:
# Patch version bump (e.g., 0.1.0 -> 0.1.1)
python scripts/bump-and-publish.py
# Minor version bump (e.g., 0.1.0 -> 0.2.0)
python scripts/bump-and-publish.py minor
# Major version bump (e.g., 0.1.0 -> 1.0.0)
python scripts/bump-and-publish.py majorThe script will:
- Update version numbers in all package
pyproject.tomlfiles - Build packages in dependency order (common β cli β webhook β webadmin)
- Upload to PyPI using
twine
Prerequisites:
buildandtwinepackages installed- PyPI authentication configured (API token or username/password)
Containers follow the pattern: dev-<org>-<repo>-<dev-name>
Example: dev-ideonate-devs-sally, dev-ideonate-devs-bob
Each container gets unique workspace paths to ensure VS Code treats them as separate sessions.
Each dev environment gets its own workspace copy with:
- Git-tracked files (or all files for non-git projects)
- Special directories (.git, .claude, .devcontainer extras)
- Proper exclusion of build/cache directories
The original zsh script is still available at the repository root (./devs) but will be removed in a future version. New users should use the Python CLI tool.
MIT License - see individual package directories for details.
- Fork the repository
- Create a feature branch
- Make your changes in the appropriate package directory
- Add tests for new functionality
- Ensure all tests pass
- Submit a Pull Request