sandbox.nvim · Project & repos · vimdoc
:help sandbox
Manage containers (Podman/Docker/nerdctl) from Neovim
doc/sandbox.txt — rendered from the plugin's own vimdoc
*sandbox.txt* Manage containers (Podman/Docker/nerdctl) from Neovim
Table of Contents
Introduction ······················· |sandbox-introduction| Requirements ························ |sandbox-requirements| Setup ······························· |sandbox-setup| Configuration ······················· |sandbox-configuration| Commands ···························· |sandbox-commands| :Sandbox container ················ |sandbox-commands-container| :Sandbox image ····················· |sandbox-commands-image| :Sandbox volume ···················· |sandbox-commands-volume| :Sandbox network ··················· |sandbox-commands-network| :Sandbox compose ··················· |sandbox-commands-compose| :Sandbox engine ····················· |sandbox-commands-engine| :Sandbox registry ··················· |sandbox-commands-registry| :Sandbox docs ······················· |sandbox-commands-docs| :Sandbox devcontainer ··············· |sandbox-commands-devcontainer| :Sandbox wsl ························ |sandbox-commands-wsl| Keymaps ····························· |sandbox-keymaps| Statusline ··························· |sandbox-statusline| Health ······························· |sandbox-health| About ································ |sandbox-about|
Introduction
*sandbox* *sandbox.nvim* sandbox.nvim manages Podman, Docker, and nerdctl containers directly from Neovim: containers, images, volumes, networks, compose projects, registry auth, devcontainers, and (on Windows) WSL distros, all through a single:Sandboxcommand tree with <Tab> completion. Architecture is hexagonal (ports & adapters): aContainerEngine/ComposeEngine/WslEngineport defines each capability, and a Docker, Podman, or nerdctl adapter implements it by shelling out to the matching CLI. Adding a new engine or a new operation means adding an adapter/port method, not touching the command layer. See https://github.com/StefanBartl/sandbox.nvim/blob/main/docs/add_usecase.md if you want to contribute one.
Requirements
- Neovim 0.10+ - lib.nvim (https://github.com/StefanBartl/lib.nvim) — REQUIRED. The:Sandbox/:Sbxcommand tree is built onlib.nvim.bindings.usercmd.composer, and the buffer/window views underlua/sandbox/ui/depend onlib.nvim.windowdirectly. The plugin does not run without it. - ui.nvim (https://github.com/StefanBartl/ui.nvim) — REQUIRED.ui.contextmenuandui.kit(the right-click menu's item builders and renderer, pluskit.input()/kit.confirm()'s scripted prompts) arerequire()d unconditionally fromlua/sandbox/integrations/menu.lua,lua/sandbox/util/confirm.lua, andlua/sandbox/ui/list_actions.lua. - At least one of: podman, docker, or nerdctl on$PATH, WITH ITS DAEMON RUNNING. Being on$PATHsays an engine is installed; it says nothing about a daemon being up, and detection skips one that does not answer. See |sandbox-health|. - (Optional) telescope.nvim — only needed for the:Telescope sandboxpicker extension, an alternative front end to the list views described under |sandbox-keymaps|. Everything else works without it. - (Optional) nvzone/menu — preferred renderer for the right-click context menu on list-view buffers;ui.kit.menudraws it otherwise, so the menu itself is not optional (see ui.nvim above), only this choice of renderer. See |sandbox-config-menu|. - (Optional) hover.nvim — image previews under the cursor, see |sandbox-config-hover|. - (Optional, Windows only)wsl.exeon$PATH— enables thewslsub-namespace; it is simply absent everywhere else.
Setup
You must call require("sandbox").setup({}) — even with an empty table —
to initialize the plugin's configuration; nothing self-registers otherwise.
lazy.nvim, loaded after the UI is ready (recommended):
{
"StefanBartl/sandbox.nvim",
dependencies = { "StefanBartl/lib.nvim" },
event = "VimEnter",
config = function()
require("sandbox").setup({
engine = "podman", -- or "docker" / "nerdctl"; omit for auto-detect
})
end,
}
Ifengineis omitted, sandbox.nvim auto-detects in this order: Podman, then Docker, then nerdctl — the first that is on$PATH*and answers*. An engine you name is an instruction and is never probed.
Configuration
All fields of the table passed to setup({}) are optional.
*sandbox-config-engine*
engine
Type:"podman"|"docker"|"nerdctl"|nilDefault:nil(auto-detect) Explicitly pin the engine instead of relying on auto-detection. Overridden by a session-level |:Sandbox-engine-set| and by a per-project.sandboxrcfile (a bareengine=docker/engine=podman/engine=nerdctlline in the project root) — see |sandbox-commands-engine| for the full precedence order. *sandbox-config-confirm_destructive*
confirm_destructive
Type:booleanDefault:trueAsk for confirmation (via ui.nvim'sui.kit.confirm) beforeremove,prune, orkill. Set tofalseto skip the prompt and act immediately. *sandbox-config-default_shell*
default_shell
Type:stringDefault:"sh"Shell used by:Sandbox container execwhen no shell is given explicitly. *sandbox-config-refresh_interval*
refresh_interval
Type:integer|nilDefault:nilMilliseconds between automatic re-runs of a visible list view'slistcommand.nil/0disables auto-refresh (the default); refreshing is paused while the buffer isn't shown in any window. *sandbox-config-list_split*
list_split
Type:"above"|"below"|"left"|"right"Default:"left"Window placement for the container/image/volume/network list views. *sandbox-config-list_size*
list_size
Type:integer|nilDefault:nilWidth (for left/right splits) or height (for above/below splits) of list view windows.niluses Neovim's own default sizing. *sandbox-config-progress_style*
progress_style
Type:"auto"|"notify"|"statusline"|"fidget"|"float"|"kit"Default:"auto"How to show that a long-running command is in flight. The commands that never block the editor —image pull/push, the devcontainerbuild,compose up/down/restartand the fourprunecommands — can still run for minutes with no output of their own, so each one gets a progress indicator labelled with the operation (e.g.docker pull nginx). Requireslib.nvim, which provides the indicator via itslib.nvim.progressmodule. Withoutlib.nviminstalled the option is silently a no-op — the commands behave exactly as before."auto"prefersfidget.nvimwhen installed and falls back tovim.notify."statusline"draws nothing and instead publishes the text for your own statusline to read viarequire("lib.nvim.progress.styles.statusline").active()."float"and"kit"open a small window that can be focused and<Esc>-ed to abort the command (SIGTERM), same as the handle's ownstop(). *sandbox-config-hover*
hover
Type:booleanDefault:trueRegister the request-only image preview with hover.nvim::Hover showon an image reference in a Dockerfile or compose file reports whether it is pulled, its size, and any containers from it. Never fires on hover.nvim's automatic trigger — an engine call costs 300-750 ms. A no-op when hover.nvim is not installed. *sandbox-config-max_error_length*
max_error_length
Type:integerDefault:200How much of an unrecognized adapter error reaches the notification. The full text always goes tosandbox.logger, and the truncated message says where to read it. *sandbox-config-status_cache_ttl_ms* *sandbox-config-completion_cache_ttl_ms*
status_cache_ttl_ms
completion_cache_ttl_ms
Type:integerDefaults:3000and4000How long a statusline reading (|sandbox-statusline|) and a<Tab>completion listing stay cached. Both trade freshness against how often the engine is asked: raise them for a slow daemon, lower them if a stale reading annoys you. *sandbox-config-menu*
menu
Type:{ enable: boolean }Default:{ enable = true }Bind<RightMouse>on list-view buffers to a context menu mirroring that buffer's own keymaps, via ui.contextmenu (ui.nvim). nvzone/menu is only its preferred renderer: when absent, the menu still renders, viaui.kit.menu.enable = falsedrops the trigger entirely. *sandbox-config-keymaps*
keymaps
Type:table|false|nilDefault:nilPer-list overrides of the buffer-local keys in |sandbox-keymaps|.nilkeeps every default,falsebinds none. Each entry isaction = lhs, where an lhs may be one key, a list of keys, orfalseto drop that action; action names are the descriptions the views declare, slugified ("logs (follow)" islogs_follow). A name matching nothing is reported, not silently ignored.
require("sandbox").setup({
keymaps = {
list = { engine = false },
containers = { inspect = "o", remove = false },
inspect = { close = "<Esc>" },
},
})
Per-project engine override: drop a.sandboxrcfile with a singleengine=docker(orpodman/nerdctl) line in a repository's root to pin that repository to a specific engine regardless of the global default — useful on a machine where different projects need different engines.
Commands
Every capability hangs off one user command,:Sandbox, with a short alias:Sbx(identical behavior, same routes). Sub-namespaces below are typed as:Sandbox <namespace> <subcommand> [args...] [flags]; both the namespace and subcommand name complete on <Tab>, as do container/image/volume/ network/distro identifiers where a command takes one — resolved live against the active engine, cached briefly so repeated <Tab> presses don't shell out on every keystroke. Where a subcommand's Args column shows[--buffer|-b], appending--buffer(or-b) streams the CLI's raw stdout/stderr into a scrollable terminal buffer instead of collapsing it into a singlevim.notifysummary — useful for verbose operations likestart,stop,prune, orbuild. Example::Sandbox container start web --buffer. Run:Sandbox docs generateat any time to regeneratedocs/GENERATED_COMMANDS.md, a mechanical dump of the live route table generated straight from the composer — useful for confirming this help file (anddocs/BINDINGS.md) haven't drifted from what's actually registered.
:Sandbox container
list List all containers (running and
stopped).
logs {id} Show logs for a container.
logs-follow {id} Stream a container's logs live
(logs -f); press q in the
buffer to stop following.
exec {id} [shell] [workdir=<path>] Open an interactive shell
inside a container.
exec-once {id} [workdir=<path>] Run a one-off command and show
[command...] its output (non-interactive).
workdir= becomes the
engine's -w, and goes before
the command: every token after
it belongs to what runs inside
the container.
start {id} [--buffer|-b] Start a container.
stop {id} [--buffer|-b] Stop a container.
kill {id} [--buffer|-b] Force kill a container.
restart {id} [--buffer|-b] Restart a container.
pause {id} Pause a running container's
processes.
unpause {id} Resume a paused container's
processes.
rename {id} {new-name} Rename a container.
stats {id} One-shot CPU/memory/network/
block-IO usage snapshot.
top {id} List processes running inside
a container.
cp {src} {dest} Copy a file/directory between
host and container; either
side may be <id>:<path>.
run Interactively create and start
a new container — prompts for
image, name, port mappings,
volume mounts, and env vars.
remove {id} [--buffer|-b] Remove a stopped container
(confirms first, see
|sandbox-config-confirm_destructive|).
prune [--buffer|-b] Remove all stopped containers
(confirms first).
inspect {id} Inspect a container's full
metadata (folded, indented).
:Sandbox image
list List all local images.
pull {name} [--buffer|-b] Pull an image. Runs
asynchronously either way
(doesn't block the UI);
--buffer additionally streams
progress into a terminal
buffer instead of a single
completion notify.
push {name} Push an image to a remote
registry (async); requires
prior |sandbox-commands-registry|
auth.
tag {source} {target} Tag a local image with a new
repository:tag.
build {tag} [path] Build an image from a
Dockerfile/Containerfile
(streams to a terminal
buffer); path defaults to
..
save {image} {path} Save (export) an image to a
tarball on disk.
load {path} Load (import) an image from a
tarball on disk.
history {image} Show an image's layer
history.
inspect {image} Inspect an image's full
metadata.
remove {id} Remove a local image
(confirms first).
prune [--buffer|-b] Remove all dangling images
(confirms first).
:Sandbox volume
list List all local volumes.
create {name} Create a new named volume.
remove {name} Remove a volume (confirms
first).
inspect {name} Inspect a volume's full
metadata.
prune Remove all unused volumes
(confirms first).
:Sandbox network
list List all local networks.
create {name} Create a new named network.
remove {name} Remove a network (confirms
first).
inspect {name} Inspect a network's full
metadata.
connect {network} {id} Connect a container to a
network.
disconnect {network} {id} Disconnect a container from a
network.
prune Remove all unused networks
(confirms first).
:Sandbox compose
Scoped to adocker-compose.yml/compose.yml/podman-compose.ymlauto-detected in the cwd or an ancestor directory (vim.fs.find, the same lookupdocker compose/podman composethemselves do). No id/name argument on any subcommand — there is exactly one project per detected file, re-resolved on every call. up Start the compose project, detached. down Stop and remove the compose project. restart Restart the compose project. ps List services in the compose project. logs Show logs for the compose project.
:Sandbox engine
*:Sandbox-engine-set* Switch the active engine for the rest of the Neovim session instead of only atsetup({})time — useful on a machine with more than one engine installed. Precedence, highest first: session override (engine set)
per-project.sandboxrc> configured/auto-detected default. set {docker|podman|nerdctl} Switch the active engine for this session. get Show the currently active engine and which mechanism chose it. reset Clear the session override, falling back to.sandboxrc/config.
:Sandbox registry
Authenticate beforepush/pullagainst a private registry.loginprompts for a username (vim.ui.input) and password (vim.fn.inputsecret, masked); the password is piped to the engine over stdin (--password-stdin), never passed as an argv element, so it never appears in the process list or shell history. Podman (unlike Docker) requires an explicitregistryargument since it has no implicit Docker Hub default. login [registry] Log in to a registry (prompts for username/password). logout [registry] Log out of a registry.
:Sandbox docs
generate Regenerate
docs/GENERATED_COMMANDS.md
from the live route table, for
diffing against the
hand-maintained
docs/BINDINGS.md.
:Sandbox devcontainer
Detects.devcontainer/devcontainer.jsonor.devcontainer.jsonin the cwd or an ancestor directory (JSONC://and/* */comments and trailing commas are stripped before parsing) and offers to build/attach, similar to VS Code's Dev Containers extension.buildpullsimageor buildsbuild.dockerfile(or delegates to |sandbox-commands-compose|'supfor adockerComposeFileproject), then runs it with the workspace bind-mounted atworkspaceFolder,forwardPortsmapped,containerEnvpassed through, andsleep infinityas the command so it stays up forattach— the same override VS Code's own devcontainer CLI applies to images with no long-running default CMD. The container is namedsandbox-devcontainer-<workspace-dir-basename>soattachcan find it again. Scope: single-container (image/build.dockerfile) anddockerComposeFileshapes only. No devcontainer "features", lifecycle commands (postCreateCommand, ...), orremoteUsersupport yet. build Build/pull the devcontainer's image and start a container from it. attach Open a shell in the running devcontainer for the project in cwd.
:Sandbox wsl
Only registered whenwsl.exeis reachable on$PATH(Windows with WSL installed) — checked once when the plugin's commands are set up, so on Linux/macOS thewslsub-namespace simply doesn't exist rather than existing and always failing. list List all registered WSL distributions. start {name} Start a WSL distro. stop {name} Stop (terminate) a WSL distro. exec {name} [command...] Open a shell or run a command inside a WSL distro. set-default {name} Set a distro as the WSL default. set-version {name} {1|2} Toggle a distro between WSL1/WSL2. export {name} {path} Export a distro to a tarball on disk. import {name} {install-path} {tar-path} Import a distro from a tarball. shutdown-all Shut down the WSL2 VM and all running distros (confirms first).
Keymaps
No global keymaps are defined — nothing to map via which-key. The
read-only list-view scratch buffers opened by `:Sandbox {container,image,
volume,network} list` carry **buffer-local** keymaps instead, so you can
act on the entry under the cursor without retyping its id/name into a new
:Sandbox command. Press ? inside any list buffer for a live reminder
of its keymaps; q closes it.
Four keys every list view shares: q closes it, ? lists what is
actually bound in that buffer, E cycles docker -> podman -> nerdctl for
the session and re-renders, and f narrows the list — matching across
every field of an entry, not just the rendered text, so f redis finds the
container running that image. f is bound only where the view supplies a
filter callback; the container list has it.
Every key below is remappable via |sandbox-config-keymaps|. With
telescope.nvim installed and require("telescope").load_extension("sandbox")
called, :Telescope sandbox containers|images|wsl offers the same action
set through a fuzzy finder instead.
Container list (`sandbox.nvim://container-list`):
<CR> / i inspect n rename (prompts)
s start D remove (confirm)
x stop l logs
X kill L logs (follow)
r restart e exec (shell)
p pause t top
P unpause T stats
R refresh list
The [status] prefix on each line is highlighted by container state:
green (SandboxStatusRunning), red (SandboxStatusStopped), yellow
(SandboxStatusPaused), comment-colored (SandboxStatusOther) — each
linked to a Diagnostic{Ok,Error,Warn}/Comment highlight group so it
follows your colorscheme. Override any of the four directly, e.g.
:hi SandboxStatusRunning guifg=#00ff00
Image list (`sandbox.nvim://image-list` / `sandbox.nvim://images`):
<CR> / i inspect
h history
t tag (prompts for target)
D remove (confirm)
R refresh list
Volume list (sandbox.nvim://volume-list) and Network list
(`sandbox.nvim://network-list`) both carry:
<CR> / i inspect
D remove (confirm)
R refresh list
Multi-select: select several lines in any Visual mode (V, v, j/k
to extend, ...), then press the same key you'd use on a single line to
apply it to every selected entry — s/x/X/D (start/stop/kill/
remove) in the container list, D (remove) elsewhere. A destructive bulk
action confirms once for the whole batch, not once per item.
Inspect view (sandbox.nvim://inspect/<id>), opened by any inspect
action above: renders the engine's metadata as a folded, indented
vim.inspect-style Lua table (foldmethod=indent, foldlevel=1) rather
than a flat dump — za/zo/zc toggle sections, q closes the buffer.
Set |sandbox-config-refresh_interval| in setup({}) to have a visible
list buffer re-run its list command on a timer instead of relying only
on R/manually reopening it; refreshing pauses while the buffer isn't
shown in any window. There is no global augroup for any of this — a
buffer-local, one-shot BufWipeout autocmd per list/log-follow buffer
stops its timer/job when that specific buffer is wiped, so nothing lingers
after you close it.
Statusline
*sandbox.statusline.status()*require("sandbox.statusline").status()returns an ambient"engine (running/total)"summary string (e.g."docker (2/5)"), cached for |sandbox-config-status_cache_ttl_ms| so a statusline redrawing several times a second doesn't shell out on every call. It degrades to""on any failure (daemon down, no engine configured) instead of erroring — plain string return with no hard dependency on any statusline plugin. lualine:
require("lualine").setup({
sections = {
lualine_x = { require("sandbox.statusline").lualine_component },
},
})
Health
sandbox.nvim integrates with Neovim's health-check system:
:checkhealth sandbox
Five checks, in order:
1. The resolved engine — the one a command would actually use, session
override and .sandboxrc included, not the configured default.
2. Whether its CLI is reachable on $PATH.
3. Whether it ANSWERS. A stopped daemon leaves the binary on $PATH and
every call failing; when another installed engine does answer, the
check names it and says how to switch.
4. WSL availability (informational — its absence is expected off
Windows, and explains a missing :Sandbox wsl).
5. The state of the hover.nvim integration: disabled, not installed,
registered, or installed-but-too-old to honour a request-only
contribution.
Then lib.nvim's composer checks the registered :Sandbox routes.
Every line it can print is spelled out in
https://github.com/StefanBartl/sandbox.nvim/blob/main/docs/health.md
About
Repository: https://github.com/StefanBartl/sandbox.nvim Issues: https://github.com/StefanBartl/sandbox.nvim/issues Contribution guide and architecture notes live in the repo'sdocs/directory (README.mdindexes it;CONTRIBUTING.mdandadd_usecase.mdare the two for contributors).LICENSEis in the repository root.