VCode home

Install VCode on Linux with one command

Agents: claude, codex, opencode, cursor · On: desktop · beta

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:

  1. Log in to the machine as the user VCode should run as, over ssh or at the console. Not through su: systemctl --user must answer.

  2. Run:

    curl -fsSL https://v-code.dev/install | sh
    

    On a machine with wget and no curl, such as a stock Ubuntu desktop:

    wget -qO- https://v-code.dev/install | sh
    

    The installer then downloads with wget too.

    To pick the port yourself:

    curl -fsSL https://v-code.dev/install | sh -s -- --port 3500
    
  3. When it asks, type your v-code.dev email, then the code from the email. This is v-code box link running on your terminal (link-this-box).

  4. 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:

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:

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:

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:

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:

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