Skip to content
DocsAll docs
Console

Device workspaces

Run a workspace on your own macOS machine, inside a sandbox, and use it from a desk like any other workspace.

Last updated

On this page

Overview

A device workspace runs on your own machine instead of on Kode's servers. Its files are an ordinary folder on that machine. The tools of the workspace (read, write, edit, exec, process, watch, and the others) run in a sandbox, and the desk reaches them through the Kode gateway. A device workspace joins a desk the same way as other workspaces.

ItemDevice workspace
Runs onA machine you registered, while the background service is running on it.
FilesThe folder {workspaces_dir}/{account}/{name} on that machine. The account is your handle or the team the workspace belongs to.
NameUnique within its account, because it is the folder name. Two accounts can use the same name; their folders are separate. A name cannot change.
QuotaNone. The workspace uses no cluster resource.
OwnerYou. A device workspace is personal and takes no team.
DesksYour own desks only.

The folder

workspaces_dir is the folder that holds the folders of all device workspaces. The default is ~/kode/workspaces. To change it:

CLI
kode config set workspaces_dir /Volumes/work/kodekode config get workspaces_dir

The path must be absolute, owned by you, and not a symbolic link. Change it before you create workspaces. A workspace folder does not move with the setting.

When you delete a workspace, its folder is not removed. It is moved to {workspaces_dir}/{account}/.trash and purged 7 days later. If a folder of a running workspace is missing, the workspace reports FolderMissing and does not start. Kode does not make an empty folder in its place.

Accounts

A machine has one login and one device. The device is active in one account at a time: your personal scope or one team you belong to. Its workspaces and desks are those of that account.

CLI
kode login                  # the one login of this machinekode devices connect acme   # make the device active in team acmekode devices connect ana    # back to your personal scope (your handle)

The account that the device leaves sees it as offline. Nothing is deleted, and the workspaces of the other account wait on this machine. A second user cannot log in on the machine. The login is refused until the first user runs kode logout. A folder profiles/ from an earlier version is no longer used, and you can remove it.

Background service

kode login sets up the machine. It installs a background service for your user, which runs the device in its active account while it is enabled. It then asks for your password once, to install the network helper. The helper only edits names and loopback addresses, so this Mac can reach your environments. Use kode login --no-root to skip the password. A second login finds both in place and asks for nothing.

CLI
kode start                  # run the service, and repair the setupkode stop                   # stop the servicekode status                 # the service, each account's device, the tunnel, the network helper

The service keeps the names of every desk of the active account in /etc/hosts. A desk that is added, deleted or connected to another environment changes its names within ten seconds. A desk whose name is a public domain ending, such as dev or app, gets no names. These names are for this Mac only. A device workspace reaches only the services of its own desk.

After a restart the service starts at login. If something is missing, kode start installs it again. kode update restarts the service on the new version. The log is ~/Library/Logs/kode-daemon.log.

When you log out of the last account, the background service is removed. The network helper stays. The reference says how to remove it.

Why one password is needed

macOS lets only an administrator edit /etc/hosts and add loopback addresses. Names like web.main need the first. Each service needs its own loopback address for the second. Kode asks for your password once, before it runs sudo, and kode status and --no-root repeat the reason. The reason is this sentence:

text
macOS lets only root edit /etc/hosts (so names like web.main resolve) and add the 127.77.x.y loopback addresses (with a port redirect for service ports below 1024); the network helper does only that, with no network access and no credentials, and everything that connects to your environment runs as you.

The network helper is a small program that runs as root, and it does three things:

  • It edits its own marked block in /etc/hosts.
  • It adds and removes loopback addresses in 127.77.0.0/16.
  • It redirects service ports below 1024 on those addresses to a port above 60000, because only root can listen on a low port.

The helper cannot do anything else. It cannot open a network connection. It cannot read your login, your desk, or any token. It cannot change a line of /etc/hosts outside its block, or an address outside 127.77.0.0/16. It reads one file that your own service writes, and it checks every entry in it.

The tunnel and every connection to your environment run as you, in the background service. If you skip the password, workspaces and desks keep working, but the names of your environment do not resolve on this Mac. Run kode start later to install the helper.

Environment services

A device workspace in a desk that has an environment reaches the environment's services on loopback. The sandbox can connect only to loopback, so the device relays each service port to the service. The tunnel runs in the background service, and kode status shows it.

Each workspace reaches only its own desk's services, through its own relay ports. The relay ports of other workspaces are outside the sandbox, so they refuse it.

Each service port gets a variable in the workspace: KD_ENV_{SERVICE}_{PORT} is 127.0.0.1:{port}, and KD_ENV_{SERVICE} is the first port of the service. A service named db with port 5432 is KD_ENV_DB_5432.

The relay reaches the service through the tunnel of the workspace's own desk. It does not use /etc/hosts, so it works whichever desk the laptop opened last. It resolves the service name at every connection, so a changed service address needs no restart. A service that is added to the environment appears after the workspace restarts.

Take over a service

A device workspace in a desk that has an environment can take over one service of the environment. Everything that calls the service in the environment is then delivered to a server that runs in the workspace on your machine.

CLI
kode desks intercept api --workspace dev --port 8080=3000
ItemRule
PortThe server in the workspace listens on a port from 1024 to 17000. A port outside this range is refused with 422. The device connects to that port on the loopback address of your machine and refuses a port that its own tool servers, environment ports or proxy use.
DeviceThe device must be online. The service goes to your machine only while the workspace is ready and the device is connected.
OfflineWhen the device goes offline, the service keeps your machine for 30 seconds. After that the environment's own service starts again. The intercept stays. It takes hold again when the device reconnects.
StopWhen the workspace stops, the service returns to the environment at once.
Releasekode desks release {service} removes the intercept.

The server must be reachable on 127.0.0.1 inside the sandbox. The traffic goes through the gateway and the connection of the background service, so it is slower than an intercept of a cloud workspace.

The sandbox

The tools of a device workspace run under an operating system sandbox that the device process applies before the tool server starts. The sandbox cannot be turned off from inside the workspace.

AccessAllowed
WriteThe workspace folder and a private scratch folder. Nothing else, unless the workspace names extra access (see "Extra access").
ReadThe workspace folder, system directories, and toolchain directories. Not your home folder, ~/.ssh, ~/.aws, ~/.config, ~/.gnupg, ~/.kube, ~/.docker, or the Kode configuration folder.
NetworkEvery host through the sandbox proxy, except the Kode API, the hosts of device_deny_hosts, and cloud metadata addresses. A device_allow_hosts list in the configuration file restricts it to exactly that list. No direct connections: the proxy carries all of it. The proxy never connects to a loopback address of your Mac, by address or by name. That includes the tunnel listeners of every desk. The callback relay of the device process is loopback-only.
LoopbackOn macOS, connections to local servers on ports 1024 to 17000, and to the workspace's own relay ports for environment services.
EnvironmentA fixed set of variables. HOME, PATH, KD_*, GIT_CONFIG*, GIT_TERMINAL_PROMPT, *_PROXY, LD_*, DYLD_*, and SSH_AUTH_SOCK cannot be set. A device workspace takes no env in this version (422, a device workspace takes no env in this version).
Kode credentialsNone. The sandbox holds no token for the Kode API. Git gets credentials through the device process (see "Git").

By default a device workspace reaches every host. device_deny_hosts is a list of host names, exact or with a leading *., that it never reaches. The Kode API host is always on that list, because nothing inside the sandbox holds a credential or calls the API. device_allow_hosts is the opposite: when it is present, the workspace reaches exactly the hosts in it, and an empty list reaches none. To change either list, edit config.json in the Kode configuration folder, and run kode stop and then kode start. Addresses on your local network are not blocked by default; add them to device_deny_hosts, or use device_allow_hosts, if a workspace must not reach them.

The sandbox has no access to your Keychain, so security create-keychain fails. Tools that write a temporary file outside TMPDIR also fail, because the system temporary folder is not writable. sips does this when it converts an image. The proxy password of the sandbox is in its environment, because the tools need it to reach the proxy. The journal masks it.

The kode binary must not live inside a workspace folder: kode devices doctor and the background service refuse otherwise (the kode binary must not live inside a workspace folder), because the sandbox can overwrite it.

The sandbox holds no Kode credentials. No Kode command inside the workspace can sign in, because the login is in the configuration folder that the sandbox cannot read. Registry pushes are not available in this version.

Extra access

Some builds need more than the workspace folder. An iOS build reads Xcode and writes DerivedData. A workspace names extra access in access, a list of names, at create (kode devices workspaces create NAME --access xcode) or later (PUT /v1/workspaces/{id}/access). The new list applies at the next start. Kode stores only the names. Your Mac decides what each name grants.

kode devices access list shows every name on this machine. These names are built in to kode:

NameReadRead and write
xcodeEvery /Applications/Xcode*.app~/Library/Developer/Xcode/DerivedData, Archives, and ~/Library/Developer/CoreSimulator. Also the test socket of a booted iOS Simulator (/private/tmp/com.apple.launchd.*/com.apple.testmanagerd.unix-domain.socket), which the workspace may connect to.
user-tempYour macOS temporary and cache folders (DARWIN_USER_TEMP_DIR, DARWIN_USER_CACHE_DIR). They also hold the temporary files of your other apps.
ptyA pseudo-terminal: /dev/ptmx and every /dev/ttys*. xcodebuild test needs one to capture the test runner's output. The terminal devices of your own Terminal windows are /dev/ttys* too, so a workspace with pty can read and write them.
android~/Library/Android/sdk~/.android/avd, ~/.android/cache

Built-in names cover only what a workspace cannot make itself: SDKs, system folders and a pseudo-terminal. Package caches (cargo, npm, pip, go, gradle and the rest) need no access: ~ in a workspace is its own folder, so each workspace keeps its own. To share a cache with your Mac, define a name with devices access add. A built-in folder that does not exist is skipped. Kode makes the three xcode write folders if they are missing.

kode devices access add NAME --read PATH --write PATH defines a name of your own in config.json as device_access. ~ is your home folder. A built-in name that you define replaces the built-in. add with no paths hides a built-in. kode devices access rm NAME removes your definition, and a built-in comes back.

Every path goes through the checks of the sandbox. A path that is missing or is a symlink refuses the start. So does a path that covers your home folder, ~/.ssh, ~/.aws, ~/.config, the keychains, the workspaces folder, or the Kode configuration folder. A name that your Mac does not define refuses the start too. The workspace then shows AccessUndefined with the command that defines the name.

Git

Git in a device workspace clones and fetches private repositories over HTTPS with your connectors, as in a cloud workspace. The sandbox holds no Kode token. Git asks a helper in the sandbox. Your own git helpers, from your global or repository configuration, run before ours. The helper asks the device process on a loopback port. The device process asks Kode with the device token.

ItemGit in a device workspace
Hostsgithub.com and gitlab.com. With a device_allow_hosts list, the host must be in it. If it is not, git cannot connect and asks for no credential.
ConnectorsYour own connectors first, then the connectors of each team where you are an active member. A paused or removed member's team is skipped.
AccessRead only. A GitHub credential reads every repository of the installation. A GitLab credential reads one project. Each lives for at most one hour.
PushRefused by the provider. Use your own token in the clone URL. A username in the URL makes Kode answer nothing.
LimitThe device process answers at most 300 requests a minute from the sandbox. Kode counts none of them, so the provider's own limits apply beyond that.
Refusals403 this device is disabled, 409 the workspace is stopped, and the refusals of "Git credentials" in the errors reference. The helper writes kode git-credential: {sentence} to the output of git.
Kode API unreachableThe clone fails as if there were no helper.

What the sandbox does not limit:

  • On macOS, connections to any local server on ports 1024 to 17000 are open. A database or the Docker API that listens on one of them can be used by the tools of the workspace.
  • /etc and /Library can be read.
  • Any host the workspace reaches can be used to send data out of the sandbox. By default that is every host but the Kode API.
  • A process in the sandbox can ask the device process for git credentials, at most 300 a minute. It gets read-only credentials for your connectors. It also gets them for the connectors of every team where you are an active member, all at once. Each is valid for at most one hour. A GitHub credential reads every repository of its installation. A cloud workspace reaches only your own connectors and one team's.
  • A malicious API can ask your machine to move a workspace folder into .trash. The folder stays there for 7 days and can be moved back.

Snapshots

A snapshot is a copy-on-write clone of the workspace folder, in {workspaces_dir}/{account}/.snapshots/{name}/{id}. The clone shares disk blocks with the folder until either side changes, so it costs no disk space until you edit. A small tree is cloned almost at once. A large tree, such as one with a big node_modules, takes longer, and the workspace is stopped for that time. An edit to the folder never changes the snapshot, and an edit to the snapshot never changes the folder.

ItemSnapshot
FilesystemAPFS only. On any other volume the command is refused with snapshots need a copy-on-write filesystem (APFS); {workspaces_dir} is on {fs}, and Kode does not make a full copy instead. kode devices doctor reports whether the folder supports it.
LocationThe same disk as the workspace. A snapshot does not protect against the loss of the machine or its disk.
TakeThe device stops the workspace and every process it started, including detached ones. If one cannot be stopped, the snapshot is refused with could not stop the workspace's processes. Then it clones the folder, and starts the workspace again. A stopped workspace is only cloned. The tools of the workspace are unavailable during that time.
RestoreThe device stops the workspace, moves its folder to {workspaces_dir}/{account}/.trash, clones the snapshot into its place, and starts the workspace again. The snapshot stays. If the clone fails, the folder is put back unchanged.
CloneA new workspace on the same device. Its folder is a clone of the snapshot, made when the workspace first starts. The clone is in no desk. To clone into a desk, run kode desks clone-workspace WORKSPACE --snapshot ID --to NAME.
Mount points and permissionsA mount point inside the workspace stops the snapshot (the workspace contains a mount point; snapshots cannot cross it). A folder inside the workspace that you cannot read (mode 000) makes the snapshot fail.
Hard linksNot kept. Two names of one file become two separate files in the snapshot, and in a restore or a clone.
After deletionA snapshot stays when its workspace is deleted, until you delete it. Deleting a snapshot moves it to the trash, which is purged after 7 days.
QuotaNone.
OfflineRefused with 409, device {name} is offline.

A snapshot is held by the device that made it. Only that device can restore it or clone it.

Files out and in

The agent tools that move files work on a device workspace as they do on a cloud one, while the device is online. Every path stays inside the workspace folder.

ToolOn a device workspace
readAn image (PNG, JPEG, GIF, WebP) comes back as an image the agent sees.
downloadThe console URL and resources/read read the file through the gateway from your machine when it is opened. If the device is offline then, the download fails with the device did not answer; is it online?.
transferCopies a file to or from the workspace folder, through the gateway. Both ends must be online.

The gateway carries one range of a file at a time, at most 4 MiB, as one relay frame.

Enable, disable, and switch

Each device has two settings. enabled is on when you register. account is the handle or team that the device is active in. It starts as your handle.

Change them with the CLI, or with PATCH /v1/devices/{id} and a body such as {"enabled": false} or {"account": "acme"}. The call takes your login and never a device token. The console lists your devices with their active account, and can enable, disable or switch each one.

CLI
kode devices disable laptopkode devices enable laptopkode devices connect acme

A disabled device stops running its workspaces on its next poll, within 10 seconds. The workspace list shows each of them as stopped with the reason DeviceDisabled. Set "enabled": true to start them again. You can switch only to your own handle or to a team where you are an active member.

Not available

ItemStatus
LinuxNot supported in this version. kode devices doctor says so.
Container buildscontainer_build and container_push answer not available on this device.
PackagesNot managed. The package routes are refused with 422. Install the tools yourself, with a package manager that your allowed hosts reach.
Live pageNot available yet. The live page of a device workspace answers the live page is not available for device workspaces in this version.
SshNot available yet. A device workspace has no ssh door.
Environment accessNot available yet beyond kode env on your machine. The workspace has no network group of its own.
Env variablesNot available. The workspace takes no env.

Data on your machine

The files of a device workspace stay on your machine. Kode keeps no copy and makes no backup. If the machine or its disk is lost, the files are lost. Deleting the workspace moves its folder to the trash. Logging out removes the device and leaves every folder where it is.

Offline

A device is online while the background service is connected to the gateway. It reads offline after 45 seconds without a report.

ActionDevice offline
Start and stopRecorded. The device applies them when it reconnects.
Create and deleteRefused with 409, device {name} is offline.
ToolsRefused. The workspace reads stopped with the reason device offline.

A workspace of an idle desk is stopped, and a use of the desk starts it again.

A workspace that is in no desk stops when it has had no activity for the idle time of the region (30 minutes by default). It reads stopped with the reason Idle, and a start wakes it. A workspace whose device never reported activity is not stopped. The workspace shows the device it runs on as device_name.

Tasks

Register this machine

Reason for the task

Make this machine a device that can run workspaces.

Job set-up

Procedure

  1. Run the check that the machine can sandbox workspaces.

    CLI
    kode devices doctor

    The command prints ok and the sandbox it found.

  2. Add the machine as a device. Kode names it from the machine's hardware identity, so a machine has one device.

    CLI
    kode devices register
  3. Start the background service, if kode login did not.

    CLI
    kode start

Close-out

  1. Run kode devices list.
  2. Make sure that the device of this machine is online.

Create a device workspace

Reason for the task

Get a workspace whose files are in a folder on this machine.

Job set-up

Procedure

  1. Create the workspace.

    CLI
    kode devices workspaces create api

    The command prints the folder {workspaces_dir}/api.

  2. Start it.

    CLI
    kode devices workspaces start api

Close-out

  1. Run kode devices workspaces list.
  2. Make sure that api is ready.

Join a desk

Reason for the task

Let a desk use the workspace and keep it awake.

Job set-up

Procedure

  1. Create the workspace in the desk. A device workspace joins a desk only when it is created.

    CLI
    kode devices workspaces create web --desk desk-0123456789abcdef

Close-out

  1. Open the desk.
  2. Make sure that the tools of web are in the desk.

When the desk is idle, the device stops the workspace. When the desk is used, the device starts it.

Free this machine

Reason for the task

Stop using this machine as your device, or let another user log in on it.

Job set-up

Procedure

  1. Delete the workspaces of the device that you do not want to keep. kode devices workspaces delete api moves the folder to the trash.

  2. Run the logout.

    CLI
    kode logout

    The command removes the device and its token, then the background service.

Close-out

  1. Run kode devices list on another machine, or open your profile settings.
  2. Make sure that this machine is not in the list.

Logging in again as you registers the same device. It takes the workspaces back that are still in the folder. The token of a removed device stops working.

If another user logs in on this machine, the folders you left stay untouched. The device never adopts or trashes a folder of another user or another account. The login says how many folders belong to a previous user.

Limits of the machine identity

The hardware UUID of a machine is not a secret. Any program on the machine can read it. The first user to register a UUID owns the device. If another user registers it first, your login gets 409 with this machine is registered to another user. You cannot remove another user's device. Ask support to remove it, then log in again.

Machines cloned from one image can share a UUID and collide in the same way.

Take, restore, and clone a snapshot

Reason for the task

Keep a state of a workspace before a risky change, return to it, or start a second workspace from it.

Job set-up

Procedure

  1. Take a snapshot. The command waits up to 60 seconds for the device.

    CLI
    kode devices workspaces snapshot web --message "before the refactor"
  2. List the snapshots of the workspace.

    CLI
    kode devices workspaces snapshots web
  3. Restore the workspace to a snapshot.

    CLI
    kode devices workspaces restore web dsnap-0123456789abcdef
  4. Make a second workspace from a snapshot.

    CLI
    kode devices workspaces clone dsnap-0123456789abcdef web-try
  5. Delete a snapshot that you do not need.

    CLI
    kode devices workspaces snapshot-delete dsnap-0123456789abcdef

Close-out

  1. Read the result that each command prints.
  2. Make sure that the snapshot is in the list.

The folder that a restore replaced is in {workspaces_dir}/{account}/.trash for 7 days. A command that waits and gets no answer leaves the request pending. The device does it when the background service connects.

Troubleshooting

SymptomPossible causeCorrective action
409 with device {name} is offlineThe background service is not connected.Run kode start, and wait 45 seconds.
The workspace reads FolderMissingThe folder is not in {workspaces_dir}.Move the folder back from .trash, or delete the workspace.
snapshots need a copy-on-write filesystem (APFS)workspaces_dir is not on APFS.Set workspaces_dir to a folder on an APFS volume.
could not stop the workspace's processesA process of the workspace did not stop.Stop the process, and take the snapshot again.
the kode binary must not live inside a workspace folderThe kode program is in a workspace folder.Move the program out of the folder.

Simulator tests

A workspace builds for the iOS Simulator and runs xcodebuild test against it with --access xcode --access user-temp --access pty. Boot the simulator first, then start (or restart) the workspace: the simulator's test socket exists only while it runs, and the workspace is granted it when it starts. Building for a physical iPhone, and signing, are not supported: no access name opens your keychain, and a request that would (AccessRefused) is final. Swift macros may need -Xfrontend -disable-sandbox, because the macro plug-in cannot start its own sandbox inside the workspace's.