Install VCode on Linux with one command
curl -fsSL https://v-code.dev/install | sh installs the newest signed release
of VCode on a Linux x64 machine, starts it as a systemd --user service, and
links it to your v-code.dev account. You need no git, node or npm. The command
from "Add a computer" on app.v-code.dev adds a join code, so the box links and
pairs with that browser in one step, with no email prompt.
Why you would use it
You want VCode on a server or a spare machine and do not plan to work on its code. The release brings its own node and packages, and it keeps itself up to date (update-automatically). To run from a git clone instead, see install-as-systemd-units.
How to use it
The quickest way starts on app.v-code.dev: open "Add a computer" and copy the command it shows (add-a-computer). It looks like this:
curl -fsSL https://v-code.dev/install | V_CODE_JOIN=<code> sh
Paste it on the machine, logged in as the user VCode should run as. The installer runs as below, but at the end it joins instead of asking for your email: the box links to the account that made the code and pairs with the browser that showed it. That browser then opens the box. The code works once, for 15 minutes; after that, make a new command.
Without a join code:
Log in to the machine as the user VCode should run as, over ssh or at the console. Not through
su:systemctl --usermust answer.Run:
curl -fsSL https://v-code.dev/install | shOn a machine with wget and no curl, such as a stock Ubuntu desktop:
wget -qO- https://v-code.dev/install | shThe installer then downloads with wget too.
To pick the port yourself:
curl -fsSL https://v-code.dev/install | sh -s -- --port 3500When it asks, type your v-code.dev email, then the code from the email. This is
v-code box linkrunning on your terminal (link-this-box).Pair your phone:
v-code box pair(add-a-phone).
The installer has no screen of its own. It runs in the terminal only.
What you see
A first install prints a short summary. Each line starts with v-code:, in order:
v-code: installing <version>v-code: running at http://127.0.0.1:<port>v-code: agents: claude, codex, or, with none found:No agent found. Install claude, codex or opencode, then run: ~/.local/share/v-code/current/install.sh install- with
V_CODE_JOIN, the join:v-code: linked to <email>,v-code: paired with <browser name>andv-code: your browser opens it now - otherwise
v-code: to sign in on this machine, use the AUTH_TOKEN in ~/.config/v-code/env, then the link. With a terminal:Link this box to your v-code.dev account:and theEmail:prompt. With no terminal:v-code: Link this box: v-code box link. On a box already linked:v-code: Linked to <email>. v-code: details in ~/.local/share/v-code/install.log
The steps go to ~/.local/share/v-code/install.log (0600) instead of the
terminal. Each run of install.sh install appends to it under a line
== <UTC time> install.sh install <version>. The steps are:
unpacked <version> into ~/.local/share/v-code/versions/<version>, from the download script, above that headerwrote ~/.config/v-code/env (0600), or on a re-runenv file exists, left alone: ~/.config/v-code/envinstalled the updater: ~/.local/share/v-code/bin/v-code-updatewrote ~/.local/bin/v-codeandwrote ~/.local/bin/portal- one
rendered …line per unit - what
systemctl --userprints while it enables and restarts the units, such asCreated symlink … skipped v-code-tunnel: TUNNEL_TOKEN is empty in <env file>. Fill it in, then run: install.sh install
When a systemctl --user call fails, the install stops with
systemctl --user <arguments> failed; see ~/.local/share/v-code/install.log,
and its message is in that file.
A link that fails prints v-code: The link did not complete. Try again with: v-code box link.
The box runs either way. A free account gets the site's own refusal; linking
needs premium.
A join that fails prints The join did not complete: <reason> and
Make a new command under Add a computer. The box is installed and runs either
way. The reasons you may see:
that code is wrong, used or expired; make a new command under Add a computerthis box is linked to another account; unlink it firstthe site's answer does not match this join code
On a box already installed. The same command with V_CODE_JOIN only joins.
It prints v-code: already installed: <version>; joining, then the join lines.
Nothing is downloaded, so it works while dl.v-code.dev is down. No unit is
rendered or restarted, so agents running on the box carry on, even when a newer
release is out; the updater installs that one when the box is idle. A box
linked to the same account keeps its link and gains the browser.
When the installed release is from before the join, the command installs the
newest release instead and joins at the end of that install. It prints
v-code: the installed release predates the join; installing <version>. That
install restarts the units, so a turn an agent is running on the box stops.
When no newer release is out, it stops with
v-code: this release does not support V_CODE_JOIN.
On a box installed before the rename to VCode. Releases from before
2026-09-26 run as portal-workbench-prod.service and read
~/.config/claude-portal/env. Their updater never moves such a box to a newer
release: it reads latest.json, which stays at the last of them. Run the
install command once. It installs the newest release as it would over any older
one, and that release's install.sh install moves the env file, the state, the
units and their drop-ins to the new names. The threads, the AUTH_TOKEN, the
box's link and its paired devices stay. From then on the box updates itself
again. With V_CODE_JOIN, the command installs the newest release first and
joins at the end of that install, printing
v-code: the installed release predates the rename to VCode; installing <version>.
When no newer release is out, the installed release joins as it is.
When the old updater died part way through a switch, the command stops before
it changes anything:
v-code: an update from before the rename to VCode has not finished: run ~/.local/share/v-code/bin/v-code-update --update first, then this again.
That runs the old updater, which settles its own switch. Then run the install
command again.
Run it from a terminal, not from a thread or the shell panel of the box it
moves: the move stops that unit. Inside it, the command stops before it
downloads anything:
v-code: this is running inside portal-workbench-prod.service, which the move to VCode stops part way through. Nothing was changed. Run the install command from a terminal outside VCode: ssh, or a desktop terminal.
After the move, the release from before the rename is not one to go back to by
hand. Its install.sh writes a fresh env file at the old path with a new
AUTH_TOKEN, and starts the old units on empty state.
Other lines you may see:
warning: tmux not found, so the shell panel is off. Fedora: sudo dnf install tmux, Debian: sudo apt install tmuxnote: ~/.local/bin is not on PATH in this shell; a new login shell usually adds itwarning: Could not turn on linger, so VCode stops when you log out. Run: sudo loginctl enable-linger <user>— see Limits below.v-code: VCode did not answer on http://127.0.0.1:<port> within 60 s. See: journalctl --user -u v-codev-code: already installed: <version> (the newest release is <version>), thenv-code: to run its installer again: ~/.local/share/v-code/current/install.sh install. A re-run never installs an older or equal version. A re-run that installs a newer one keeps the release it replaced and removes any older one, except the one VCode runs; when VCode does not answer it removes none:v-code: could not ask VCode which version it runs, so no old release is removed this run. A re-run after an update that died part way settles that update first, as the updater would.
The checks that stop the install before anything is written:
| Check | Message |
|---|---|
V_CODE_JOIN with --update |
V_CODE_JOIN is for an install; --update never links |
V_CODE_JOIN is 34 letters, digits, - and _ |
V_CODE_JOIN is not a join code; copy the command again |
| Linux or macOS (a Mac takes the path in install-on-macos) | VCode installs on Linux and macOS only; this machine runs <os> |
| x86_64 | VCode installs on x86_64 only; this machine is <arch> |
tar, gzip, ssh-keygen, bash, head, find, readlink, flock |
<tool> is missing; install it and run this again |
curl or wget |
curl and wget are both missing; install either one and run this again |
| GNU tar | VCode needs GNU tar; this tar is: <first line of tar --version> |
| OpenSSH 8.1 or newer | ssh-keygen has no -Y verify: VCode needs OpenSSH 8.1 or newer |
sha256sum or shasum |
sha256sum and shasum are both missing; install coreutils and run this again |
systemctl |
systemctl is missing: VCode runs as systemd --user units |
| a user manager | systemctl --user does not answer: log in as this user over ssh or a console (not su) and run this again |
And the checks on the release itself, which install nothing when they fail:
signature check failed for releases/<version>/manifest.json: <ssh-keygen's message>download does not match the signed manifest: sha256 <got>, signed <signed>download is longer than the signed manifest's <size> bytesstable.json names <a> but its signed manifest is for <b>another install or update is running; try again when it has finished
With V_CODE_JOIN, a release whose install.sh features does not list join
stops the run with v-code: this release does not support V_CODE_JOIN. The
release is left unpacked under versions/, but current does not move and its
installer does not run; run the plain command, or wait for a newer release.
Options and settings
| Option | Default | What it changes |
|---|---|---|
--port N |
first free port from 3445 to 3499, skipping 3446 | VCODE_PORT in a new env file. An existing env file keeps its port |
--base URL |
https://dl.v-code.dev |
Where releases are read from. For tests |
V_CODE_JOIN=<code> |
unset | An environment variable, not an option: the join code from "Add a computer". The installer joins with it instead of asking for the email. It is never written to the env file |
The port pick binds each port on 127.0.0.1 with the release's node. 3446 is
skipped because it belongs to the voice sidecar, running or not. With no port
free the install stops: no free port from 3445 to 3499; pass --port N.
Where everything lives:
~/.local/share/v-code/
versions/<v>/ one unpacked release (at most the current one and the last good one)
current -> versions/<v> the units run from here
bin/v-code-update the updater the hourly timer runs
update-state/ installed, pending, rejected, agent-dirs
install.log what each install did, step by step (0600)
~/.local/bin/v-code runs current/node/bin/node current/bin/v-code
~/.local/bin/portal the same, under the name from before the rename
~/.config/v-code/env AUTH_TOKEN, VCODE_PORT and the rest (0600)
~/.local/state/v-code/ threads, the box's key, voice files
Commands on an installed box:
| Command | What it does |
|---|---|
~/.local/share/v-code/current/install.sh install |
Re-renders the units, restarts them, prints the URL and links again. Run it after you install an agent |
~/.local/share/v-code/current/install.sh join |
Waits for VCode, then joins with V_CODE_JOIN from the environment. Renders and restarts nothing. Without the variable: install.sh join needs V_CODE_JOIN: run the command from Add a computer on app.v-code.dev |
~/.local/share/v-code/current/install.sh features |
Prints what this release supports, one word per line: join and v-code-names |
~/.local/share/v-code/current/install.sh update |
Runs v-code-update --update now. See update-automatically |
~/.local/share/v-code/current/install.sh status |
systemctl status over VCode, the tunnel and v-code-update.timer |
~/.local/share/v-code/current/install.sh uninstall |
Disables and removes the units, then removes ~/.local/bin/v-code, ~/.local/bin/portal, current, .lock, bin/, versions/, update-state/, and any .stage.* or current.new.* a killed run left. The env file and the state stay. While an update runs it stops: an update is running; try again when it has finished |
Limits and known gaps
- Linux on x86_64 with
systemd --useronly; no Linux on arm64. The same command installs on a Mac, with launchd instead of systemd (install-on-macos). On Windows, see install-on-windows: it runs this installer inside WSL2. - Linger. Without it the user manager, and VCode with it, stops when you
log out. The installer runs
loginctl enable-linger <user>. logind asks polkit before it lets a normal user change that, so on a minimal server or a container without polkit it is refused. The install carries on and prints thesudo loginctl enable-linger <user>line; run it once. - The installer does not install agents. Install
claude,codex,opencodeorcursor-agent, then run~/.local/share/v-code/current/install.sh installso the unit'sPATHfinds it. It looks onPATH, beside nvm's node, in~/.opencode/bin, and in~/.local/bin, where claude's own installer putsclaude. install.shfrom a release refuses to run unlesscurrentpoints at that release:install through get-v-code.sh: current does not point at this release (<dir>).--portapplies only when the env file is new. To move an installed box, editVCODE_PORTin the env file and runinstall.sh install.cloudflaredis needed only when you setTUNNEL_TOKEN.
Related
- update-automatically — how the box keeps itself up to date
- add-a-computer — the command with the join code
- link-this-box — the link the installer runs at the end
- add-a-phone — the next step after linking
- install-as-systemd-units — the same units from a git clone
- install-on-windows — the same box on Windows, inside WSL2
- install-on-macos — the same command on a Mac, with launchd
- environment-variables — what the env file holds
- logs-and-troubleshooting — the journal