vk dev runs a project's development environment in microVMs, using a tracked
.virtkit/config.toml. The same configuration drives command execution, interactive
shells, VS Code, services, published ports, lifecycle hooks and project tasks.
The environment stays running between commands; each worktree and named environment
has its own state and identity.
This guide covers setup, daily use and the configuration reference, with examples of a development VM and on-demand services. For the underlying compose dialect,
see Run compose services. The machine-wide
virtkit config is a separate file: --dev-config selects a project config,
whereas --config selects virtkit's host configuration.
- Quick start
- Configuration and local overrides
- Images, builds and resources
- Daily workflow and freshness
- Compose services and endpoints
- Workspace, mounts and storage
- VS Code and SSH
- Lifecycle hooks
- Project tasks
- Host integration
- Importing a devcontainer
- Troubleshooting
- Command reference
Install vk following the installation instructions.
You need an x86-64 Linux host and read/write access to /dev/kvm. Pulling images
requires network access. Using an installed vk does not require Docker.
From a trusted project checkout:
vk dev init
vk dev plan
vk dev doctor
vk dev shellinit validates an existing config, including its local overrides. Otherwise it
looks for a devcontainer definition, a root compose file, then a Dockerfile;
without any of these it writes a stock-image template. It reports what it
translated and what needs attention. A draft requiring an essential decision is
still written, but the command exits 1. Review and complete it before booting.
Importing runs no hooks, downloads nothing and boots nothing.
To start explicitly from an image:
vk dev init --from image --image docker.io/library/debian:13A minimal standalone config is:
schema = 1
[requires]
min-version = "0.64.0"
[dev]
image = "docker.io/library/debian:13"
workspace = "/workdir"
user = "root"This gives you the image's tools and the checkout at /workdir. Add your project's
toolchain to a Dockerfile or use an image that already contains it.
vk dev exec -- sh -lc 'pwd; ls'
vk dev status
vk dev stopExiting a shell or finishing an exec command leaves the environment up.
stop shuts it down while retaining its state for the next start.
Keep the shared environment in version control and personal values beside it:
| File | Purpose |
|---|---|
.virtkit/config.toml |
Tracked project configuration; requires schema = 1. |
.virtkit/local.toml |
Machine-specific overrides, in the same shape. |
.virtkit/local.env |
Optional KEY=value data for ${localEnv:NAME}. |
.virtkit/toolchain.lock |
Optional tracked pin for the team's virtkit release and artifacts. |
Add .virtkit/local.toml and .virtkit/local.env to the project's .gitignore.
Also ignore any additional personal env files or disk images you place in the
checkout. init does not manage your local files.
Commands discover the config by walking toward the current Git checkout's root,
stopping at its .git boundary. You can address a checkout from elsewhere:
vk dev --workspace /path/to/project status
vk dev --dev-config /path/to/project/.virtkit/config.toml plan
vk dev --environment hook plan[dev] is the default environment. Additional [environments.NAME] tables are
independent configurations: they inherit nothing from [dev], including its
user, mounts and environment variables.
Local tables merge recursively; scalars and arrays replace the tracked values.
An empty array clears a list. Named mounts, endpoints and tasks accept
enabled = false. Only local.toml accepts top-level remove and env-files:
# .virtkit/local.toml
remove = ["dev.host.git-gui"]
env-files = [".virtkit/private.env"]
[dev]
cpus = 4
mem = "8G"
profiles = []
[dev.mounts.gitconfig]
enabled = falseremove drops dotted paths before the other overrides apply. Extra env-file paths
are relative to the workspace and must stay within it. They are read after
local.env, with later files overriding earlier ones. The process environment
has precedence over all of these files. An exported empty value is still a value;
it does not fall back to the file or substitution default.
Env files are data, not shell scripts: shell commands and variable references in
them are not evaluated. Loading local.env does not inject every entry into the
guest; reference the values you need in the config.
Supported substitutions in expanded values such as mount paths, build arguments and environment values are:
| Expression | Value |
|---|---|
~, ${HOME} |
Host user's home directory (~ at the start of a path). |
${workspace} |
Host workspace directory. |
${state} |
This worktree/environment's state directory. |
${VK_UID}, ${VK_GID} |
Host user's numeric IDs. |
${localEnv:NAME} |
Process environment, then local env files; required if no default. |
${localEnv:NAME:default} |
Same lookup with a fallback; ${localEnv:NAME:} permits absence. |
Other ${…} names are rejected. Compose files have their own interpolation
syntax and reserved ${VK_WORKSPACE}, ${VK_STATE_DIR} and ${VK_SELF} names;
see Compose interpolation.
Use exec-env for development sessions and guest lifecycle hooks, and
container-env for the guest's processes at boot:
[dev.exec-env]
PROJECT_TOKEN = "${localEnv:PROJECT_TOKEN:}"
BUILD_MODE = "debug"
[dev.container-env]
LANG = "C.UTF-8"vk dev plan redacts environment values and build arguments by default, including
in --explain output. --show-secrets reveals them. Keep secrets in local files
or the process environment rather than the tracked config.
Unknown configuration keys and unsupported values produce errors. Hook groups
are the exception to ordinary key checking: arbitrary member names are allowed,
so a misspelled run can look like a group member.
init adds a #:schema directive for editor completion and validation. The
JSON Schema is also available from
vk dev schema. Use [requires].min-version for the compatibility floor and
[requires].features for feature names understood by vk check --feature.
A toolchain lock pins the team's chosen release independently of that floor:
vk toolchain lock writes it, vk toolchain install downloads its artifacts to
a versioned cache, and vk toolchain status reports the pin and installation.
Installing a locked toolchain does not replace the vk on PATH; scripts can
use vk toolchain export to obtain its paths.
Each environment selects exactly one of image, build or compose.
A compose source also requires service, naming the primary VM you work in.
For a project Dockerfile, replace the image source with:
[dev]
workspace = "/workdir"
user = "dev"
cpus = "host"
mem = "8G"
nested = "auto"
[dev.build]
context = "."
dockerfile = "Dockerfile"
target = "dev"
[dev.build.args]
DEVUSER_UID = "${VK_UID}"
DEVUSER_GID = "${VK_GID}"The context is relative to the workspace; the Dockerfile is relative to that
context and defaults to Dockerfile. Omitting target selects the final stage.
The image must provide the configured user. Pass UID/GID build arguments only
when your Dockerfile consumes them to create the development user.
cpus accepts a positive integer or "host". mem uses sizes such as 8G.
When unset for compose, they inherit the primary service's x-virtkit settings,
then virtkit's defaults. nested = true requires nested virtualization and
fails if the host cannot provide it; "auto" uses it when available. The default
is off. Use it when the guest itself needs to boot microVMs.
[dev.cache]
registry = "http://127.0.0.1:5000"
insecure = trueThe registry setting accepts a cache reference, local directory or none.
insecure permits a plain HTTP registry. For one invocation, override these with
--cache-registry REF|DIR|none and --cache-insecure.
vk dev build
vk dev build --service runnerThese build into the cache without starting or stopping the development
environment. cached-only = true is available for a build source: it requires
a cache hit unless fallback = { target = "smaller-stage" } names a stage to
build on a miss. The fallback responds to a cache miss, not a failed guest boot
or command. An ephemeral task whose environment has no cache registry uses the
calling environment's cache settings. See Project tasks for an example.
vk dev up
vk dev shell
vk dev exec -- make test
vk dev exec -t -- sh
vk dev exec --dir /workdir/subproject -- make test
vk dev exec --user root -- idup builds and boots in the foreground, then returns once the environment is
ready. Concurrent callers join an in-flight boot; up --no-wait fails promptly
instead. Primary exec, shell and code bring the environment up when needed.
Commands use the configured user and exec-env; a host working directory inside
the checkout maps to its corresponding guest directory. --dir overrides that
mapping. exec passes arguments directly and reproduces the command's exit
status; use sh -lc '…' explicitly for shell syntax.
A matching running environment is reused. When its recorded configuration differs
from the current plan, freshness controls the next attachment:
| Policy | Behavior |
|---|---|
ask (default) |
Offer refresh on a terminal; declining or having no terminal reuses with a note. |
reuse |
Reuse the recorded environment and report differences. |
refresh |
Rebuild and replace the environment. |
require-current |
Fail with the differences and the command to reconcile them. |
Override the project policy for an invocation with --freshness POLICY.
Inspect a change before applying it:
vk dev status
vk dev plan --diff
vk dev refresh --dry-run
vk dev refreshThe diff classifies changes as requiring a new session, a host-side step, a
restart or an image rebuild. It compares the resolved plan with the boot record;
it is not a watcher of every file in the build context. Use refresh after
changing Dockerfile contents or when you explicitly want a rebuild even though
the configuration still matches.
refresh unconditionally rebuilds and restarts without confirmation. The build
runs while the current environment remains available; a failed build leaves it
running. The restart interrupts existing sessions. Durable data follows the
storage rules below.
A project can combine a primary development service with database dependencies and profiled test appliances. Give each appliance its own disk and published ports, and use a separate environment for pre-commit checks so checking a commit does not start the whole LAN.
Here is a smaller version of that pattern. Save this as .virtkit/compose.yaml;
the example assumes a project Dockerfile with dev and runner stages:
services:
devcontainer:
build:
context: ..
dockerfile: Dockerfile
target: dev
entrypoint: []
command: sleep infinity
depends_on:
- redis
volumes:
- ${VK_WORKSPACE}:/workdir
- ${VK_SELF}:/usr/local/bin/vk:ro
redis:
image: docker.io/library/redis:7
runner:
build:
context: ..
dockerfile: Dockerfile
target: runner
profiles:
- runner
volumes:
- ${VK_WORKSPACE}:/workdir:ro
- ${VK_WORKSPACE}/.virtkit/runner-data.qcow2:/var/lib/runner:diskSelect the primary and describe a runner endpoint in .virtkit/config.toml:
schema = 1
[dev]
compose = ".virtkit/compose.yaml"
service = "devcontainer"
workspace = "/workdir"
user = "dev"
[dev.endpoints."runner.https"]
service = "runner"
target = 443
host-port = 8443
address = "auto"
scheme = "https"
path = "/ui"The compose build context is relative to the compose file. Dependencies start
with the primary; profiled services remain on demand unless selected through
[dev].profiles. Services resolve each other over the shared LAN by service name
and hostname. The runner image in this example must provide its own server on
port 443; declaring an endpoint does not install or start an application.
vk dev service status
vk dev service up runner
vk dev endpoints --service runner
vk dev open runner.https
vk dev exec --service runner --user root -- id
vk dev logs --service runner -f
vk dev service reboot runner
vk dev service down runnerservice up brings up the environment if needed and builds the requested service
on first use. reboot restarts the guest in place without rebuilding its image.
Other service commands do not boot the environment. Service exec requires a
running service and does not inherit the primary's user, workspace directory or
exec-env; pass --user and --dir when needed.
| Field | Meaning |
|---|---|
target |
Required guest TCP port. |
service |
Compose service; omit for the primary. |
host-port |
Host port; defaults to target. |
address |
auto (default) for stable loopback allocation, or an explicit address. |
scheme, path |
URL components for vk dev open; scheme is required to open a URL. |
required |
Default false; when true, readiness requires publication. |
enabled |
Default true; false disables the declaration. |
Automatic allocation gives each environment a loopback block and each service its own address, allowing multiple worktrees to use the same port numbers. Addresses are retained with environment state. Use an unprivileged host port, such as 8443 for guest port 443, unless the host permits low-port binding.
vk dev endpoints reads existing allocations and publication state without
allocating or booting. --primary, --service NAME and --json filter or format
the output. vk dev open NAME --print prints the URL; without --print, it uses
xdg-open when available and prints otherwise. It does not start the service.
Development egress defaults to unrestricted. [dev.network] egress = "restricted"
is currently rejected; it is not an implemented development allowlist.
workspace names the guest path for the host checkout. Normal development
sessions write through to the host files. Linked Git worktrees receive the Git
metadata mapping needed to use the checkout from the guest.
Additional mounts are named entries:
[dev.mounts.gitconfig]
source = "~/.gitconfig"
to = "/home/dev/.gitconfig"
read-only = true
optional = true
[dev.mounts.package-cache]
source = "${state}/package-cache"
to = "/home/dev/.cache/project"A mount requires source and to. It is writable by default; read-only = true
prevents guest writes through that mount. optional = true skips an absent
source. Directories under ${state} are managed storage, created at boot and
kept across refreshes. Use individual data directories there, not the entire
state directory or its reserved control files.
| Storage | Lifetime |
|---|---|
| Host checkout and ordinary bind mounts | Host files; guest writes persist independently of VM lifetime. |
Compose disk volume |
Durable service data, retained across refreshes until explicitly reset. |
${state} managed directory |
Retained across stop/start and refresh; removed with its environment state. |
| Persistent VS Code server | Managed editor data retained across refreshes. |
| Compose persistent root or persistent overlay | Bound to the image generation; recreated when its image changes. |
| Ephemeral task checkout overlay | Writes discarded when the task VM is removed. |
See Compose volumes for disk and root configuration. The inventory comes from these declarations; there is no second storage configuration to maintain.
vk dev storage list --sizes
vk dev storage list --json
vk dev storage reset 'runner:/var/lib/runner'Use the exact item name printed by list. Reset destroys a durable item's data,
with its owner stopped first; --yes authorizes stopping and removal without a
prompt. The next start recreates the item empty. Reset refuses image-generation
storage owned by refresh, and the editor's server storage, which
vk dev code --reset-server starts over instead.
State is stored under $XDG_STATE_HOME/virtkit/dev, falling back to
~/.local/state/virtkit/dev. Separate worktrees and environment names have
separate state, SSH identities and endpoint allocations.
vk dev list
vk dev list --json
vk dev gc --all-stale
vk dev gc ENVIRONMENT_NAME --yeslist and gc work from anywhere without a project config. Copy names from
list; they identify state directories, not just the config's dev or hook
selector. The MEM column shows what a running environment's VM holds on the
host now over the size it booted with (1.2G/8G), the same figure vk list
reports; a stopped environment reads -, and mem_used_bytes in --json is
null for it. gc refuses running environments. --all-stale selects stopped
state whose workspace is gone or which never recorded a boot, including
leftovers from throwaway tasks. It does not mean every stopped development
environment.
Without --yes, GC asks on a terminal; without a terminal it only lists what
would be removed. GC deletes state directories, including managed data inside
them. It does not delete external disk backings merely because the environment
referenced them. Reset durable external data explicitly before discarding its
configuration if you no longer need it.
Install a local VS Code-compatible editor and its Remote-SSH extension, then:
vk dev code
vk dev editor status
vk dev editor logcode finds code, code-insiders, codium, vscodium or code-oss on PATH,
in that order; --editor BIN chooses explicitly. It boots the environment and
opens the workspace over the run's own SSH setup, without modifying your SSH
identities or using your personal keys for the connection.
Under WSL2, a VS Code found under /mnt/<drive>/ is a Windows one: its Remote-SSH
runs on Windows and reads %USERPROFILE%\.ssh\config, which knows nothing of the
distro's setup. For that case code also writes %USERPROFILE%\.ssh\vk\<alias>.conf,
a host block whose ProxyCommand re-enters the distro through wsl.exe, next to
<alias>.key, a copy of the run's managed key — Windows OpenSSH cannot read a key
at a Linux path. %USERPROFILE%\.ssh has to exist already, because the copy inherits
its permissions. The host block is rewritten on every launch and the key refreshed
only when it changed, and Include vk/*.conf is added once at the top of
%USERPROFILE%\.ssh\config. vk dev ssh-config --windows prints the same block
without writing anything. Remote-SSH asks which platform a host it has not seen runs
and waits for the answer before installing its server;
"remote.SSH.remotePlatform": { "vk-*": "linux" } in the Windows VS Code user
settings answers it once for every environment, and code says so while it is missing.
[dev.editor.vscode]
state = "persistent"
home = "/home/dev"
extensions = ["rust-lang.rust-analyzer"]
reconcile = ["/workdir/scripts/editor-setup.sh"]
[dev.editor.vscode.settings]
"editor.formatOnSave" = truestate defaults to persistent; ephemeral ties editor storage to the
environment generation. home defaults to /root for root or /home/<user>.
For an image or build source, persistent mode supplies managed server storage;
do not also mount the same guest directory. For a compose source, declare the
server mount explicitly:
[dev.mounts.vscode-server]
source = "${state}/vscode-server"
to = "/home/dev/.vscode-server"After Remote-SSH installs the guest server, a detached reconciliation installs
configured extensions, applies settings while preserving unrelated preferences,
and runs reconcile in the guest. Project reconciliation scripts can use
VK_VSCODE_CLI to address the installed server CLI. Reconciliation is separate
from VM readiness: check its status and log if the editor opens before extensions
are ready. vk dev editor retry [--editor BIN] retries in the foreground; the
environment must be up and the editor connected or connecting.
vk dev code --reset-server starts the guest's server over: it stops the server,
empties its data directory in the guest (~/.vscode-server for a stable build, the
channel's own directory otherwise — installed server, extensions, machine settings and
caches), forgets the reconciliation, and opens the editor. Remote-SSH
installs the server again when the window connects, and the reconciliation applies
[dev.editor.vscode] afresh. It works whatever holds the directory — vk's managed
storage, a mount you declared, or the environment itself. The environment keeps
running: nothing else in the guest is touched, hooks.create does not run again, and
windows already attached lose their server and reconnect. A reconciliation already
running must finish first; the command does not wait for it. vk dev editor reset [--editor BIN] does the same without opening an editor; the environment must be up.
For other SSH clients and editors:
vk dev up
vk dev ssh
vk dev ssh -- uname -a
vk dev ssh-configssh does not boot the environment. ssh-config prints the run's stanza for an
editor, rsync or an Include in your own SSH config. It refers to keys and sockets
in the state directory, so removing that state invalidates it. Use shell when
you want an interactive session that also starts the environment.
| Hook | Where and when |
|---|---|
init |
Host workspace, before a build or start attempt. |
create |
Guest, once per materialized environment generation. |
start |
Guest, each actual boot; not on reuse of a running environment. |
Guest hooks use the configured user, workspace and exec-env. create runs before
start. A successful required create is stamped for its generation; a failed
one is not. Recreating an image or its writable storage can create a new generation,
so create does not mean once forever for a checkout.
[dev.hooks]
init = ["./scripts/prepare-host.sh"]
create = { run = ["./scripts/setup.sh"], timeout = "10m" }
start = { run = "./scripts/start-dev.sh", cwd = "/workdir", required = true }A hook may be a shell string, a direct argv array, a table with run, cwd,
timeout and required, or a table of named hooks. Named members execute
sequentially in name order, not in parallel. The default working directory is
the workspace. Timeouts accept seconds, s, m or h, for example 90s or 10m.
A required failure fails the operation. required = false reports the failure
and continues; a best-effort create is stamped even if it fails.
There is no attach hook. Put editor setup in editor.vscode.reconcile and ordinary
project commands in tasks. Review host init commands before running a newly
cloned config: they execute as your host user.
Tasks declare a command and how it obtains an environment:
[dev.tasks.test]
run = ["make", "test"]
policy = "require"
[dev.tasks.test.env]
TEST_MODE = "local"vk dev task test -- VERBOSE=1Arguments after -- are appended to the task command, and its exit status is
returned to the caller. An argv list avoids shell interpretation; a shell string
is also accepted. Tasks run from the guest workspace, with their env added to
the selected environment's exec-env.
policy |
Placement |
|---|---|
reuse |
Attach to an already running environment; fail if absent. |
require |
Bring up the selected environment first and leave it running. |
ephemeral |
Boot a throwaway VM for this command and tear it down afterwards. |
reuse-or-ephemeral (default) |
Use a running environment if available, otherwise a throwaway VM. |
environment defaults to dev. reuse may name a different environment for the
two reusing policies, allowing a small standalone fallback when the full
development environment is absent. checkout defaults to shared, which writes
through to the host checkout.
For pre-commit checks, use a cached development build if it exists, otherwise build a smaller checking stage, and discard file normalization performed by the checks.
[dev.tasks.pre-commit]
run = ["./scripts/pre-commit"]
environment = "hook"
policy = "ephemeral"
checkout = "overlay"
[environments.hook]
build = { context = ".", dockerfile = "Dockerfile", target = "dev" }
cached-only = true
fallback = { target = "precommit" }
workspace = "/workdir"
user = "dev"
[environments.hook.exec-env]
PRE_COMMIT_ISOLATED = "1"
[environments.hook.mounts.gitconfig]
source = "~/.gitconfig"
to = "/home/dev/.gitconfig"
read-only = true
optional = trueThe Dockerfile must supply both stages and the script must exist in the project. A host hook or wrapper can invoke it with:
vk dev task pre-commit -- "$@"If the same script is the guest task, make its guest path run the checks directly
rather than dispatching itself into vk dev again. An environment flag such as
PRE_COMMIT_ISOLATED can distinguish that path.
checkout = "overlay" requires policy = "ephemeral"; configurations that could
attach to a shared running VM are rejected. The checkout gets a tmpfs overlay,
so its writes disappear with the VM. This does not make additional writable host
mounts disposable; declare those separately and read-only where appropriate.
Ephemeral tasks boot no service LAN and publish no endpoints. Use a standalone
image or build environment for them, and require when a task needs the normal
service environment.
Host integration is off by default:
[dev.host]
git-gui = truegit-gui enables the built-in policy for running gitk and git gui on the host
against the mapped workspace, with argument and environment filtering.
For other host commands, wrapper names a project dispatcher relative to the
workspace and wrapper-env lists environment-variable patterns it accepts.
git-gui and a custom wrapper cannot both be configured; a custom dispatcher
can call vk host-policy git-gui itself.
These are explicit host capabilities. A strict config schema does not sandbox a host hook or custom dispatcher, and a writable host mount lets guest code modify that data. Keep the shared config's mounts and host commands reviewable.
A present [dev.ssh] forwards the host SSH agent into the guest and writes a
matching guest ~/.ssh/config:
[dev.ssh]
keys = ["work"]
[dev.ssh.host."gitlab.example.com"]
user = "git"
key = "work"Only the agent socket crosses into the guest — no private keys and no ~/.ssh.
List keys (and/or a host's key) to restrict which identities the agent offers;
with none listed, the whole agent is forwarded. The whitelist is the union of
keys and every host's key, and each token is a key comment, a SHA256:…
fingerprint (as ssh-add -l prints), or a .pub path or ~/.ssh basename.
The generated config sets IdentityAgent on Host * and on each listed host, so
git and ssh authenticate in both shell sessions and Remote-SSH / vk dev ssh
sessions (where SSH_AUTH_SOCK is not set). IdentityAgent needs an OpenSSH
client in the image; busybox and dropbear ignore it, though exec-session
forwarding through SSH_AUTH_SOCK still works with them.
Forwarding needs a running host agent — check with ssh-add -l — and takes effect
on the next boot, so run vk dev refresh after enabling it.
vk dev init --from devcontainer
vk dev init --from compose
vk dev init --from dockerfileThese produce a starting config from the project's existing files. An existing
config is not silently overwritten: --force replaces the tracked config while
preserving its local files. Review the import report before using the result.
The importer translates supported sources, session settings, mounts, editor settings and lifecycle commands. The main hook mapping is:
| Devcontainer field | virtkit field |
|---|---|
initializeCommand |
dev.hooks.init |
postCreateCommand |
dev.hooks.create |
postStartCommand |
dev.hooks.start |
Dev Container Features are not installed automatically; bake them into the image
or Dockerfile. Fold onCreateCommand and updateContentCommand into create
as appropriate. Move editor work from postAttachCommand to
editor.vscode.reconcile. Docker privilege and capability settings do not carry
over as equivalent microVM settings. Review ports as named endpoints and any
reported source/service/stage decisions rather than treating the import as full
devcontainer compatibility.
Start with read-only diagnostics:
vk dev doctor
vk dev plan --explain
vk dev status --json
vk dev plan --diff
vk dev logs -n 100doctor checks version/feature requirements, KVM and the VMM, external tools,
source and mount paths, endpoint ports, state access and referenced host variables.
It exits 1 when a check fails. plan --format shell shows the underlying run
shape for inspection, not a complete script to execute.
| Symptom | Next step |
|---|---|
| No config found | Run init at the checkout root or pass --workspace DIR. |
| Unknown key or invalid source combination | Check the error's file location, local overrides and schema; select one source. |
Missing ${localEnv:NAME} |
Export it, add it to local.env, or declare an intentional default. |
| Wrong files or permissions in the guest | Inspect workspace, mount paths, the image's user and UID/GID build arguments. |
| Environment is out of date | Inspect plan --diff; use refresh to rebuild and restart. |
| Upgrade causes guest protocol/image incompatibility | Refresh the environment so its guest components match the current vk. |
| Runner endpoint is unpublished | Check service status, start the service, then inspect endpoints and doctor. |
| Editor opens without extensions | Inspect editor status and editor log, then editor retry with the editor connected. |
| Editor stuck on "Copying VS Code Server to host with scp" | Add GNU wget or curl to the image—BusyBox's wget rejects Remote-SSH's flags—then run refresh. |
| "The remote host does not meet the prerequisites for running VS Code Server", or extension installs fail with "Signature verification failed with 'ENOENT'" | The image has no FHS loader or libraries for the prebuilt glibc binaries Remote-SSH installs (Nix images in particular). Provide /lib64/ld-linux-x86-64.so.2, /usr/lib64/libstdc++.so.6, and libssl/libcrypto on the loader's search path, then run refresh; this repo's .devcontainer/Dockerfile does it with nix-ld. |
| Startup hook fails | Read its output and guest logs; fix the command, user or working directory before retrying. |
| Pre-commit overlay is refused | Pair checkout = "overlay" with policy = "ephemeral". |
| Disk use keeps growing | Inspect storage list --sizes and host-wide list; reset or GC only data you intend to discard. |
Logs remain readable after exit. Filter with --kernel, --agent, --guest or
--level warn, and follow with -f. Use --service NAME for a service console.
All project commands accept --workspace DIR, --dev-config FILE,
--environment NAME, --freshness POLICY, --cache-registry REF|DIR|none and
--cache-insecure as global vk dev options. Use vk dev COMMAND --help for
individual options.
| Command | Purpose |
|---|---|
init |
Import or validate a config; --from, --image, --force. |
up |
Ensure the environment is ready; --no-wait. |
exec -- ARG… |
Run a command; --dir, --user, --service, -t. |
shell |
Boot if needed and open an interactive shell. |
code |
Boot if needed and open VS Code; --editor. |
editor status, editor log, editor retry, editor reset |
Follow or retry editor reconciliation, or start the server over, without booting. |
build |
Build the primary or --service NAME into cache. |
service up/down/reboot NAME, service status [NAME] |
Control compose services. |
endpoints |
Show publication state; --primary, --service, --json. |
open NAME |
Open an endpoint URL, or --print it. |
task NAME -- ARG… |
Run a declared project task. |
ssh -- ARG…, ssh-config |
Connect through or print the run's SSH setup; --windows for a Windows client. |
refresh |
Rebuild and restart; --dry-run only reports changes. |
status |
Running state and configuration match; --json. |
logs |
Read or follow primary/service console output. |
doctor |
Check the host and resolved requirements. |
plan |
Inspect the resolved config; --explain, --diff, --format, --show-secrets. |
stop |
Stop the environment and publishers; --timeout SECONDS. |
storage list, storage reset NAME |
Inspect storage or destroy a durable item's data. |
list |
Host-wide environment inventory (on-disk sizes by default); --no-sizes, --json. |
gc [NAME…] |
Remove stopped state; --all-stale, --yes. |
schema |
Print the configuration JSON Schema. |