devvault-cli

command module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 1 Imported by: 0

README

devvault

OpenSSF Scorecard

[!WARNING] Work in progress — devvault is under active development and not yet ready for production use. Commands, configuration formats, and behavior may change without notice between releases.

devvault is a macOS-first CLI for reproducible, isolated Terraform development environments.

It prefers Apple container on supported Apple Silicon macOS 26 hosts and falls back to Docker Desktop with Docker Compose v2.

Contents

Requirements

  • macOS, with Apple Silicon recommended for Apple container.
  • Apple container on supported macOS 26 hosts, or Docker Desktop with Docker Compose v2.
  • Git.
  • Xcode Command Line Tools.
  • AWS CLI when using AWS credential checks and Terraform workflows.
  • cosign, optional, to verify a release signature and to let devvault update verify one.
  • Go 1.25 or newer only if you build devvault from source. The released binary has no Go dependency.

Installation

Download the archive for your Mac from the latest GitHub release, together with checksums.txt and checksums.txt.sigstore.json:

  • Apple Silicon: devvault_Darwin_arm64.tar.gz
  • Intel: devvault_Darwin_x86_64.tar.gz

Verify the release before you run it. Step 1 proves who built it and needs a current cosign (--bundle is not in old releases); step 2 proves your download matches what was signed:

cosign verify-blob checksums.txt \
  --bundle checksums.txt.sigstore.json \
  --certificate-identity-regexp 'https://github\.com/Marcel2409Dev/devvault-cli/\.github/workflows/release\.yml@refs/tags/v.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

shasum -a 256 -c checksums.txt --ignore-missing

Releases up to 0.7.0 carry the same signature under the name checksums.txt.bundle; pass that name to --bundle when you verify one of them. Both names are published for now, and checksums.txt.bundle goes away in 0.10.0. devvault update handles this itself: it reads checksums.txt.sigstore.json and falls back to checksums.txt.bundle.

If either step fails, do not run the binary. Then unpack and install:

tar -xzf devvault_Darwin_arm64.tar.gz
mkdir -p ~/.local/bin
install -m 755 devvault ~/.local/bin/devvault   # make sure ~/.local/bin is on your PATH
devvault version
devvault --help

Use the x86_64 archive name on Intel Macs. Any directory on your PATH works, including /opt/homebrew/bin: devvault update replaces the running binary in place and only refuses when that binary is actually managed by Homebrew — that is, when it resolves into a Cellar or Caskroom directory — because replacing it there would leave brew with a binary it did not install.

Each archive also contains shell completions under completions/ (bash, zsh, fish) and man pages under man/; install them wherever your shell and man look. Every archive ships an SBOM (*.sbom.json) next to it in the release. The pages under man/ are produced by the hidden devvault man --dir <directory> command, which exists for packaging and is not part of the everyday surface.

Releases are not Apple-notarized yet, so Gatekeeper quarantines the downloaded binary. Verify the signature first, then clear the attribute with xattr -dr com.apple.quarantine <path>.

Homebrew is the simpler route: every release publishes a cask to the tap Marcel2409Dev/homebrew-tap, which installs the binary, the man pages and the shell completions in one step.

brew tap Marcel2409Dev/tap
brew install --cask devvault

To update, use brew upgrade devvault for a Homebrew installation, and devvault update for one you installed by hand — devvault refuses to replace a binary Homebrew manages, so the two never fight over the same file. To uninstall, brew uninstall --cask devvault or remove the binary you installed.

Quick start

Start devvault and navigate the guided menu:

devvault

The menu groups the common workflows into Projects, Setup, Terraform, Credentials, and System. You can also open it explicitly:

devvault menu

Running devvault without arguments opens that menu only when it has a terminal and a project is already active. Without an active project it starts devvault quickstart instead, and without a terminal it prints the help.

Direct commands stay available for automation, wrappers, and fast shell workflows:

devvault init                            # generate the devvault files
devvault add . --id kunde-a/network_p    # register the project — required
devvault doctor
devvault build
devvault tf version
devvault validate
devvault ij

devvault init deliberately does not register. Registering is where you state which customer and credential set a directory belongs to, and a repository must not make that statement about itself. Until a project is registered, devvault mounts no credentials for it and refuses to run anything in its container — see Registered projects. devvault clone, devvault setup and devvault quickstart register on their own.

If the id you pick names a different customer or credential set than the config init generated, run devvault sync afterwards so the generated files match.

devvault doctor exits with status 1 when a required host check fails — architecture, git, and "at least one container backend is available". Everything else is reported as a warning. --fix performs the repairs devvault can make safely and locally; it never installs software, starts a daemon, or touches the network.

Clone and prepare a customer repository:

devvault clone git@github.com:example/kunde-a-platform.git
devvault clone git@gitlab.com:example/kunde-a-platform.git
devvault clone git@bitbucket.org:example/kunde-a-platform.git
devvault open git@github.com:example/kunde-a-platform.git

Manage registered projects:

devvault add /path/to/repo --id kunde-a/platform --alias kunde-a
devvault list
devvault use kunde-a
devvault current
devvault tf version
cd "$(devvault cd kunde-a)"

Set up a customer with credential sets:

devvault setup customer \
  --customer kunde-a \
  --repo /path/to/network-p \
  --project network_p \
  --credential-set network_p \
  --profile network-p-dev \
  --auth existing

devvault setup project \
  --customer kunde-a \
  --repo /path/to/network-c \
  --project network_c \
  --credential-set network_c \
  --profile network-c-dev \
  --auth credential_process \
  --credential-process "/usr/local/bin/customer-auth network-c"

Credentials are grouped per customer and set:

~/.aws-kunden/kunde-a/network_p/config
~/.aws-kunden/kunde-a/network_c/config

Check or log in:

devvault credentials list kunde-a
devvault credentials status --project kunde-a/network_c
devvault credentials login --project kunde-a/network_p

Git metadata is detected for GitHub, GitLab, Bitbucket, self-hosted Git servers, and local bare repositories. devvault list shows projects grouped by customer with their credential set and Git provider.

Generated wrapper scripts under .devvault/scripts/ are intended for configured editor, Codex App, and shell usage. New projects keep DevVault-owned files under .devvault/, including config.yaml, docker-compose.yml, Dockerfile, and wrapper scripts. Standard integration files such as .devcontainer/devcontainer.json, GitHub Actions, GitLab CI, and pre-commit configuration stay in their conventional locations.

Command overview

One line per command. The sections below describe each of them in context.

Command What it does
devvault Opens the interactive menu, or starts quickstart when no project is active
add Registers an existing devvault repository in your registry
apply Runs terraform apply in the container; needs --yes
apply-plan Applies a plan saved with plan --save; needs --yes and refuses a plan the code has moved past
backend doctor Runs terraform init -backend=false to check backend configuration
build Builds the project's development container image
cache Shows the shared Terraform provider caches and reclaims their disk space: list, prune
cd Prints a registered project's path, for cd "$(devvault cd …)"
clone Clones a Git repository, generates the devvault files and registers it
completion Generates the shell completion script for bash, zsh, fish or PowerShell
context Prints customer, project, credential set, AWS profile, Terraform version and backend
credentials Creates, inspects and refreshes the AWS config of a credential set
current Prints the active project
customer Runs doctor, validate, scan or docs across all projects of one customer
destroy Runs terraform destroy in the container; needs --yes
docs Runs terraform-docs and injects the result into README.md
doctor Checks host and backend readiness; --fix applies the safe local repairs
fix Recreates missing local support directories and, with --regenerate, missing generated files
graph Renders terraform graph into terraform-graph.svg with Graphviz
ij Switches between open IntelliJ projects
images Shows the container images devvault built and reclaims their disk space: list, prune
import Runs terraform import in the container; needs --yes
init Generates the devvault files in the current repository, without registering it
lint Runs tflint and the enabled filesystem scanners
list Lists registered projects, grouped by customer
menu Opens the interactive menu explicitly
modules Module helpers: doctor validates recursively, changelog prints guidance
open Opens a project in the configured editor, cloning a Git URL first if needed
overview Shows every registered project in one table — image, trust, pins, credentials — with the one command each needs
plan Runs terraform plan in the container; --save writes a protected plan file
policy check Runs conftest test .
precommit Installs or runs the pre-commit hooks inside the container
quickstart Interactive first-project setup
registry check Reports registrations that no longer describe reality, with the command that fixes each one
registry export Writes the project mapping as a portable document, without the records that only hold on this machine
registry import Restores those registrations on another machine, for every checkout it finds
remove Removes a project from the registry, leaving the files alone
scan Runs the enabled security scanners over the project
secrets scan Runs a gitleaks secret scan
setup Guided customer/project setup: setup customer, setup project
shell Opens an interactive shell in the development container
state Terraform state helpers: list, pull, mv, rm
status Reports project readiness with per-check hints
sync Regenerates the devvault-managed files from your trusted config
tf Runs an arbitrary Terraform command in the container
tools Compares the pinned tool versions against upstream: outdated, bump
trust Inspects and approves the devvault files a repository ships: status, review, accept
update Replaces this devvault binary with the latest release
use Sets the active project
validate Runs the fmt / init / validate / tflint chain
version Prints the devvault version and build information
workspace Terraform workspace helpers: list, show, select

Flags that apply almost everywhere

-p, --project <id-or-alias> selects the project a command acts on. It is accepted by every command that touches a project: apply, apply-plan, backend doctor, build, cache prune, context, credentials login, credentials status, destroy, docs, fix, graph, images prune, import, lint, modules doctor, plan, policy check, precommit install, precommit run, scan, secrets scan, shell, state …, status, sync, tf, tools bump, tools outdated, trust accept, trust review, trust status, validate and workspace ….

The value may be the full id <customer>/<project>, the alias, the project name, or the base name of the project directory; a value that matches more than one project is rejected as ambiguous instead of guessed. Without -p, devvault uses the current directory when it contains .devvault/config.yaml (or the legacy .devvault.yaml), and otherwise the active project set by devvault use.

--json prints one machine-readable document on stdout. It exists on cache list, cache prune, context, credentials list, credentials status, current, doctor, fix, images list, images prune, list, overview, registry check, registry export, status, sync, tools outdated, trust status and version. On registry export it selects the format of the exported document rather than a report about it. credentials status --json moves the AWS CLI's own output to stderr so stdout carries exactly the one document devvault promises, and cache prune --json / images prune --json refuse to run without --yes rather than draw a confirmation prompt over that document.

--yes confirms an operation devvault will not perform on its own. It has three distinct roles:

  • On apply, apply-plan, destroy, import, state mv and state rm it is a hard gate: without it the command stops with … is destructive or mutating; rerun with --yes to confirm, before anything is started. These commands also print customer, workspace, AWS profile and credential set before they run, so you can see which account you are about to change. On apply-plan, --yes confirms that the run changes infrastructure and nothing else — that the saved plan still matches the code is a separate question with a separate flag, see apply, apply-plan, destroy, import.
  • On add, cache prune, images prune, trust accept, tools bump and update it replaces an interactive confirmation. Without a terminal those commands refuse rather than assume consent; the reasons are in Registered projects, The shared provider cache, The images devvault builds, Trusted project files, Keeping the pinned tool versions current and Updating devvault.
  • On registry import it says that an existing registration may be replaced by the one in the document. Without it such a project is reported and skipped; see Moving to another machine.

Those are the only commands that have --yes. The read-only passthrough subcommands — state list, state pull, workspace list, workspace show, workspace select and secrets scan — change nothing, so they neither document the flag nor consume it: whatever you pass them goes to the tool in the container, and an unknown flag fails there rather than being silently swallowed.

Passthrough commands. tf, plan, apply, destroy, import, workspace …, state … and secrets scan forward their arguments to the tool in the container. devvault only interprets the flags in the leading run of flags: --help there is devvault's own help, a -- there ends devvault's interpretation and is removed, and everything from the first non-flag argument on belongs to Terraform. That also covers devvault's own flags on plan, so -- is the way to pass a Terraform flag whose name devvault happens to use too.

devvault plan --help            # devvault's help for plan
devvault tf plan --help         # Terraform's help for plan
devvault tf -- -help            # reaches Terraform as: terraform -help
devvault plan -- --save         # reaches Terraform as: terraform plan --save
devvault plan --save -- -target=module.network   # --save is devvault's, the rest is not
devvault tf -p kunde-a state list
devvault tf -p=kunde-a state list

All four spellings of the project flag work here — -p <value>, -p=<value>, --project <value> and --project=<value>. devvault removes it before the rest is forwarded, so it never reaches Terraform.

Registered projects

devvault mounts AWS credentials into a container that then runs the repository's own code, so which host directory that is can never be the repository's decision. It comes from your own registry under ~/.devvault, which is written by devvault add, devvault clone, devvault setup, devvault quickstart and devvault registry import — and by nothing inside a clone.

A project that is not registered therefore gets no credentials at all, and every command that runs something inside the container refuses before a backend is even selected. Registering is what unblocks it:

devvault add . --id kunde-a/network_p

Without --id, devvault add falls back to the customer, project and credential set the repository's own config proposes. That proposal decides which host directory gets mounted, so devvault prints it and asks for confirmation first; without a terminal it refuses and asks for --id or --yes.

Where the registry names no mount — because the credential set has none, or because there is no credential set for the project at all — devvault derives it from the registered customer and credential set, ~/.aws-kunden/<customer>/<set>. That is a registry decision too: the repository's aws.credentials_mount never steps in for a missing one. It is also why devvault registry export can leave a mount behind that holds only on the old machine — see Moving to another machine.

Read-only commands keep working and report the missing registration honestly rather than naming a path: devvault context prints (not registered) for the AWS profile and config path, devvault status lists the credentials mount as missing, and devvault list, devvault credentials list and devvault trust status are unaffected. devvault credentials status and devvault credentials login do refuse — they would have to point the AWS CLI at a config file the repository picked.

Setting up a project

There are four ways into a devvault project. They differ in how much they decide for you, not in what they produce.

devvault quickstart

Interactive, and the right choice for a first project. It asks whether to initialize the current directory, use an existing local repository, or clone a Git URL, then asks for customer id, project name, credential set and AWS profile, generates the files, registers the result and prints the readiness status.

devvault quickstart

It requires a terminal; in a script it stops with a message pointing at devvault setup and devvault clone.

devvault setup

The scriptable variant of the same thing. devvault setup customer creates a new customer together with its first project, devvault setup project adds a project to an existing customer, and plain devvault setup behaves like setup customer. All three take the same flags; missing values are asked for interactively when devvault has a terminal, so a fully flagged invocation is non-interactive.

devvault setup customer \
  --customer kunde-a \
  --customer-name "Kunde A GmbH" \
  --repo git@github.com:example/kunde-a-platform.git \
  --project network_p \
  --credential-set network_p \
  --profile network-p-dev \
  --auth sso \
  --sso-start-url https://kunde-a.awsapps.com/start \
  --sso-region eu-central-1 \
  --sso-account-id 123456789012 \
  --sso-role-name AdministratorAccess
Flag Meaning
--customer Customer id, e.g. kunde-a
--customer-name Customer display name
--workspace Customer workspace directory
--repo Git URL or local repository path
--project Project name, e.g. network_p
--credential-set Credential set name
--profile AWS profile name
--auth sso, assume_role, credential_process or existing
--distribution terraform or opentofu; detected from the repository when omitted
--terraform-version Explicit version of the selected distribution
--region Default AWS region, default eu-central-1
--sso-start-url, --sso-region, --sso-account-id, --sso-role-name AWS SSO parameters
--role-arn, --source-profile assume_role parameters
--credential-process credential_process command
--force Overwrite generated files and config

setup prints the resulting project id, path, distribution, Terraform version and credentials mount.

devvault clone

Clones a Git repository into ~/DevVault/<repo> (override with --base-dir, or pass an explicit target directory), generates the devvault files and registers the result.

devvault clone git@github.com:example/kunde-a-platform.git
devvault clone git@github.com:example/kunde-a-platform.git ~/work/platform
devvault clone git@github.com:example/kunde-a-platform.git --id kunde-a/platform --alias platform --open
Flag Meaning
--base-dir Base directory for cloned repositories, default ~/DevVault
--id Project id, e.g. kunde/platform
--alias Short project alias
--no-init Clone only, do not generate the devvault files
--force-init Overwrite generated devvault files after cloning
--open Open the clone in the configured editor
--editor Editor override: intellij, vscode, cursor, codex, custom
--allow-custom-editor See Editor integration

An existing target directory is not overwritten; the clone step is skipped and reported as clone: skipped, directory already exists.

devvault init and devvault add

init generates the devvault files in the current repository and nothing else. It is the right entry point for a repository you already have checked out.

devvault init
devvault init --force --readme
devvault init --no-create-credentials-dir
Flag Meaning
-f, --force Overwrite generated files
--readme Also generate README-devvault.md
--no-create-credentials-dir Do not create the configured AWS credentials mount directory

The files it writes are .devvault/config.yaml, .devvault/Dockerfile, .devvault/docker-compose.yml, the wrapper scripts .devvault/scripts/tf, validate, lint, docs, plan, scan and context, plus .devcontainer/devcontainer.json, .pre-commit-config.yaml, .github/workflows/terraform.yml and .gitlab-ci.yml.

add registers a directory. This is the step that makes the project usable — see Registered projects for why it cannot be skipped and why it asks before accepting an identity the repository proposed.

devvault add . --id kunde-a/network_p
devvault add /path/to/repo --id kunde-a/platform --alias kunde-a
devvault add /path/to/repo --yes          # accept the repository's proposal

add prints the credentials mount it registered, and warns when the generated files no longer match the resulting configuration — the fix is devvault sync -p <id>.

It also reads the repository's origin remote and records the same Git metadata devvault clone stores: provider, host, owner and repository name. Those fields are descriptive only — nothing about them decides what devvault mounts or runs — but two commands use them: devvault list shows the provider, and devvault doctor derives the reachability check for that Git host from the registry (git ssh gitlab.com for an SSH remote, a credential-helper reminder for an HTTPS one). Without the metadata that check does not fail, it silently disappears. A directory that is not a Git repository, or a repository without an origin remote, is registered without Git metadata; that is the normal local case and not an error.

devvault remove <project-or-alias> deletes the registry entry again. It never touches the repository on disk.

Selecting and inspecting projects

devvault list                   # projects grouped by customer
devvault list --json
devvault use kunde-a            # set the active project
devvault current                # print the active project
devvault remove kunde-a

list marks the active project with * and appends (missing) when the registered path no longer exists. Alias, credential set and Git provider are shown when they are known. It stays the cheap listing: it reads your registry and nothing else.

devvault overview is the view across all of them — one line per registered project, so "which of my projects need attention" is a single command instead of a devvault status per project:

devvault overview
devvault overview --json
PROJECT              STATE    IMAGE      TRUST    PINS      CREDENTIALS
* kunde-a/network_p  ok       built      ok       current   ok
  kunde-a/platform   blocked  not built  drifted  2 behind  ok
  kunde-b/sandbox    blocked  -          -        -         -

2 of 3 projects need attention:
- kunde-a/platform: devvault trust review -p kunde-a/platform
- kunde-b/sandbox: devvault registry check

The PINS column compares each project's pinned tool versions against the versions
this devvault ships as defaults. It makes no network request: `devvault tools
outdated -p <project>` is the command that asks upstream for the current
releases.

The active project is marked with *, the same way devvault list marks it. STATE is the worst of the four columns and has three values: blocked when devvault would refuse to do the work or the work would fail — a drifted file, missing credentials, a directory that is gone; warn when something is behind or undetermined but nothing is in the way; and ok when all four columns are clean.

devvault status --json calls the same severity missing, because there it describes one check that looked for one thing. Here it folds four unlike concerns into one word, and a project whose compose file drifted is not "missing" anything — so the cross-project table says blocked. The severities are otherwise identical: ok, warn and unknown carry the same meaning in both.

Column Values What it says
IMAGE built, not built, ? Whether the container runtime has the image this project is configured to use. not built is answered by devvault build -p <project>
TRUST ok, stale, drifted, ? Whether the repository-owned files devvault executes are still trustworthy — see below
PINS current, N behind, ? How many pinned tool versions are older than the ones this devvault ships as defaults — an offline comparison, see below
CREDENTIALS ok, no profile, missing, ? Whether the AWS config of this project's credential set exists and holds its profile. It is the same check devvault status runs, so the two can never disagree. The column stays one word and never names a path — devvault status -p <project> is where the detail belongs

Every suggested command names the project it is for, because in a table of eleven projects a bare command would act on the active one — which is rarely the row you were reading. devvault credentials login gets -p <project>; devvault credentials setup, which has no -p, is addressed with --customer <customer> --set <credential set>, taken from your registry entry rather than from the repository's config.

A project usually reports more than one thing, and you get one command for it: the one that unblocks the most. drifted comes first, because devvault refuses to run anything in a container for that project until you have looked at the file — a suggestion to set up credentials would send you to a devvault build that still refuses, for a reason your row never mentioned. Credentials come second: they block the work that talks to AWS, but build and shell run without them. Then stale, which blocks nothing but is worth doing before a build rather than after it; then the image; and last the pins, which cost nothing until the next build.

drifted and stale are different problems and have different fixes. drifted means a file devvault executes — the compose file, the Dockerfile, one of the helper scripts — is neither what devvault would generate nor something you approved. devvault refuses to run anything for that project until you looked at it, so the suggested command is devvault trust review -p <project>. stale means the file still carries your approval, so nothing is blocked, but it no longer follows your config — what a devvault tools bump or a hand-edited .devvault/config.yaml leaves behind. devvault sync -p <project> regenerates it. See Trusted project files for the whole model.

- and ? are not the same answer. ? means devvault tried and could not find out: no container runtime on this host to ask about the image, a trust inspection that failed. - means devvault did not look, because there was nothing to look at — the registered directory is gone, so the row carries - in every column and blocked as its state. devvault registry check is the command for a registration that no longer describes reality.

The PINS column makes no network request. It compares each project's pins against the versions this devvault build ships as defaults, which is why it is free enough to run for every project on every invocation. It is therefore not the upstream check: devvault tools outdated is the command that asks HashiCorp and GitHub what the current releases are, and it is one of only three commands that open a connection at all (see Network access). A pin that is newer than the shipped default is never a finding — running devvault tools bump puts you ahead of the built-in defaults, and that is correct rather than a problem.

A registered project whose .devvault/config.yaml devvault cannot read gets no row at all. Those are listed under the table with the reason, because a missing row must not be read as a healthy one.

The command only reads, and the exit code is always 0. It starts no container, builds nothing and writes nothing; the furthest it goes is asking the container runtime whether an image exists. And "not built yet" is a normal state rather than a failure, so — like devvault status — the report is the result and the exit code is not. If you need something a script can gate on, use devvault registry check, or read devvault overview --json, whose overall field carries the same verdict per project.

devvault context answers "what would devvault do here", without starting anything:

devvault context
devvault context -p kunde-a/network_p --json

It prints customer, project, path, credential set, AWS profile, the path of the AWS config file, the Terraform version and the selected backend — and (not registered) where a credential value would stand for a registered project. It never reads the AWS config file's contents. AWS_ACCOUNT_ID is echoed as aws_account when the environment variable is set.

devvault status is the readiness view: it runs the individual checks, prints each one with its state and a next: hint, and ends with a list of suggested commands.

devvault status
devvault status -p kunde-a/network_p --json

It always reports config, credentials, backend and image. A fifth check, terraform, appears only when there is something to say about it: your project pins a CLI version in .devvault/config.yaml, and the repository states which versions it accepts — in a required_version block, or in a .terraform-version / .opentofu-version file. When the pin does not satisfy that constraint, the container builds fine and then fails minutes later in terraform init, with Terraform's own error. devvault says it first:

[warn] terraform: terraform 1.0.11 does not satisfy ">= 1.5.0" from "versions.tf", so terraform init will fail in the container
  next: run devvault tools bump -p <project> --only terraform for the current release, or set terraform.version in .devvault/config.yaml to a version the constraint allows; then devvault sync and devvault build, because the image keeps the old version until it is rebuilt

It is a warning, never an error — the exit code does not change, and neither does anything else devvault does. Terraform explains the mismatch better than devvault can once it actually runs; the point of checking here is that it runs only after a build of several minutes. For the same reason devvault build prints the identical warning on stderr before it starts building, and builds anyway: you may be building exactly that image on purpose.

The comparison follows terraform.distribution, so an OpenTofu project is measured against terraform.opentofu_version and never against a Terraform release number.

There is deliberately no "ok" line for this check. devvault understands the common constraint syntax — =, !=, >, >=, <, <=, the pessimistic ~>, and comma-separated conditions that all have to hold — with two- or three-component versions, where ~> 1.5 means "any 1.x from 1.5 on" and ~> 1.5.0 means "any 1.5 patch". Anything beyond that — an unknown operator, a prerelease like 1.5.0-rc1, a leading v, a constraint devvault cannot parse — produces no line at all, rather than a green one. A repository that states no version produces no line either. Silence here means "not checked", and never "checked and fine".

devvault version prints the version and build information of the binary itself; devvault --version prints the short form.

Health checks and repairs

devvault doctor probes the host: macOS version, architecture, git, docker, Apple container, the folded "at least one container backend" check, the configured editor, the SSH agent, the credentials mount directory, your project registry and the optional host tools you enabled in tools.*.

It adds one check per Git host that appears in your registry: an SSH remote is probed with ssh -T and reported as git ssh <host>, an HTTPS remote as a reminder to have a credential helper or token configured. Those hosts come from the Git metadata devvault add, devvault clone, devvault setup and devvault quickstart record — a project whose repository has no origin remote contributes none.

devvault doctor
devvault doctor --json
devvault doctor --fix

Architecture, git and the container backend are the required checks, and they alone decide the exit code — a host that fails one of them cannot run devvault at all. Everything else is a warning: a single missing container runtime is normal when the other one is installed, and editor, SSH agent and credentials mount are per-project or convenience concerns.

The project registry line is the same rule applied to devvault's own bookkeeping. It counts what devvault registry check found and points at that command for the detail; it is never a required check, because an inconsistent registry is not a property of this host and must not change whether a script considers the machine usable.

[warn] project registry: 4 problem(s): 3 error(s), 1 warning(s)
  next: run devvault registry check for the findings and the command that fixes each one

The "SSH agent" check also looks at the active backend. With Apple container selected and ssh.forward_agent enabled it warns that the agent cannot be forwarded even though your host has one, because that combination gives you a container without an agent; see Choosing a container backend.

--fix performs only the repairs devvault can make safely and locally — today that is creating a missing AWS credentials mount directory. It never installs software, starts a daemon, touches the network or writes into a repository; for those it keeps printing the hint. After repairing it probes again, so the printed report is the state after the run and not a prediction.

devvault fix is the project-level counterpart:

devvault fix
devvault fix --dry-run
devvault fix --regenerate
devvault fix --json
devvault fix --login

It creates the credentials mount directory when it is missing, reports which of the expected generated files are absent, and with --regenerate writes them again. --regenerate requires a registered project, because regenerating writes the credentials mount into the project's files. --dry-run reports without changing anything, and --login runs the AWS login afterwards.

devvault backend doctor checks the Terraform backend configuration by running terraform init -backend=false inside the container; see The Terraform lifecycle.

Checking the registry itself

devvault doctor checks the host, devvault trust status checks the files a repository ships and devvault status checks whether one project can start. None of them looks at the registry under ~/.devvault — the file that decides, for every project, which directory devvault works in and which credentials it mounts. devvault registry check is that missing check.

devvault registry check
devvault registry check --json

It reports:

Finding Severity Why it matters
missing_path error The registered directory does not exist, cannot be read, or is not a directory. devvault list marks it (missing); here it comes with the way out
duplicate_path error Several entries point at the same directory. Which identity — and with it which credential set gets mounted — then depends on the entry a command happens to name
unreadable_config error The directory is registered but has no readable .devvault/config.yaml, so every command that loads the project fails
dangling_active_project error devvault use selected a project that is no longer registered, so every command without -p fails
alias_collision error Two projects share one alias. devvault refuses an ambiguous key rather than guessing, so the alias stops working for both
missing_git_repository warning The entry records a Git remote, but the directory is no longer a checkout. devvault doctor keeps probing a host this project does not use any more
missing_credential_mount warning The credentials directory of a set that projects use is not on disk, so those projects get a container without an AWS config
orphaned_credential_set warning A credential set no registered project uses, left behind by a project that was renamed or removed

A real run against a registry with several of those:

$ devvault registry check
[error] platform/platform: the registered path "/private/tmp/devvault-demo/code/platform" does not exist; the directory was moved or deleted. Register it again at its new location with devvault add <new-path> --id platform/platform, or drop the entry
  fix: devvault remove platform/platform
[error] /private/tmp/devvault-demo/code/network_p: 2 registry entries point at the same directory "/private/tmp/devvault-demo/code/network_p": gitlab/devvault-test, network_p/network_p. Which identity — and with it which credential set devvault mounts — then depends on the entry a command happens to name
  fix: devvault remove gitlab/devvault-test
[error] network_p: 2 projects share the alias "network_p": gitlab/devvault-test, network_p/network_p. devvault refuses an ambiguous key, so every command naming that alias fails instead of picking one of them
  fix: devvault add /private/tmp/devvault-demo/code/network_p --id network_p/network_p --alias <unique-alias>
[warning] platform/platform: the credentials directory "~/.aws-kunden/platform/platform" of this set does not exist, so the 1 project(s) using it get a container without an AWS config: platform/platform
  fix: devvault fix -p platform/platform

4 problems: 3 errors, 1 warning
devvault changed nothing: each command above removes or rewrites a registration,
which stays your decision.

Once the registry is in order the command says so and exits 0:

$ devvault registry check
registry is consistent

There is no --fix, on purpose. Every command in the report deletes or rewrites a registration, and a registration is where you state which customer and credential set a directory belongs to — the assertion devvault derives the credentials mount from, see Registered projects. devvault names the command and leaves the decision to you. registry check writes nothing at all, not even the registry it just read.

Note that removing a project leaves its credential set in the registry: the files under ~/.aws-kunden are yours, and devvault does not delete them behind your back. The next run reports that set as orphaned_credential_set, which is a note, not damage.

--json prints the same findings as one document, with a stable kind per finding, so a script can act on the categories instead of on wording:

$ devvault registry check --json
{
  "ok": false,
  "errors": 3,
  "warnings": 1,
  "findings": [
    {
      "kind": "missing_path",
      "severity": "error",
      "subject": "platform/platform",
      "projects": [
        "platform/platform"
      ],
      "message": "the registered path \"/private/tmp/devvault-demo/code/platform\" does not exist; the directory was moved or deleted. Register it again at its new location with devvault add <new-path> --id platform/platform, or drop the entry",
      "fix": "devvault remove platform/platform"
    },
    ...
  ]
}

The exit code is 1 as soon as there is any finding, warnings included; see Exit codes.

Moving to another machine

Your registry is the mapping between directories, customers and credential sets, and on a new Mac it starts out empty: every project has to be registered again by hand. Two commands carry that mapping across.

devvault registry export -o registry.yaml       # on the old machine
devvault registry import registry.yaml          # on the new one
What does not travel, and why

The obvious shortcut — copying ~/.devvault/projects.yaml — is the wrong move, and this is the part to read before treating an export as a backup. That file holds five things that are true only on the machine that wrote them, and the export leaves every one of them behind:

Left out Why
git_url and git.url An HTTPS clone URL can carry a token in its userinfo part, so the clone URL never leaves the machine — the same reason devvault list --json does not print it. provider, host, owner, repo and clone_method identify the same remote, and registry import rebuilds the URL from them where it needs one
trust An approval means you looked at that file content, on that machine, and accepted it. Carried over it would pre-approve repository content nobody here has seen. On the new machine everything matching the rendered template is trusted automatically anyway; the rest is meant to be looked at again — see Trusted project files
images Records of images devvault built here. Elsewhere they name images that do not exist, and devvault images prune reads them as "safe to delete"
path and last_opened An absolute path of the old machine says nothing about the new one. Only the last element travels, as directory:, and only as a hint for finding the checkout again; last_opened is dropped without replacement
mount, unless it is portable A credentials mount travels as long as it holds elsewhere: a ~/ path as it is, a path below your home directory rewritten to a ~/ one. A mount that names a directory outside your home — /Users/olduser/.aws-kunden/kunde-a/alpha after you moved once already — is left out, because it would arrive pointing at nothing. Nothing is lost: a credential set without a mount is one devvault derives from customer and credential set, ~/.aws-kunden/<customer>/<set>, which is the directory that exists on the new machine. registry export says how many mounts it left out

And no credential material, in either direction. Profile, credential set and mount names are the mapping and travel with it; nothing below ~/.aws-kunden is ever read or written. Both commands say so in their output, because an export that looked like a backup would be a dangerous thing to believe.

The document

devvault registry export writes YAML to stdout so it can be piped; the note about what is not in it goes to stderr, so a redirected file holds exactly the document. -o <file> writes it directly, with mode 0600. --json writes the same content as JSON — registry import reads either.

$ devvault registry export
kind: devvault-registry-export
format_version: 1
active: kunde-a/alpha
customers:
    - id: kunde-a
      name: Kunde A GmbH
      default_workspace: dev
      credential_sets:
        - id: alpha
          profile: kunde-a-dev
          mount: ~/.aws-kunden/kunde-a/alpha
          auth_method: sso
projects:
    - id: kunde-a/alpha
      customer: kunde-a
      name: alpha
      alias: alpha
      credential_set: alpha
      backend: auto
      directory: alpha
      git:
        provider: gitlab
        host: gitlab.example.com
        owner: kunde-a
        repo: alpha
        clone_method: https

A credentials mount below your home directory is written as a ~/ path, so it lands below the new home rather than repeating the old machine's user name. A mount somewhere else is not exported at all, and the command tells you so:

$ devvault registry export -o registry.yaml
wrote 3 projects and 2 credential sets to registry.yaml
left the mount of 1 credential set out: it is an absolute path outside
your home directory and holds only on this machine, so devvault derives the
mount from customer and credential set on the other one.
This is a mapping, not a backup: it carries no credentials, no trust approvals
and no image records, and devvault neither reads nor writes ~/.aws-kunden.

Without -o that note goes to stderr along with the standing one, so a redirected file still holds exactly the document.

format_version lets a later devvault recognise an older document, and kind lets registry import refuse a file that is not an export — most importantly ~/.devvault/projects.yaml itself, which parses fine and carries exactly the five records above.

Restoring them

devvault registry import <file> looks for each project's checkout below a base directory — by default ~/DevVault, the same one devvault clone puts repositories in, and --base-dir names another one. Per project it tries the repository name from the remote, the directory name the project had, and the project name, each directly below the base directory and once more below a directory named after the customer. A candidate counts only when it is a directory with a readable .devvault/config.yaml.

$ devvault registry import registry.yaml
base directory: /Users/you/DevVault

[registered] kunde-a/alpha: registered "/Users/you/DevVault/alpha"
[not found] kunde-b/beta: no checkout below "/Users/you/DevVault"; devvault registered nothing, because an entry pointing at a directory that is not there is what devvault registry check reports
  next: devvault clone git@github.com:kunde-b/beta.git --id kunde-b/beta

credential sets: kunde-a/alpha
active project: kunde-a/alpha
devvault created no directory below ~/.aws-kunden: devvault status names the
credentials a project is missing, devvault credentials setup writes them.

2 projects: 1 registered, 0 skipped, 1 not found
This is a mapping, not a backup: it carries no credentials, no trust approvals
and no image records, and devvault neither reads nor writes ~/.aws-kunden.

A project without a checkout is not registered. An entry pointing at a directory that does not exist is precisely the missing_path finding of devvault registry check, and devvault does not create the state it reports as broken. The line names the devvault clone command that fetches the repository instead, rebuilt from the Git fields; when a directory of the right name is there but has no devvault config, the report says that and names devvault init plus devvault add instead. Run the import again afterwards — it is repeatable.

An existing registration is never overwritten silently, because a registration is where you state which customer and credential set a directory belongs to, and that decides which host directory gets mounted into a container running that repository's code (see Registered projects). Such a project is reported and skipped; --yes replaces the entry of the same id. Replacing keeps the image records — they are the only handle devvault images prune has on images built here — and keeps a trust approval only while the directory stays the same.

A directory that is already registered under a different id is left alone even with --yes. A second entry for one directory is the duplicate_path finding of registry check: which identity, and with it which credential set devvault mounts, would then depend on the id a command happens to name. The report names devvault remove <other-id> and leaves that decision to you.

Credential sets are restored as mapping only, and only for the projects that were actually registered — importing the set of a project that is not on this machine would leave you with the orphaned_credential_set finding. A set that already exists here is never overwritten: it describes files on this machine. No directory below ~/.aws-kunden is created and no credentials are written; devvault status names what a project is missing and devvault credentials setup writes it.

The active project is only taken over when this registry has none, so an import never moves the project you are working on out from under you.

--dry-run prints the same report and writes nothing, which makes it a usable pre-check. The exit code is 1 as soon as one project was not restored — skipped ones included — in a dry run as well; see Exit codes.

devvault registry import registry.yaml --dry-run                    # what would happen
devvault registry import registry.yaml --base-dir ~/work/repos      # search elsewhere
devvault registry import registry.yaml --yes                        # replace what is there

A document whose values devvault cannot use is refused as a whole rather than imported in halves: a customer, project name, credential set, profile or mount has to pass the same allowlist a repository's config passes, because those values decide which directory gets mounted and reach a container CLI as arguments. The error names the entry and the field.

Credentials

Credential material lives on the host, one directory per customer and credential set, and is mounted into the container:

~/.aws-kunden/<customer>/<credential-set>/config

devvault credentials setup writes such a config file. devvault setup and devvault quickstart call the same code, so this command is mainly for adding a second credential set to an existing customer, or for repairing one.

Guided setup

Run it without the flags it needs and devvault asks, in a terminal:

devvault credentials setup

It asks for the customer and the credential set first — prefilled from the active project, because adding a second set to the customer you are currently working with is the common case — then lets you pick the auth method from a list where each entry explains itself:

Auth method
> sso — browser login via IAM Identity Center, tokens expire on their own
  assume_role — take a role starting from a profile you already have
  credential_process — an external command prints the credentials
  existing — you maintain the AWS config yourself, devvault only mounts it

Only the fields of the chosen method are asked afterwards. Pick sso and you answer a start URL, an SSO region, an account id and a role name; --role-arn never appears. Pick assume_role and you answer the role ARN and the source profile, and no SSO question shows up. existing adds no questions at all — devvault does not write that file, it only mounts the one you maintain. Profile name and region come prefilled (<customer>-<set> and eu-central-1), so the second form is mostly confirmation.

Long-lived access keys are not on the list. credentials setup does not write aws_access_key_id / aws_secret_access_key into a file it manages, so offering that choice would only lead into a refusal.

The guided run ends with one question:

Check these credentials against AWS now?

Answering yes runs the same verification as devvault credentials status: aws sts get-caller-identity with the profile that was just written. This is the only step of the command that talks to AWS, it happens only because you asked for it, and it never happens on the flag-driven path. Say no and devvault names devvault credentials status -p <project> as the next step instead.

Flags, for scripts

Nothing about the flag-driven path changed: with every required flag set the command runs unattended and asks nothing.

devvault credentials setup \
  --customer kunde-a \
  --set network_c \
  --profile network-c-dev \
  --auth assume_role \
  --role-arn arn:aws:iam::123456789012:role/DevAccess \
  --source-profile kunde-a-sso \
  --region eu-central-1
Flag Meaning
--customer Customer id
--set Credential set name
--profile AWS profile
--mount Credentials mount; defaults to ~/.aws-kunden/<customer>/<set>, and replaces --customer / --set when you name it yourself
--auth Auth method: sso, assume_role, credential_process or existing
--sso-start-url, --sso-region, --sso-account-id, --sso-role-name AWS SSO parameters
--role-arn, --source-profile assume_role parameters
--credential-process credential_process command
--region AWS region, default eu-central-1
--force Overwrite an existing config

Without a terminal — in a script, in CI, behind a pipe — an incomplete call is refused instead of opening a form nobody can answer, and nothing is written:

$ devvault credentials setup --customer kunde-a --set network_c --profile network-c-dev </dev/null
devvault credentials setup is missing --auth, and there is no terminal to ask in; pass the flags, or run the command in a terminal for the guided setup
--auth decides what else is needed:
  --auth sso adds --sso-start-url, --sso-region, --sso-account-id, --sso-role-name
  --auth assume_role adds --role-arn, --source-profile
  --auth credential_process adds --credential-process
  --auth existing adds no further flags

When --customer and --set are both given, the credential set is also recorded in your registry, which is what makes it available to projects.

The command prints the path it wrote and never the content of that file — an AWS config names your SSO portal, your account and your role.

Listing, checking and logging in
devvault credentials list                          # every customer
devvault credentials list kunde-a                  # one customer
devvault credentials list --json
devvault credentials status -p kunde-a/network_c   # verify the profile works
devvault credentials login -p kunde-a/network_p    # aws sso login, or the equivalent

credentials status fails with an actionable message when the AWS config file does not exist or does not contain the configured profile, then calls the AWS CLI to verify the identity. credentials login runs the login for the project's auth method. Both refuse for an unregistered directory — they would otherwise have to point the AWS CLI at a config file the repository chose. Neither command ever prints the contents of a credentials file.

Noticing a missing config before Terraform does

You do not have to remember to check. The credentials line of devvault status reports it on every run, without touching the network:

[missing] credentials: AWS config missing: /Users/you/.aws-kunden/kunde-a/network_c/config
  next: run devvault credentials setup or devvault setup

[warn] credentials: profile network-c-dev not found in /Users/you/.aws-kunden/kunde-a/network_c/config
  next: run devvault credentials setup --force or update the AWS config

A file that exists and contains the configured profile reports [ok] with the profile and the path. That is the cheap way to find out that a credential set is missing, or names a different profile than the project expects — before terraform plan runs into it inside the container.

Working in the container

Everything below runs inside the project's development container. The first command builds it:

devvault build          # build the image
devvault shell          # interactive shell inside the container

Before any of these commands starts a container, devvault checks that the project is registered and that the repository-owned files it is about to execute are trusted; see Registered projects and Trusted project files for what a refusal means.

devvault build additionally compares the pinned Terraform version with the required_version the repository declares, and prints a warning on stderr before the build when the two disagree — an image that takes minutes to build and then fails in terraform init is the expensive way to find that out. The warning never stops the build; it is the same check and the same wording devvault status reports, including when it stays silent.

devvault tf runs an arbitrary Terraform command:

devvault tf version
devvault tf fmt -recursive
devvault tf output -json
devvault tf -p kunde-a/network_p providers

The three fixed chains are:

devvault validate       # terraform fmt -check -recursive, init -backend=false, validate, tflint --recursive
devvault lint           # tflint --recursive, plus trivy fs . and checkov -d . when enabled
devvault docs           # terraform-docs markdown table --output-file README.md --output-mode inject .

validate stops at the first failing step. lint adds a scanner only when its switch in tools.* is true, so a minimal project runs tflint alone. docs injects into the README.md of the project directory, between the terraform-docs markers.

Choosing a container backend

backend in .devvault/config.yaml decides which runtime starts the container: auto (the default) prefers Apple container and falls back to Docker Desktop, apple-container and docker pin one of them. devvault doctor prints which one is active. The two are equivalent for everything devvault does inside the container, with one difference that is worth deciding on before you pick:

Only the docker backend forwards your SSH agent. Apple container cannot proxy a host Unix socket into the guest — mounting one does not merely fail to forward the agent, it makes the container fail to boot with proxyVsock: failed to setup vsock proxy — so devvault never mounts the agent socket there, whatever ssh.forward_agent says. In practice that means Git operations run inside the container against a private repository (terraform init pulling a module from a private Git source, a git command in devvault shell) have no credentials on the Apple container backend and will fail. Work that only talks to AWS and the Terraform registry is unaffected.

When ssh.forward_agent is enabled and your host actually runs an agent, devvault says so once per run before it starts the container:

warning: the Apple container backend cannot forward an SSH agent socket, so it is not mounted.
warning: git operations against private repositories will fail inside the container.
warning: use the docker backend for those, or set ssh.forward_agent: false to silence this.

The two ways out are exactly those. Switch the project to Docker Desktop:

# .devvault/config.yaml
backend: docker

or, if you never clone private repositories from inside the container, turn the request off and keep the warning quiet:

# .devvault/config.yaml
ssh:
  forward_agent: false

Run devvault sync and devvault build after changing either key. Cloning and devvault clone are unaffected — those run on the host, with your normal agent.

On the Apple backend the configured container name is a prefix. devvault appends a short random suffix to every container it starts, so apple_container.container_name: devvault-network produces names such as devvault-network-4k7pq2ab. It has to: a single devvault command starts several containers in a row — devvault validate runs four — and Apple container releases the name of a finished --rm container asynchronously. Under one fixed name the next step fails with container with id … already exists, for a container that container list --all no longer shows and container delete no longer finds. Two devvault calls in the same project, one per terminal, would collide on that name outright. The prefix is what you recognise in container list while a run is in progress; if it already uses all 64 characters an identifier allows, devvault shortens it to make room for the suffix. The docker backend is unaffected and uses docker.container_name as written.

The shared provider cache

Both backends mount a persistent Terraform plugin cache at /home/vscode/.terraform.d/plugin-cache and point TF_PLUGIN_CACHE_DIR at it, so the providers terraform init downloads survive the container and are shared by every project of the same customer instead of being fetched again on each run.

This is not only a speed-up, it is what makes the container usable at all: each devvault step starts its own throwaway container, and terraform init leaves in the repository's .terraform/providers only links into that cache. Without a cache that outlives the container, the plugins vanish with the container that downloaded them and the next step — validate, plan, anything — stops with missing or corrupted provider plugins.

Where the cache lives depends on the backend, and both are named by a config key that defaults to devvault-<customer>-tf-cache:

Backend Key Cache
docker docker.plugin_cache_volume a docker volume, declared and mounted by the generated compose file
apple-container apple_container.plugin_cache_name a directory below your devvault home: ~/.devvault/plugin-cache/<name>, created with mode 0700 and bind-mounted

The Apple backend uses a host directory rather than a container volume on purpose. Apple container creates a named volume as an empty, root-owned filesystem and — unlike docker — does not seed it from the image, so the container's non-root vscode user cannot write into it; a volume would need a --user root container just to fix its ownership. A bind-mounted directory is presented to the guest as owned by the container's user, which is why the workspace mount works, and it leaves the cache visible on your host.

devvault creates the cache if it does not exist yet, so there is nothing to set up.

devvault cache list

A cache only ever grows: every provider version an init pulls is added, nothing is ever removed. cache list shows what that costs and who still needs it.

devvault cache list
devvault cache list --json
cache directory: /Users/dev/.devvault/plugin-cache
NAME                        SIZE       USED BY
devvault-altkunde-tf-cache  17.0 MiB   (orphaned)
devvault-kunde-a-tf-cache   733.2 MiB  kunde-a/network_p, kunde-a/platform
total: 750.2 MiB in 2 caches

docker volumes named by registered projects for the same cache:
  docker volume rm devvault-kunde-a-tf-cache   # used by kunde-a/network_p
devvault does not manage these: the volume belongs to docker, and `docker volume rm`
is the command that removes one.

USED BY lists the registered projects whose config names that cache. A cache no registered project names — the customer you stopped working for, a project you removed from the registry — is (orphaned) and is what cache prune removes by default. The reference is counted whatever backend a project is configured for: both config keys carry the same name, backend: auto decides per host, and the harmless mistake is to keep a cache nobody needs rather than delete one a project would have used.

Reading that list means reading the config of every registered project. A project whose directory moved away, or whose config no longer parses, is reported as skipped instead of aborting the command — with the note that a cache it uses can then look orphaned:

skipped 1 registered project(s) whose config devvault could not read:
  kunde-a/altprojekt: read config: no file found in any of [...]
a cache one of them uses can therefore look orphaned.
devvault cache prune
devvault cache prune                 # only the orphaned caches
devvault cache prune --yes           # ... without the confirmation prompt
devvault cache prune --all --yes     # every cache, including the ones in use
devvault cache prune -p network_p --yes   # exactly the cache that project uses
devvault cache prune --json --yes

Without a flag devvault deletes only the orphaned caches. --all takes the ones in use as well, and -p <project> exactly the cache that project's config names; the two cannot be combined, because together they would have to mean one thing or the other. Every run lists what it is about to delete and how much that frees before it deletes anything:

devvault will delete 1 cache from /Users/dev/.devvault/plugin-cache:
  devvault-altkunde-tf-cache  17.0 MiB  (orphaned)
this frees 17.0 MiB
removed /Users/dev/.devvault/plugin-cache/devvault-altkunde-tf-cache (17.0 MiB)
freed 17.0 MiB
the next terraform init downloads those providers again, so the first run after
a prune is slower than usual

Deleting a cache cannot be undone, so prune asks before it does it, and without a terminal to ask in it refuses:

devvault cache prune deletes 1 cache (17.0 MiB) and cannot be undone; rerun with --yes to confirm

Nothing is lost either way — the next terraform init downloads the providers again — but that run is slow, and a slow run you did not expect looks like a fault. That is why the command says so afterwards.

A devvault home that has never run a container has no cache directory at all. That is not an error: cache prune reports that there is nothing to clean up and exits 0, so it is safe in a script.

Docker volumes are listed, never deleted. On the docker backend the cache is a docker volume, and that volume is docker's state, not devvault's — docker has docker volume rm for the job, and devvault does not delete another tool's data. Staying quiet about them would be worse, though: a prune that silently ignored half your caches would be misleading. So both commands name every volume a registered project asks for, together with the exact command that removes one. devvault does not call docker to do that — the names come from the configs of your registered projects, so the list works with docker stopped or absent.

The images devvault builds

Every devvault build produces a container image of a gigabyte or more, and nothing ever removes one. Rename a project, change docker.image_name, and the next build produces a second image while the first stays on disk under the old name — invisible unless you go looking for it with container image list. devvault images is where you look.

devvault deletes only images it recorded having built itself. This is the one rule the whole command is built around, and it is what makes it different from cache prune. The provider cache lives in devvault's own directory below ~/.devvault, so everything in there is devvault's by construction. Images do not: they live in the shared storage of the container runtime, next to a database image, a base image somebody pulled, another tool's build output.

Recognising an image by its name is not good enough, however much the devvault- prefix invites it. docker.image_name and apple_container.image_name are ordinary config keys that any repository may set to anything, so a name that looks like devvault's may belong to somebody else — and a wrong match deletes work that cannot be recovered. So after a successful build devvault writes the image name into that project's entry in ~/.devvault/projects.yaml:

projects:
    kunde-a/network_p:
        path: /Users/dev/code/network_p
        images:
            - devvault-netzwerk-prod

That record is the only evidence devvault accepts. It is also what catches the rename: the old name stays in the list while the config already names a new one, which is exactly what "orphaned" means below.

devvault images list
devvault images list
devvault images list --json
IMAGE                   PROJECT            RUNTIME          SIZE       STATE
devvault-netzwerk-prod  kunde-a/network_p  apple-container  185.5 MiB  in use
devvault-platform       kunde-a/platform   apple-container  -          not built yet
devvault-altkunde-net   kunde-a/platform   apple-container  1.6 GiB    orphaned
1 image orphaned, 1.6 GiB reclaimable with `devvault images prune`

devvault lists the images of its registered projects and the images it recorded
building. Everything else in the container runtime belongs to somebody else and
never appears here — use `container image list` or `docker image ls` for those.

One line per registered project for the image its config names, plus one for every image devvault recorded that no project names any more. Rows are grouped by project; the configured image comes first, the leftovers after it.

RUNTIME is the backend that project resolves to on this host, and it decides which of the two config keys is shown: apple_container.image_name or docker.image_name. Sizes come from container image inspect (summed over the variants of a multi-architecture image) and docker image inspect.

SIZE is never invented: - means the runtime answered that the image is not there, and unknown that devvault could not ask or got no number back. STATE is the answer to "may I delete this":

STATE Meaning
in use A registered project's config names this image, and devvault built it
orphaned devvault built it, no registered project names it any more — this is what prune removes by default
not built yet The project's configured image, which devvault has not built
not recorded, devvault will not delete it The image exists, but devvault has no record of building it. See the limit below
..., already gone devvault holds a record for an image the runtime no longer has; prune clears the record
no container runtime available No backend is reachable on this host, so nothing could be asked. prune refuses these rather than assume they are gone

An image name is counted as in use whatever backend the naming project is configured for: both config keys normally carry the same name, backend: auto decides per host, and the harmless mistake is to keep an image nobody needs rather than delete one a project would have used. Reading that means reading the config of every registered project, and a project whose directory moved away or whose config no longer parses is reported as skipped instead of aborting the command — with the note that an image it uses can then look orphaned.

devvault images prune
devvault images prune                     # only the orphaned images
devvault images prune --yes               # ... without the confirmation prompt
devvault images prune --all --yes         # every recorded image, including the ones in use
devvault images prune -p network_p --yes  # every image recorded for that project
devvault images prune --json --yes

Without a flag devvault deletes only the orphaned images. --all takes the ones in use as well, and -p <project> every image devvault recorded for that project; the two cannot be combined, because together they would have to mean one thing or the other. Every run lists what it is about to delete and how much that frees before it deletes anything:

devvault will delete 1 image:
  devvault-altkunde-net  kunde-a/platform  1.6 GiB  orphaned
this frees 1.6 GiB
removed devvault-altkunde-net (1.6 GiB) from apple-container
freed 1.6 GiB
the next devvault build builds these images again, which takes minutes rather
than seconds

Deleting an image cannot be undone, so prune asks before it does it, and without a terminal to ask in it refuses:

devvault images prune deletes 1 image (1.6 GiB) and cannot be undone; rerun with --yes to confirm

Nothing is lost either way — the next devvault build builds the image again — but that build takes minutes rather than seconds, which is why the command says so afterwards.

After the runtime confirms the deletion, devvault drops the record from ~/.devvault/projects.yaml, so the entry never outlives the image it described. The order matters: the record is the evidence that the image is devvault's, so it goes last. If the runtime refuses — a container is still using the image — devvault reports that image, keeps its record and carries on with the rest; the command then exits 1 while everything it could delete is gone:

removed devvault-platform (1.6 GiB) from docker
freed 1.6 GiB
remove the docker image "devvault-netzwerk-prod": exit status 1: Error response from daemon: conflict: unable to remove repository reference "devvault-netzwerk-prod" (must force) - container 9a1 is using its referenced image

devvault never passes --force or -f to the runtime. Forcing would remove an image a container is still running on, and that container is not devvault's to break — stop it yourself and rerun.

What this cannot find

Images built before devvault recorded them are invisible to this command. Anything devvault build produced under 0.4.1 or older has no entry, so images list does not show it and images prune does not remove it. There is deliberately no name-pattern fallback to paper over the gap — guessing "this looks like ours" is exactly the mistake that costs somebody an unrelated image. Find and remove those by hand:

container image list                       # Apple container
container image delete devvault-old-name

docker image ls                            # docker
docker image rm devvault-old-name

A rebuild also fixes it for the current name: the next devvault build records the image it produces, so from then on the project's own image is tracked.

The same blind spot applies to devvault remove: dropping a project from the registry drops its image records with it. Run devvault images prune -p <project> --yes before unregistering a project whose image you no longer want, or clean it up by hand afterwards.

The Terraform lifecycle

plan
devvault plan
devvault plan -var-file=prod.tfvars -target=module.network
devvault plan --save
devvault plan --save --plan-file .devvault/plans/prod.tfplan

--save runs terraform plan -out=<file> and defaults the file to .devvault/plans/latest.tfplan; --plan-file overrides the location. Both are devvault's own flags, and like every other flag devvault interprets they stop at a --: devvault plan -- --save reaches Terraform as terraform plan --save and writes no plan file (see Flags that apply almost everywhere).

A saved plan regularly contains secrets in plaintext — resolved variables and provider responses end up in the file. devvault therefore creates the plan directory with mode 0700, drops a .gitignore into it that ignores everything except itself, and restricts the written plan file to 0600. An existing directory keeps its mode and an existing .gitignore is left untouched, so a --plan-file pointing somewhere else is not silently re-permissioned; the plan file itself is restricted either way. If devvault cannot tighten the permissions, or no plan file appears at all, it says so on stderr instead of failing silently. Never commit a plan file and never hand one out.

apply, apply-plan, destroy, import
devvault apply --yes
devvault apply --yes -target=module.network
devvault apply-plan --yes
devvault apply-plan --yes --plan-file .devvault/plans/prod.tfplan
devvault apply-plan --yes --max-plan-age 2h
devvault apply-plan --yes --allow-stale-plan
devvault destroy --yes
devvault import --yes aws_s3_bucket.logs my-log-bucket

All four require --yes; see Flags that apply almost everywhere. Before running, they print the project id, customer, workspace, AWS profile and credential set, so the account you are about to change is visible in the output and in CI logs.

apply-plan applies a plan saved with plan --save and defaults to the same .devvault/plans/latest.tfplan. Delete the plan file once it has been applied. If there is no plan at that path, devvault says so and names devvault plan --save instead of starting a container that fails on a missing file.

Why devvault checks how old a saved plan is

A saved plan is self-contained. terraform apply plan.out never re-reads the .tf files: everything it will do was decided when the plan was made. Terraform catches half of the danger by itself — if the state moved on, it refuses the plan. What it never notices is that the configuration changed. Edit a module, forget to re-plan, apply yesterday's file, and Terraform applies yesterday's intent against today's code without a word of warning. That is the case devvault can see and Terraform cannot, so apply-plan looks at the plan file before it hands it to the container and refuses a stale one:

  • the plan is older than --max-plan-age, which defaults to 24h; or
  • a Terraform file is newer than the plan — any *.tf, *.tf.json, *.tfvars, *.tfvars.json or .terraform.lock.hcl in the project whose modification time lies after the plan's. .git/, .terraform/, .devvault/ and the directory the plan itself lives in are not looked at: they hold Git and Terraform machine state and devvault's own generated files, none of which change what a plan would do.

The refusal names both — how old the plan is and which files overtook it, at most five of them plus an and N more — and it names the way out:

$ devvault apply-plan --yes
the saved plan .devvault/plans/latest.tfplan is 48h0m1s old, past the 24h0m0s
limit of --max-plan-age, and 1 Terraform file(s) changed after it was written:
"main.tf".

terraform apply never re-reads the Terraform files: it applies the plan as it
was made, so a configuration that changed in the meantime is applied as it
looked back then. Terraform refuses a plan whose state moved on, but it cannot
see this.

Make a current plan:

  devvault plan --save

Or apply this one anyway, if you are sure the difference does not matter:

  devvault apply-plan --yes --allow-stale-plan

--max-plan-age sets the age limit for a single run; --max-plan-age 0 turns the
age check off and leaves the check for newer Terraform files in force.

--allow-stale-plan applies the plan anyway. devvault then prints the same sentence as a warning on stderr instead of refusing, before the customer and account block, so the record of what you overrode stays in the terminal and in CI logs.

--max-plan-age <duration> takes a Go duration (90m, 2h, 36h) and sets the age limit for that one run. --max-plan-age 0 switches the age check off — and only that half: a Terraform file that is newer than the plan still stops the run, because that check is about the plan not matching the code rather than about the plan being old. Use it for a plan that is deliberately kept for a while, for instance one made in CI and approved by a human hours later.

--yes does not double as the opt-in. The two flags answer different questions: --yes says "I know this changes infrastructure", --allow-stale-plan says "I know this plan may no longer match the code". Passing --yes alone therefore still gets the refusal above.

The age limit is a flag and deliberately not a key of .devvault/config.yaml. That file belongs to the repository, and devvault treats every cloned repository as untrusted input (see Registered projects). A repository that could write plan.max_age: 8760h would switch this protection off for the very person it protects, quietly, in the same file that describes the plan it wants applied. Flags come from you, config comes from the repo — so the limit lives on the command line.

workspace and state
devvault workspace list
devvault workspace show
devvault workspace select prod
devvault state list
devvault state pull > state.json
devvault state mv --yes aws_s3_bucket.old aws_s3_bucket.new
devvault state rm --yes aws_s3_bucket.legacy

state mv and state rm change state and therefore require --yes; state list and state pull do not. All of them forward their remaining arguments to Terraform unchanged.

backend, modules and graph
devvault backend doctor          # terraform init -backend=false
devvault modules doctor          # terraform fmt -check -recursive, then tflint --recursive
devvault modules changelog       # prints the current changelog guidance
devvault graph                   # terraform graph | dot -Tsvg > terraform-graph.svg

graph needs Graphviz in the image. When tools.graphviz is false, devvault says which switch is off and that the image has to be rebuilt, instead of letting the container fail with a bare "not found". modules changelog currently prints guidance only; module changelog automation is not implemented.

Security and quality

devvault scan            # the enabled scanners, in one run
devvault secrets scan    # gitleaks detect --source .
devvault policy check    # conftest test .
devvault precommit install
devvault precommit run   # pre-commit run --all-files

devvault scan runs trivy config . when tools.trivy is true, checkov -d . when tools.checkov is true and gitleaks detect --source . when tools.gitleaks is true. A project with none of the three enabled falls back to tflint --recursive, so the command never silently does nothing.

secrets scan, policy check and precommit each depend on exactly one tool and refuse up front when its switch is false, naming the switch and pointing at devvault build. secrets scan forwards extra arguments to gitleaks:

devvault secrets scan --redact
devvault secrets scan -p kunde-a/network_p --report-format json

For the difference between the container tools and the host tools, and for what tools.preset seeds, see Configuration.

Customer-wide workflows

devvault customer runs one workflow across every registered project of a customer, in alphabetical order by project name, printing ==> <customer>/<project> before each. It stops at the first project that fails.

devvault customer list                 # the customers in your registry
devvault customer doctor kunde-a       # trusted config loads and a backend exists
devvault customer validate kunde-a     # the validate chain in every project
devvault customer scan kunde-a         # the enabled scanners in every project
devvault customer docs kunde-a         # terraform-docs in every project

customer doctor is the only one that starts nothing: it loads each project's trusted configuration and checks that a container backend is available, which makes it a cheap way to find a project whose registration or config broke. customer validate, customer scan and customer docs run in the container and therefore need every project of that customer to be registered and trusted. customer scan runs exactly the chain devvault scan runs, per project and from that project's own tools.* switches, so a customer whose projects enable different scanners still gets the right ones.

Editor integration

devvault open                                          # current directory
devvault open /path/to/repo
devvault open kunde-a                                  # a registered project
devvault open git@github.com:example/platform.git      # clone, prepare, register, open
devvault open --editor vscode
devvault ij                                            # switch between open IntelliJ projects

open accepts a path, a registered project's id or alias, or a Git URL — a Git URL is cloned and prepared like devvault clone --open. The editor comes from editor.default in the project's config and can be overridden per call with --editor: intellij, vscode, cursor, codex or custom. IntelliJ, VS Code and Cursor are located by their CLI on PATH and, failing that, at their standard /Applications path. When no editor can be found, open prints the reason and the path to open manually instead of failing.

devvault ij lists the currently open IntelliJ projects and switches between them.

--allow-custom-editor. The codex and custom editors are the only ones whose executable comes from editor.command in the repository's config file. That file is repository-owned, so honoring it without asking would let a cloned repository choose a program that devvault starts on your host. devvault therefore refuses by default:

custom editor uses a configured command; rerun with --allow-custom-editor only for trusted repositories

The opt-in exists on devvault open and devvault clone. Pass it only for repositories you trust, after looking at editor.command and editor.args. Even with the opt-in, the command must resolve to an executable file — either on PATH or as an executable path.

Shell integration

devvault cd prints a registered project's path so a shell can change into it:

cd "$(devvault cd kunde-a)"
cd "$(devvault cd)"          # the active project

A shell function makes it a one-word command:

dv() { cd "$(devvault cd "$@")" || return; }

devvault completion generates the completion script:

devvault completion zsh > "$(brew --prefix)/share/zsh/site-functions/_devvault"
devvault completion bash > "$(brew --prefix)/etc/bash_completion.d/devvault"
devvault completion fish > ~/.config/fish/completions/devvault.fish
devvault completion powershell

devvault completion <shell> --help prints the exact installation steps for that shell, including the one-off compinit line for zsh and the bash-completion dependency for bash. All four subcommands accept --no-descriptions to leave the per-flag descriptions out of the generated script. Release archives already contain the generated scripts under completions/.

Terraform or OpenTofu

OpenTofu is a first-class distribution, not a compatibility mode. It has its own version pin, and the container installs the tofu binary — verified against the release checksums and the OpenTofu GPG signature — plus a terraform symlink pointing at it, because a lot of tooling calls terraform unconditionally.

devvault setup customer --distribution opentofu --customer kunde-a --repo /path/to/repo

devvault setup detects the distribution from the repository when --distribution is not given: a .opentofu-version file or *.tofu sources mean OpenTofu. In the config it is terraform.distribution, and the pinned version comes from terraform.opentofu_version instead of terraform.version.

Every command that runs Terraform uses the configured distribution's CLI name, so in an OpenTofu project devvault tf plan runs tofu plan and devvault validate validates with tofu.

Trusted project files

devvault runs files that live in the repository: .devvault/docker-compose.yml decides mounts and privileges, .devvault/Dockerfile decides build steps, and .devvault/scripts/* are executed by editors and CI. devvault generates those files itself, and before it runs anything in a container it checks that they still match what it would generate from your config — or that you approved their current content. The fallback locations a backend would otherwise pick up, ./docker-compose.yml and .devcontainer/Dockerfile, are covered as well, and so is any unexpected entry inside .devvault/scripts/.

A repository that ships its own version of one of these files stops the command with an explanation instead of running it:

devvault trust status -p kunde-a/network_p    # per-file state, exit code 1 when untrusted
devvault trust review -p kunde-a/network_p    # diff against what devvault would generate
devvault sync -p kunde-a/network_p            # regenerate the managed files
devvault trust accept -p kunde-a/network_p    # approve the current content on purpose

Approvals are stored in your own registry under ~/.devvault, never in the repository, so a repository can never approve itself. An approval says one thing only: this file has not changed since you agreed to it. It does not say the file still matches your config, and it never expires when the config moves on — trust status reports that separately, and devvault sync is the way back. devvault sync is also how you refresh generated files after a devvault update; --dry-run shows what would change.

Drift in .pre-commit-config.yaml, the CI workflows and .devvault/config.yaml is reported but does not block — those files do not decide what devvault executes.

trust status prints one row per managed file with its state, whether it is enforced and whether it still matches your config, and ends with trusted: yes|no. For an unregistered directory it says so explicitly: devvault does not know which customer and credential set the project belongs to, so it cannot render the expected content at all, and the differences it shows are inconclusive rather than evidence of tampering. Register the project first.

FILE                          STATE  ENFORCED  CONFIG
.devvault/docker-compose.yml  ok     yes       differs

The STATE and CONFIG columns answer two different questions, and they come apart after a config change:

  • STATE is the security verdict: has anything changed since devvault generated the file or since you approved it? Only this decides whether devvault refuses to run something. An approval means "unchanged since you agreed to it" — it does not mean "still matches your config".
  • CONFIG is the consistency one: is the file byte-identical to what devvault would generate from your config right now? match, differs, or - for a file devvault has no template for — a missing file, a fallback such as ./docker-compose.yml, or an unexpected entry in .devvault/scripts/.

So ok plus differs is the normal state after devvault tools bump or a hand-edited .devvault/config.yaml: nobody touched the generated files, they just fell behind. Nothing is blocked, the exit code stays 0, and trust status ends with a note pointing at devvault sync, which regenerates them. Leaving it that way is not a security problem, but it is a real one: the two backends read the pins from different places — the Apple container backend from the config, the docker backend from the compose file on disk — so the same project can build two different images until you sync. trust review lists these files too, marked approved, no longer matches your config, with the diff devvault sync would apply.

trust accept approves what is on disk right now, which means devvault will run those repository-owned files unchanged. It asks for confirmation, and without a terminal it refuses unless --yes is given. Run trust review first.

devvault sync rewrites the compose file, the Dockerfile, the helper scripts and the CI files from the config devvault trusts, and records the result as trusted:

devvault sync
devvault sync --dry-run
devvault sync -p kunde-a/network_p --json

Keeping the pinned tool versions current

A project pins exact versions for Terraform or OpenTofu, tflint, terraform-docs, Trivy, OPA, Checkov, pre-commit, gitleaks, conftest and Infracost, and every download in the generated Dockerfile is verified against the checksums published for exactly that version. A pin is kept whether or not the tool is switched on, so enabling one later does not silently install an old release. A stale pin is a security problem, not a cosmetic one:

devvault tools outdated -p kunde-a/network_p   # exit code 1 when something is outdated
devvault tools outdated --json
devvault tools bump --dry-run                  # show the diff without writing
devvault tools bump --only terraform --yes     # bump one tool

--only takes one of terraform, opentofu, tflint, terraform-docs, trivy, opa, checkov, pre-commit, gitleaks, conftest or infracost.

tools outdated reports a tool whose upstream could not be reached as unknown, with the reason on stderr; that neither fails the command nor hides the other results.

tools bump rewrites only the version keys in .devvault/config.yaml, keeping comments, key order and every other value, and reloads and validates the result before it returns — if anything but the pins changed, the previous content is restored. It asks before writing, and without a terminal it refuses unless --yes is given, because it edits a file in your repository. Afterwards run devvault sync (the generated Dockerfile and CI files embed those versions), review and re-approve the changed files if the project was approved, and devvault build to rebuild the container.

Updating devvault

devvault update --check   # report only; exit code 1 when an update is available
devvault update
devvault update --yes     # skip the interactive confirmation
devvault update --force   # update even from a dev build, or to a version that is not newer

update downloads the current release from GitHub, always verifies its SHA-256 checksum against that release's checksums.txt, and verifies checksums.txt itself with cosign when cosign is installed — without cosign you get a loud warning that only the checksum was checked. The running binary is then replaced atomically. devvault never checks for updates on its own; this command is the only place it looks. A devvault that Homebrew manages is refused, because replacing it would break that installation; the check follows symlinks, so a binary you installed into /opt/homebrew/bin yourself is still updated.

After updating, run devvault sync in your projects so the generated files match the new devvault.

Exit codes

devvault exits 0 on success and 1 on any error; the error is printed on stderr. A failing Terraform run inside the container is an error like any other, so devvault exits 1 and does not forward Terraform's own exit code.

Six commands use the exit code as a result rather than as an error, which makes them scriptable:

Command Exit code 1 means
devvault doctor A required host check failed: architecture, git, or "at least one container backend is available". The project registry line is not one of them and never changes this exit code
devvault registry check The registry has at least one finding. Warnings count too: the command exists to report problems, and --json carries the split into errors and warnings for a caller that only cares about one of them
devvault registry import At least one project of the document was not restored. A skipped one counts: it is not registered the way the document describes it. --dry-run reports the same, so it can gate a script before anything is written
devvault trust status The project is not trusted — or it is not registered, in which case devvault cannot determine the expected content at all. A file that only fell behind the config (CONFIG is differs) is a hint, not an error, and keeps the exit code at 0
devvault tools outdated At least one pinned tool has a newer upstream release
devvault update --check An update is available

Each of them still prints its full report before exiting, and --json output is written before the non-zero exit, so a script can read both.

Network access

devvault tools outdated, devvault tools bump and devvault update are the only commands in which devvault itself opens a network connection, and only when you run them explicitly. They use HTTPS and talk to endpoints that are compile-time constants: the HashiCorp releases API, the GitHub API and GitHub releases. GITHUB_TOKEN or GH_TOKEN raises GitHub's anonymous rate limit. There is no background check, no check at startup, and no telemetry.

Other commands can of course still cause traffic through the tools they run — git while cloning, the container backend while building an image, Terraform while talking to a provider, and the AWS CLI whenever a command verifies a profile: credentials status, credentials login, and the guided credentials setup if you answer yes to its closing question.

Configuration

.devvault/config.yaml is generated by devvault init, devvault setup and devvault sync. It is repository-owned and therefore validated strictly: every value that ends up as an argument of a docker or container invocation must match an allowlist, so a value cannot be read as a flag. An invalid value is rejected when the config is loaded rather than passed on.

Key Default Values
backend auto auto, apple-container, docker; see Choosing a container backend
project directory name, sanitized identifier
customer same as project identifier
terraform.version 1.15.8 major.minor.patch; checked against the repository's required_version, see below
terraform.opentofu_version 1.12.5 major.minor.patch, used when the distribution is opentofu; checked the same way
terraform.distribution terraform terraform, opentofu
tools.preset standard minimal, standard, full
tools.tflint 0.64.0 major.minor.patch
tools.terraform_docs 0.24.0 major.minor.patch
tools.trivy_version 0.72.0 major.minor.patch
tools.opa_version 1.18.2 major.minor.patch
tools.checkov_version 3.3.8 major.minor.patch
tools.precommit_version 4.6.1 major.minor.patch
tools.gitleaks_version 8.30.1 major.minor.patch
tools.conftest_version 0.68.2 major.minor.patch
tools.infracost_version 0.10.45 major.minor.patch
tools.trivy from tools.preset container tool: installed into the image only when true
tools.checkov from tools.preset container tool: installed into the image only when true
tools.gitleaks from tools.preset container tool: installed into the image only when true
tools.conftest from tools.preset container tool: installed into the image only when true
tools.opa from tools.preset container tool: installed into the image only when true
tools.precommit from tools.preset container tool: installed into the image only when true
tools.graphviz from tools.preset container tool: installed into the image only when true
tools.age from tools.preset container tool: installed into the image only when true
tools.infracost from tools.preset container tool: installed into the image only when true
tools.awscli from tools.preset host tool: an optional devvault doctor check
tools.sops from tools.preset host tool: an optional devvault doctor check
tools.gpg from tools.preset host tool: an optional devvault doctor check
tools.session_manager_plugin from tools.preset host tool: an optional devvault doctor check
aws.profile <customer>-dev letters, digits, ., _, -
aws.credentials_mount ~/.aws-kunden/<customer>/<credential-set> absolute path or ~/…, no .. segments
aws.credential_set same as project identifier
aws.auth_method existing sso, assume_role, credential_process, existing, static
aws.readonly true mounts the credentials directory read-only
docker.image_name devvault-<project> identifier
docker.container_name devvault-<project> identifier
docker.platform auto auto, linux/amd64, linux/arm64
docker.plugin_cache_volume devvault-<customer>-tf-cache identifier; names the docker volume holding the shared Terraform provider cache, see The shared provider cache
apple_container.image_name devvault-<project> identifier
apple_container.container_name devvault-<project> identifier; the prefix of the container name — devvault appends a per-run suffix, see Choosing a container backend
apple_container.plugin_cache_name devvault-<customer>-tf-cache identifier; names the same cache for the Apple container backend, where it is a directory ~/.devvault/plugin-cache/<name> and not a volume, see The shared provider cache
editor.default intellij intellij, vscode, cursor, codex, custom
editor.command empty only used behind --allow-custom-editor
editor.args empty only used behind --allow-custom-editor
ssh.forward_agent true offer the host SSH agent socket to the container; docker backend only, see Choosing a container backend

An identifier is lowercase letters, digits, ., _ and -, must start and end alphanumeric, and is at most 64 characters. A version must be exactly major.minor.patch — 1.4 is rejected, 1.4.0 is not. aws.profile follows the same shape as an identifier but also allows uppercase letters.

terraform.version — or terraform.opentofu_version, depending on terraform.distribution — is what the container installs, and it is independent of what your Terraform code demands. Those two can drift apart: a project with required_version = ">= 1.5.0" and a pin of 1.0.11 builds an image that then fails in terraform init. devvault status compares the two and warns when they disagree, and devvault build prints the same warning before it starts building; see Selecting and inspecting projects for the exact output, the constraint syntax devvault understands, and why a constraint it cannot read produces no line at all.

The tool switches fall into two groups, and the group decides where a tool lives. Container tools — trivy, checkov, gitleaks, conftest, opa, precommit, graphviz, age, infracost — are installed into the generated image exactly when their switch is true. A switch that is false keeps the binary out of the image, and the command that would call it says so instead of failing inside the container. Host tools — awscli, sops, gpg, session_manager_plugin — are never installed by devvault, because they authenticate you or talk to your own keyring; their switch turns them into an optional check in devvault doctor. The Terraform or OpenTofu distribution, tflint, terraform-docs and the base packages have no switch: they are what the container exists for and are always installed.

tools.preset seeds those switches when the config is loaded, so it is what decides how large the image gets: minimal enables awscli only, standard adds trivy, gitleaks, graphviz, sops and gpg, and full enables all of them. A switch written explicitly in the same file wins over the preset — and a config generated by devvault init, devvault setup or devvault sync writes every switch explicitly, so changing only preset: in such a file changes nothing. Edit the switches themselves, then run devvault sync (the Dockerfile is generated from them) and devvault build.

The credential-relevant values — aws.credentials_mount, aws.profile and aws.auth_method — are never read from the repository's config file. For a registered project they come from your own registry; for an unregistered one devvault determines none of them and refuses to run anything in the container (see Registered projects). See SECURITY.md for the full trust boundary.

Support

Run devvault doctor to check host tools, backend availability, and project readiness. This project is macOS-first; Linux and Windows hosts are not release targets for v0.1.0.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
images
Package images owns the host side of the container images devvault builds: which image a registered project is configured to use, which images devvault recorded having built, whether they still exist in the container runtime, what they cost on disk, and what it takes to delete one.
Package images owns the host side of the container images devvault builds: which image a registered project is configured to use, which images devvault recorded having built, whether they still exist in the container runtime, what they cost on disk, and what it takes to delete one.
overview
Package overview answers one question across every registered project: which of them need attention.
Package overview answers one question across every registered project: which of them need attention.
planfile
Package planfile keeps saved Terraform plans out of the repository and out of other users' reach.
Package planfile keeps saved Terraform plans out of the repository and out of other users' reach.
plugincache
Package plugincache owns the host side of the shared Terraform provider cache: where a cache directory lives, how large it is, which registered projects use it, and what it takes to delete one.
Package plugincache owns the host side of the shared Terraform provider cache: where a cache directory lives, how large it is, which registered projects use it, and what it takes to delete one.
registrycheck
Package registrycheck inspects the user-owned devvault registry for entries that no longer describe reality.
Package registrycheck inspects the user-owned devvault registry for entries that no longer describe reality.
registrytransfer
Package registrytransfer carries the project registrations of a devvault registry from one machine to the next.
Package registrytransfer carries the project registrations of a devvault registry from one machine to the next.
selfupdate
Package selfupdate replaces the running devvault binary with the current release from GitHub.
Package selfupdate replaces the running devvault binary with the current release from GitHub.
toolversions
Package toolversions compares the tool versions a devvault project pins in its .devvault/config.yaml against the current upstream releases, and rewrites those pins in place.
Package toolversions compares the tool versions a devvault project pins in its .devvault/config.yaml against the current upstream releases, and rewrites those pins in place.
trust
Package trust decides whether the devvault-managed files inside a repository may be used to run something on the host.
Package trust decides whether the devvault-managed files inside a repository may be used to run something on the host.
version
Package version exposes the build information the running binary was stamped with.
Package version exposes the build information the running binary was stamped with.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL