VCode home

Install VCode on Linux with one command

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

curl -fsSL https://fezu.dev/install | sh

This command installs the newest signed release of VCode on a Linux machine (x86_64 or ARM64), starts it as a systemd --user service, and links it to your fezu.dev account. You need no git, node or npm. The command from Add a workstation on fezu.dev/account adds a join code, so the box links in one step, with no browser step. On a computer with a desktop session the command also installs the VCode desktop app (desktop-app).

You use the box through the app at app.fezu.dev. A release box has no page of its own: opening its port in a browser shows a line that points to the app.

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 fezu.dev/account: tap Add a workstation and copy the command it shows (add-a-workstation). It looks like this:

curl -fsSL https://fezu.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 opening the browser: the box links to the account that made the code, and the account page shows it in its list. Then open the box in the app at app.fezu.dev. 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://fezu.dev/install | sh
    

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

    wget -qO- https://fezu.dev/install | sh
    

    The installer then downloads with wget too.

    To pick the port yourself:

    curl -fsSL https://fezu.dev/install | sh -s -- --port 3500
    
  3. At the end VCode opens on its login card: in the desktop app the command has just installed, else in Chrome or Chromium, else your default browser. The card reads Create an account or log in to connect <computer>. We send a code to your email. Type your email and click Continue, then enter the code the email brings. A new email makes a new account with 30 days of the full plan (free-trial); an email you have used logs in to your account. The computer is linked to that account, and VCode comes back signed in with the computer in it. On a server with no browser, open the address it prints on any device, log in there, and paste the code the page shows into the terminal. This is v-code box link running on your terminal (link-this-box).

  4. Pair your phone: v-code box pair (add-a-phone).

  5. Later, type v-code in a terminal to open VCode. On a computer linked to your email it opens the desktop app signed in, or app.fezu.dev in the browser without the app. On a computer not linked it runs v-code box link, and on one still on an older trial account it runs v-code box trial, which gets no new start link now: open VCode on a device signed in to that account.

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 workstation on fezu.dev/account. 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.fezu.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 at its next hourly run. A box linked to the same account keeps its link.

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.

Running the command again on a box that runs VCode. From a terminal outside VCode, the command installs a newer release even while threads work: the restart resumes the turns it cuts. When VCode answers as the installed version, the command checks the switch the way the hourly update does. The new version must report itself running within 30 s and still run 5 s later. A good switch prints v-code: updated to <version>. A new version that fails goes back to the version you had. The hourly update does not try it again, but the install command installs it again when you run it:

A new version that never started is not held against it. The command switches back, and you can run it again: v-code: <old version> is still the version running: <version> was never started; switched back to <old version>; run the install command again to install <version>.

When VCode does not answer, the command installs as it would on a new box.

From a thread on the box itself, the command never restarts VCode: the restart would stop the command part way. It installs nothing, and the link step and the desktop app step run as usual: v-code: <version> is out, but this run is inside v-code.service, whose restart would stop it: nothing was installed. To install it now, run the install command from a terminal outside VCode. A run there that has to restart VCode stops before it downloads anything. That is a run with --port, one after an install that did not finish, and one while an update is still settling: v-code: this is running inside v-code.service (a VCode thread), which the install would stop part way through. Nothing was changed. Run it from a terminal outside VCode: ssh, or a desktop terminal.

The shell panel runs outside VCode's own unit, so the command behaves there as in any other terminal. VCode restarts, and the shell panel stays open.

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 or aarch64 VCode installs on x86_64 and aarch64 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.fezu.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 workstation on fezu.dev/account. The installer joins with it instead of opening the browser. It is never written to the env file
V_CODE_NO_ANALYTICS=1 unset Sends no install counts. See install-counts
V_CODE_APP=no unset Skips the desktop app (desktop-app)
V_CODE_APP=only unset Installs only the desktop app, no box; with V_CODE_START=<code> it opens signed in (desktop-app)

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
  app/<v>/, app/current    the desktop app, when installed
  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 workstation on fezu.dev/account
~/.local/share/v-code/current/install.sh link The link step alone, as at the end of an install: v-code box link on a computer not linked (or removed from its account), v-code box trial on an older trial account, or v-code: Already linked to <email>. Renders and restarts nothing
~/.local/share/v-code/current/install.sh features Prints what this release supports, one word per line: join, v-code-names, channel and link
~/.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, and the desktop app with its menu and autostart entries. The env file and the state stay; a desktop app's AppArmor rule stays too, and the command prints how to remove it. While an update runs it stops: an update is running; try again when it has finished

Limits and known gaps