A CLI tool for managing devcontainer configurations for your projects.
Managing devcontainer.json files can be tedious and error-prone. The devcontainer specification is very detailed, exposing a lot of options to engineers. It also doesn't provide a way to separate team-wide project settings from personal user preferences, forcing developers to either commit their personal configs or manually maintain separate files. devenv solves both of these problems by providing a simple configuration format that lets you define both project-wide and user-specific settings in YAML format. It then generates the appropriate devcontainer.json files for you.
For example, this .devcontainer/devenv.yaml file in your project:
name: "devenv"
modules: # Pre-configured bundles (mise for version management, scala/node for IDE plugins)
- mise
- scalaand this user config file in your home directory at ~/.config/devenv/devenv.yaml:
dotfiles:
repository: https://github.com/username/dotfiles.git
targetPath: ~/.dotfiles
installCommand: ./install.sh
plugins:
vscode:
- "com.github.copilot"
intellij:
- "GitHub.copilot"
- "com.mallowigi"will generate two devcontainer.json files:
.devcontainer/shared/devcontainer.json- with project settings only (checked into the repository).devcontainer/user/devcontainer.json- project settings merged with your personal preferences (excluded via .gitignore entry)
You can then use your IDE (VSCode or IntelliJ) to launch into the user configuration for a fully personalized development environment, or the shared configuration for a standard project setup. The latter ensures that cloud-based IDEs like GitHub Codespaces can use the (checked-in) shared configuration to provide a simple and consistent development environment.
Download the latest binary for your architecture from the latest release on GitHub and place it somewhere on your PATH. Each release includes its install commands, which will look like this:
curl -L --create-dirs -o ~/.local/bin/devenv <url-of-your-release-binary>
chmod +x ~/.local/bin/devenvNote:
~/.local/binis not onPATHby default on all systems. Ifdevenvisn't found after installation, add it to your shell config (e.g.export PATH="$HOME/.local/bin:$PATH"in~/.zshrcor~/.bashrc), or install to/usr/local/bininstead.
From the root of your project, run:
devenv initThis will create a .devcontainer/devenv.yaml file with some default settings. Set the project name and any other project options in this file.
You can also create a user config file at ~/.config/devenv/devenv.yaml to set your personal preferences (dotfiles, additional IDE plugins, etc).
Then run:
devenv generateThis will generate two devcontainer.json files:
.devcontainer/shared/devcontainer.json- Project-wide settings (checked-in).devcontainer/user/devcontainer.json- Merged project and user settings (not checked-in)
You can use these to launch devcontainers in your IDE.
# Build and run locally
sbt cli/stage
cli/target/universal/stage/bin/devenvBuild a standalone native executable with GraalVM Native Image. The GraalVM dependency is included in .tool-versions so that it can be managed by mise. The build-native-binary.sh script sets up environment variables for version management and runs the native image build:
./scripts/build-native-binary.shYou can also run the native image build directly with sbt, but this will not configure the versioning environment variables that are used to set the version and architecture in the resulting binary:
sbt "cli/GraalVMNativeImage/packageBin"The resulting binary will be at cli/target/graalvm-native-image/devenv.
devenv <command>
Commands:
init Initialize .devcontainer directory structure
generate Generate devcontainer.json files from devenv config
check Ensure devcontainer.json files match current config
version Show devenv's version
update Check for updates to devenv's CLI
help Shows the help text
Typical workflow:
- Run
devenv initfrom the root of a repository to create the initial config file for your project - Edit the generated
.devcontainer/devenv.yamlto set your project settings (see below for configuration details) - Optionally create a user config at
~/.config/devenv/devenv.yamlto set your personal preferences (dotfiles, additional IDE plugins) - Run
devenv generateto create the devcontainer.json files based on your config - Open the project in your IDE (VSCode or IntelliJ) and select the appropriate devcontainer configuration (
userfor your personalised environment,sharedfor the standard project setup)
You can also run devenv check to verify that the generated devcontainer.json files are up to date with your current config. This is useful locally and in CI, to make sure your project's devcontainer configuration is up to date.
Project config: .devcontainer/devenv.yaml - Project-specific settings (name, ports, IDE plugins, commands, etc). Checked into version control.
User config: ~/.config/devenv/devenv.yaml - Personal preferences (dotfiles, additional IDE plugins). Merged with project config for the user-specific devcontainer.
Two devcontainer files are generated:
.devcontainer/user/devcontainer.json- Merged config with your personal settings.devcontainer/shared/devcontainer.json- Project-only config for team use
Your user-specific file is excluded from the Git repository with a .gitignore entry. The general project file can be checked in to provide a project environment for cloud-based editors.
For detailed configuration specifications, see the Configuration Reference.
The .tool-versions file includes toolchain dependencies that can be managed by mise or your preferred version manager.
Use sbt to run the unit and integration tests:
sbt testDocker tests validate modules by creating real Docker containers and verifying configuration. Docker must be running to execute these tests. See Docker Testing Documentation for details.
The project also includes generation tests that validate the real program output. These package the CLI in dev/universal mode with sbt cli/stage, run the program against isolated temp directories, and validate the JSON output and file structure to ensure the CLI behaves correctly in real-world scenarios.
./generation-tests/run-tests.shThe project uses a GitHub action to build and publish date-based releases that contain native binaries for macOS arm64 (m-series processors), Linux amd64 and Linux arm64.
-
Go to the Actions tab on GitHub
-
Click "Run workflow" and select the branch to build from
-
GitHub Actions will automatically:
- Build native binaries for macOS ARM64 and Linux AMD64
- Sign and notarise the macOS binary with a Developer ID Application certificate
- Create a draft GitHub Release with date-based versioning (e.g.,
20251103-143022) - Name the binaries as
devenv-{date-version}-{platform}(e.g.,devenv-20251103-143022-macos-arm64) - Mark the release as a prerelease
-
Manually verify and publish the release:
- Go to the Releases page on GitHub
- Review the draft release
- Add (generated) release notes
- Test the binaries if needed
- Click "Publish release" when ready
To build a native binary locally, use the build-native-binary.sh script. This is useful for testing the native build on your own machine outside of CI.
./scripts/build-native-binary.shThe release version defaults to the current timestamp, but can be specified explicitly if needed:
./scripts/build-native-binary.sh 20251103-143022The script will:
- Set the
DEVENV_RELEASEenvironment variable to the specified version (or current timestamp if omitted) - Append
-devto the version if building from a branch other thanmain - Set the
DEVENV_ARCHITECTUREenvironment variable (auto-detected or specified) - Set the
DEVENV_BRANCHenvironment variable (auto-detected from git or specified) - Display the build configuration and prompt for confirmation
- Build a native binary with GraalVM Native Image
The resulting binary will be at cli/target/graalvm-native-image/devenv.
Architectures the script can detect:
macos-arm64(Apple Silicon)macos-amd64(Intel Mac)linux-arm64(ARM Linux)linux-amd64(x86_64 Linux)
Releases use date-based versioning: YYYYMMDD-HHMMSS (e.g., 20251103-143022)
Note: The release workflow is triggered manually - it will not run automatically on pushes or merges to give full control over when to "cut" a release.
The version is embedded in the native binary at build time, so users can check their version with:
devenv versionThey can also check for updates with:
devenv updateThe update command checks the GitHub releases for a newer version and prompts the user to download it if available.