Bring up a project's dev container and drop into its shell — from the command
line, without VS Code. devcon is the CLI counterpart to opening a project in
an IDE: it starts the stack if it's down, runs the one-time postCreateCommand
that VS Code would normally run, then execs you into a shell inside the
container.
It is the evolution of dcon
for a fully remote, terminal-first workflow.
# Install (downloads the right binary for your OS/arch into ~/.local/bin):
curl -fsSL https://raw.githubusercontent.com/totophe/devcon/main/install.sh | sh
# Then, from any project with a .devcontainer folder:
cd ~/your/project
devcondevcon brings the stack up (asking first if it's down), runs the one-time
postCreateCommand, and drops you into a shell inside the container.
You log into a remote host over SSH/mosh and land in a tmux session (e.g. via tmosh). From there:
tmux (host — owned by tmosh at login)
└── docker exec ← devcon: ensure the container is up + postCreate ran
└── your shell ← devcon execs you in (later: zellij workspace)
devcon deliberately does not touch the multiplexer. It never starts its
own tmux session, so there's no tmux-in-tmux. It just guarantees the container
is alive and gets you inside — then gets out of the way (exec, no lingering
wrapper).
Run devcon at a project root:
- Finds
.devcontainer/by walking up from the current directory. - Parses
devcontainer.json(JSONC — comments and trailing commas welcome). - Finds the running container (via the
devcontainer.local_folderlabel). - If the stack is down, asks
Start it? [Y/n], then brings it up (docker compose up -dfor compose stacks,docker runfor image-based) and runs the declaredpostCreateCommandonce. - Resolves the container-side workspace directory (
docker exec -w). - Resolves which shell to use, then
execsdocker exec -it -w … <shell>.
The Quick start one-liner is all you need. The installer honors a couple of environment knobs:
| Variable | Default | Purpose |
|---|---|---|
DEVCON_INSTALL_DIR |
~/.local/bin |
where to put the binary |
DEVCON_VERSION |
latest |
install a specific tag, e.g. v0.1.0 |
Update in place any time:
devcon self-updatedocker- A
.devcontainer/devcontainer.jsonat (or above) the project root
No VS Code, no Node, no @devcontainers/cli — devcon is a single static
binary.
devcon Bring the stack up (asking first) and drop into a shell
devcon -y Start the stack without asking if it's down
devcon --shell /bin/bash Override the shell for this run
devcon self-update Update to the latest release
devcon --help Show help
devcon figures out which shell to drop you into, and remembers the answer:
--shellflag.devcontainer/devcon.json(persisted from a previous run)- auto-detect inside the container (
$SHELL, then probezsh → bash → sh) - if detection is ambiguous, ask once and save the choice
On the standard wellmade images this is silent — they ship zsh, so step 3
resolves immediately.
Because VS Code isn't in the loop, the lifecycle hooks it normally runs never
fire. devcon runs them itself, with the spec's timing — important because
these dev containers sleep infinity and stay up across many connects, so a
devcon connect is an attach, not a start.
postCreateCommand — once per container creation.
- Containers
devconcreates carry adev.devcon.postcreatelabel. - Any container (including compose services it didn't create) also gets an
in-container sentinel at
/tmp/.devcon-postcreate-done. - Either signal makes later launches skip it. Survives restarts.
postStartCommand — once per container start.
- Keyed on the container's
State.StartedAt(sentinel/tmp/.devcon-poststart-<startedAt>). - Skipped on re-connects to the same running container; re-runs
automatically after a real
docker restart(newStartedAt→ new sentinel). This is what keeps start-time setup (e.g. wellmade'spost-start.sh) applied in a VS-Code-free flow.
Hooks run as the declared remoteUser (if that user exists in the container —
see below) in the resolved workspace folder. The wellmade scripts are
idempotent anyway, so the markers are an optimization, not a correctness
crutch.
devcon execs as the remoteUser from devcontainer.json — but only after
verifying that user actually exists in the running container (id <user>). If
the running image doesn't provide it, devcon warns and falls back to the
image's default user instead of failing with
unable to find user … in passwd file.
Per-project config lives at .devcontainer/devcon.json:
{
"shell": "/bin/zsh"
}Global fallback: ~/.config/devcon/config.json. Precedence: project over
global; --shell beats both.
The container name (for containers devcon creates) is derived from the
project path, same rule as dcon:
pwd |
Codename |
|---|---|
/home/user/workspaces/totophe/devcon |
totophe_devcon |
/home/user/workspaces/myproject |
myproject |
/home/user/projects/myapp |
myapp |
- zellij workspace mode — a
--workspaceflag that execszellij attach -c <codename>instead of a bare shell, completing thetmux → docker → zellijvision. Parked; the machinery is already in place (it only swaps the final exec command).
cargo build --release
cargo test --all
cargo clippy --all-targets -- -D warningsMIT — see LICENSE.