# Connect to a Workspace (/docs/developing-in-workspaces/connect-to-a-workspace) Once a workspace is created, it is reachable over SSH at `WORKSPACE_NAME.devsy`. If you chose an IDE, Devsy opens it after the workspace starts. Pick the IDE with `--ide` when you start a workspace, or set a default for all workspaces: ```sh devsy workspace up my-workspace --ide vscode devsy ide use vscode ``` Run `devsy ide list` to see every supported IDE. ### VS Code in the browser [#vs-code-in-the-browser] Devsy installs [openvscode-server](https://github.com/gitpod-io/openvscode-server) in the workspace and tunnels to it from localhost. ```sh devsy workspace up my-workspace --ide openvscode ``` ### VS Code [#vs-code] Install the [Remote - SSH extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) and the [code CLI](https://code.visualstudio.com/docs/editor/command-line), then: ```sh devsy workspace up my-workspace --ide vscode ``` If that fails, connect VS Code to `WORKSPACE_NAME.devsy` over SSH. ### JetBrains IDEs [#jetbrains-ides] Install [JetBrains Gateway](https://www.jetbrains.com/remote-development/gateway/). You need a license for the IDE you use. Start the workspace with the IDE name, for example: ```sh devsy workspace up my-workspace --ide goland ``` Devsy installs the IDE server in the workspace and opens Gateway. Click **Check Connection and Continue** in the prefilled SSH dialog. If that fails, connect Gateway to `WORKSPACE_NAME.devsy` over SSH. Fleet works only through a manually added SSH connection to `WORKSPACE_NAME.devsy`. ### SSH [#ssh] Devsy adds a `WORKSPACE_NAME.devsy` entry to `~/.ssh/config` when it creates a workspace. ```sh ssh WORKSPACE_NAME.devsy ``` Any IDE with remote SSH support can use this host. Without an `ssh` client, use the CLI: ```sh devsy workspace ssh my-workspace devsy workspace ssh my-workspace --command "echo Hello World" ``` ## IDE options [#ide-options] Each IDE has options such as the version. List and change them with: ```sh devsy ide get openvscode devsy ide set openvscode -o VERSION=v1.76.2 ``` You can also set an option for a single workspace with `--ide-option VERSION=...` on `devsy workspace up`. ## Desktop shortcuts [#desktop-shortcuts] * `Cmd/Ctrl + K` opens the command palette. * `Cmd/Ctrl + N` starts the New Workspace wizard. * `Cmd/Ctrl + 1` to `8` jumps between the main sections. * `Esc` closes the open sheet, dialog, or palette. # Continuous Integration (/docs/developing-in-workspaces/continuous-integration) `devsy ci` builds an ephemeral devcontainer, runs a command in it, and deletes the workspace afterwards. Use it to check that your `devcontainer.json` still builds and your tests pass inside it. ## How it works [#how-it-works] 1. Resolves a workspace from the current directory (or a given source). 2. Builds and starts the devcontainer (equivalent to `devsy workspace up`, without launching an IDE or touching your SSH config). 3. Runs the given command inside the running container. 4. Deletes the workspace - always, even when the command fails - unless `--keep` is passed. A non-zero exit from the command propagates as the exit code of `devsy ci`, so a failing test or build fails the CI job. ## Usage [#usage] ```sh devsy ci [flags] [workspace-path|workspace-name] -- [args...] ``` Run the test suite inside the devcontainer for the current project: ```sh devsy ci -- make test ``` Target a specific project folder and pass environment variables: ```sh devsy ci ./service -- npm test devsy ci --remote-env CI=true --remote-env NODE_ENV=test -- npm test ``` Select a specific devcontainer config (e.g. a named `.devcontainer/` profile): ```sh devsy ci --devcontainer id:ci -- make test ``` Keep the workspace around for debugging when the command fails: ```sh devsy ci --keep -- ./run-integration-tests.sh ``` ### Common flags [#common-flags] Run `devsy ci --help` for the full list. The most used: * `--keep` keeps the workspace instead of deleting it. * `--remote-env KEY=VALUE` sets an environment variable in the container at run time. Repeatable. * `--devcontainer` picks the config: `none`, `image:`, `id:`, or a path to a `devcontainer.json`. * `--cache-from` and `--no-cache` control the image build cache. * `--platform` runs the container under another platform, such as `linux/amd64`. * `--secret`, `--env`, `--build-secret`, and `--git-token` inject stored Devsy secrets and variables. See [Secrets](./secrets.mdx). ## Pre-building and pushing images [#pre-building-and-pushing-images] `devsy ci` focuses on running a command. To pre-build and publish a devcontainer image for reuse as a build cache, use `devsy workspace build`: ```sh devsy workspace build --repository ghcr.io/my-org/my-devcontainer --tag latest --push ``` Downstream CI jobs can then reference that image via `--cache-from` to speed up builds. ## GitHub Actions [#github-actions] Install the CLI, configure the docker provider, then run `devsy ci`: ```yaml name: Devcontainer CI on: push: branches: [main] pull_request: jobs: devcontainer: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Devsy run: | sudo curl -fsSL https://github.com/devsy-org/devsy/releases/latest/download/devsy-linux-amd64 -o /usr/local/bin/devsy sudo chmod +x /usr/local/bin/devsy - name: Run CI in devcontainer run: | devsy provider add docker && devsy provider use docker devsy ci -- make test ``` # Create a Workspace (/docs/developing-in-workspaces/create-a-workspace) A workspace is a containerized development environment that holds a project's source and the tools needed to work on it. A [provider](../managing-providers/what-are-providers.mdx) creates the environment it runs in. Devsy uses the [devcontainer.json](https://containers.dev/) specification, which VS Code dev containers and GitHub Codespaces also use, so a project that already has one needs no extra setup. If there is none, Devsy detects the project's language and offers a template. A workspace keeps its state when you stop and restart it. While it is running, it is reachable over SSH at `WORKSPACE_NAME.devsy`. Devsy can open it in a local IDE if you select one. ## Create a workspace [#create-a-workspace] The source can be a Git repository, a local folder, or an image such as `golang:latest`. ### Devsy Desktop [#devsy-desktop] Open **Workspaces** and click **Create**. The wizard has five steps: 1. **Provider.** Pick an initialized provider. You cannot change it later. 2. **Source.** Pick a template or an image, or enter a Git URL, image, or local path. **Show advanced options** lets you set the branch, commit, or PR, a project subfolder, the folder to open, the `devcontainer.json` path, and a prebuild repository. 3. **IDE.** Optional. The default is none. 4. **Review.** Check the workspace name and configuration. If the image has no build for your architecture, you can turn on **Run under emulation**. 5. **Launch.** Devsy creates the workspace and streams progress, then opens your IDE if you selected one. The desktop app runs `devsy workspace up` for you. ### Devsy CLI [#devsy-cli] [Install the CLI](../getting-started/install.mdx#install-devsy-cli) and add a provider, for example Docker: ```sh devsy provider add docker ``` **Git repository** ```sh devsy workspace up github.com/microsoft/vscode-remote-try-node ``` Add `@` and a ref to pick a branch, commit, or pull request: ```sh devsy workspace up github.com/microsoft/vscode-remote-try-node@main devsy workspace up github.com/microsoft/vscode-remote-try-node@sha256:15ba80171af11374143288fd3d54898860107323 devsy workspace up github.com/microsoft/vscode-remote-try-node@pull/108/head # GitHub only ``` Devsy forwards your git credentials, so private repositories work. Use `--id` to name the workspace, which lets you create several from one repository. **Local folder** ```sh devsy workspace up ./path/to/my-folder ``` Devsy copies the folder to the provider and builds from its `devcontainer.json`. See [.devsyignore](#devsyignore) to leave files out. **Image** ```sh devsy workspace up ghcr.io/my-org/my-repo:latest ``` Devsy generates a `devcontainer.json` that names the image. To run an image built for another architecture, add `--platform linux/amd64`. Emulation can be much slower, so prefer multi-arch images. The desktop app also has a catalog of ready-made images and flags any that do not fit your machine. **Existing container** ```sh devsy workspace up my-workspace --source container:$CONTAINER_ID ``` Only the `docker` provider supports this, and `--recreate` is rejected for these workspaces. To change where Devsy keeps its data, pass `--home=PATH` or set `DEVSY_HOME`. This helps on WSL when a workspace tries to mount a Windows path instead of a path inside WSL. ## .devsyignore [#devsyignore] Put a `.devsyignore` file in the root of a local workspace folder to leave files out of the upload when the provider streams the folder, as Kubernetes does. It uses [Docker ignore syntax](https://docs.docker.com/build/concepts/context/#dockerignore-files). Paths are relative to the folder, and the last matching rule wins. ```text big/ **/node_modules/ docs/*.tgz build/ !build/keep.txt ``` * A missing or empty file uploads everything. * An unreadable file or an invalid pattern stops the upload with an error that names the file. * It does not apply to extra bind mounts, to local Docker bind mounts, to remote Git clones, or to image builds, which use their own rules. `.gitignore` and `.dockerignore` are not used in its place. ## Inspect a workspace [#inspect-a-workspace] ```sh devsy workspace describe my-workspace ``` This prints the configuration and live state. Add `--result-format json` for scripts. Other commands: * `devsy workspace status ` shows whether it is running or stopped. * `devsy workspace logs ` streams the agent logs. * `devsy workspace ping ` checks that the agent is reachable. * `devsy workspace troubleshoot ` bundles diagnostics for an issue report. ## Recreate a workspace [#recreate-a-workspace] Recreating applies changes to `devcontainer.json` or its Dockerfile. If a prebuild repository is configured, Devsy looks there for the new image first. ```sh devsy workspace up my-workspace --recreate ``` Only the project folder and mounted volumes survive. Everything else in the container is lost. ## Reset a workspace [#reset-a-workspace] A reset pulls the latest Git changes or uploads your local folder again. For local-folder workspaces, it removes and re-seeds Devsy-managed named workspace volumes. Bind mounts and unmanaged volumes are not removed. ```sh devsy workspace up my-workspace --reset ``` # Reuse local credentials (/docs/developing-in-workspaces/credentials) Devsy makes some of your local credentials available inside the dev container, so you do not configure them in each workspace. It supports git credentials, Docker registry credentials, and GPG keys. Code that runs in the container, including lifecycle commands and features from `devcontainer.json`, can use these credentials. Do not enable them for repositories or `devcontainer.json` files you have not reviewed. SSH agent forwarding never exposes your private key. It does let anything in the container request signatures, and so act as you against any service the key can access, for as long as the workspace runs. ## Git [#git] Devsy provides HTTPS credentials through a [git credential helper](https://git-scm.com/docs/gitcredentials). To turn off this credential injection in the default context: ```sh devsy context set default -o SSH_INJECT_GIT_CREDENTIALS=false ``` SSH agent forwarding is separate and enabled by default (`SSH_AGENT_FORWARDING=true`). To disable it in the default context: ```sh devsy context set default -o SSH_AGENT_FORWARDING=false ``` Disabling HTTPS credential injection does not disable SSH agent forwarding. ## Docker [#docker] Devsy provides registry credentials through a [Docker credential helper](https://docs.docker.com/engine/reference/commandline/login/#credential-helpers), so you can pull and push private images from the container. To turn it off for all workspaces: ```sh devsy context set default -o SSH_INJECT_DOCKER_CREDENTIALS=false ``` ## GPG [#gpg] Devsy can forward your GPG keys into the container over the SSH tunnel, so you can sign commits there. To turn it on for all workspaces: ```sh devsy context set default -o GPG_AGENT_FORWARDING=true ``` Or for one workspace: ```sh devsy workspace up --ssh-gpg-forwarding my-workspace ``` # devcontainer.json (/docs/developing-in-workspaces/devcontainer-json) Devsy uses the [open `devcontainer.json` standard](https://containers.dev/) to define the development container for a project. Because VS Code and GitHub Codespaces use the same format, existing configurations work in Devsy, and the container behaves the same on every provider. If a project has no configuration, Devsy detects the language and uses a default. For the full format, see the [reference](https://containers.dev/implementors/json_reference/), the [VS Code documentation](https://code.visualstudio.com/docs/devcontainers/create-dev-container), and the [Codespaces documentation](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers#devcontainerjson). ## Where the file lives [#where-the-file-lives] Devsy looks for one of: * `.devcontainer/devcontainer.json` * `.devcontainer.json` * `.devcontainer//devcontainer.json` To use a specific file, pass its path: ```sh devsy workspace up github.com/my-org/my-repo --devcontainer-path ./path/to/devcontainer.json ``` A configuration can inherit from other files with `extends`, which takes a path or a list of paths. Keep files the configuration depends on inside its folder. ### Existing workspaces [#existing-workspaces] Devsy remembers which configuration a workspace uses. If a later search would pick a different file, Devsy warns and keeps the old one. To switch on purpose, recreate the workspace: ```sh devsy workspace up --recreate --devcontainer "" devsy workspace up --recreate --devcontainer "id:" ``` The choice is saved for later runs. ## Dockerfile [#dockerfile] ```json { "build": { "dockerfile": "Dockerfile" } } ``` ## Docker Compose [#docker-compose] Point `dockerComposeFile` at one or more compose files and choose the service to use as the dev container: ```json { "dockerComposeFile": ["docker-compose.yml", "docker-compose.dev.yml"], "service": "backend" } ``` Devsy names the compose project the same way Docker Compose does, so it reuses a project that is already running. In order: 1. `COMPOSE_PROJECT_NAME` in your shell. 2. `COMPOSE_PROJECT_NAME` in an `.env` file, in the devcontainer folder, the workspace root, or a compose file's folder. 3. The top-level `name:` in the compose files. The last file that sets it wins. 4. A name derived from the workspace. The `name` field in `devcontainer.json` is only a display name and never names the compose project. A custom `DOCKER_HOST` set for the Docker provider is passed to the `docker compose` commands Devsy runs. ## Features [#features] [Features](https://containers.dev/features) are reusable pieces that Devsy merges into your Dockerfile, such as `docker-in-docker` or `kubectl`. To send HTTP headers when downloading a feature archive, set them under `customizations`: ```json { "features": { "https://example.com/foo_feature.tar.gz": {} }, "customizations": { "devsy": { "featureDownloadHTTPHeaders": { "FOO_HEADER": "${localEnv:FOO_ENV_VAR}", "BAR_HEADER": "bar" } } } } ``` ## Applying changes [#applying-changes] After you edit the configuration, apply it without deleting the workspace: ```sh devsy workspace up my-workspace --recreate ``` This applies all changes, including the Dockerfile, mounts, and features. Devsy replaces the running container only if the rebuild succeeds, so a mistake does not break the existing workspace. Changes outside volumes are lost. The project folder and other mounted paths are kept. ## Environment variables in devcontainer.json [#environment-variables-in-devcontainerjson] When you use `${localEnv:NAME}` in `devcontainer.json` with the SSH provider, the variable has to reach the remote machine. 1. Reference the variable in `devcontainer.json`: ```json { "name": "Node.js", "image": "mcr.microsoft.com/devcontainers/javascript-node:${localEnv:IMAGE_VERSION}" } ``` 2. Send it from your local `~/.ssh/config`: ``` Host SetEnv IMAGE_VERSION=0-18-bullseye ``` 3. On the remote machine, add `AcceptEnv IMAGE_VERSION` to `/etc/ssh/sshd_config` and restart the SSH service. 4. Start the workspace: ```sh devsy workspace up --provider ssh --ide=none ``` # Devcontainer overlays (/docs/developing-in-workspaces/devcontainer-overlay) `--devcontainer-overlay` layers a second `devcontainer.json` over the config selected for a workspace: ```sh devsy workspace up --devcontainer-overlay ./devcontainer/overlay.json ``` The overlay is applied during build planning, before substitution and image creation. It supports these fields: `image`, `dockerFile`, `context`, `build`, `dockerComposeFile`, `service`, `runServices`, `initializeCommand`, `features` and `overrideFeatureInstallOrder`. It does not enable arbitrary config replacement. The primary config stays the effective origin. Paths inside the overlay, including local features, are relative to the file that declares them. ## How fields combine [#how-fields-combine] * **Same build type.** Fields missing from the overlay keep the primary value. * **Different build type** (image, Dockerfile or Compose). Fields from the previous type are cleared before the new selector applies. An overlay that selects Compose can replace a primary image config without keeping its image. * **`build`.** `args` merge by key, with overlay keys winning. `target`, `cacheFrom` and `options` replace the primary values. * **`dockerFile` and `context`.** The top-level forms are the same as `build.dockerfile` and `build.context`. Setting both forms to different paths is an error. * **Compose.** `dockerComposeFile` takes a path or a list, and replaces the primary selection as a whole. `service` replaces the service. `runServices` replaces the sidecar list. Relative paths resolve from the declaring file, including files reached through `extends`. Paths inside the Compose YAML follow Docker Compose's own rules. * **Required selectors.** They cannot be `null` or empty. Devsy reports an error instead of guessing. * **`initializeCommand`.** The overlay's command replaces the primary one. It runs on the host before the build, in the workspace root. A string runs through the shell, an array runs directly, and an object runs its commands in parallel. To disable the primary command, use `""`, `[]` or `{}`. `null` is an error. * **`runServices`.** Use `[]` to clear the primary list. * **Features.** Precedence is primary config, then overlay, then CLI `--features`. * **Runtime metadata.** The existing overlay behavior still applies. For example, `remoteEnv` values combine, with the overlay winning on matching keys. The feature lockfile stays next to the primary config. Structural overlays cannot attach to or replace a `containerID` config. ## Applying changes [#applying-changes] A missing or malformed overlay is an error. Devsy reads it before replacing an existing workspace, so a failure leaves the current workspace available. To apply a structural change to an existing workspace, recreate it: ```sh devsy workspace up --recreate --devcontainer-overlay ./devcontainer/overlay.json ``` After a successful creation, workspace cleanup uses the recorded invocation, so you can still delete the workspace if the overlay file is later removed or broken. # Dotfiles in a Workspace (/docs/developing-in-workspaces/dotfiles-in-a-workspace) Devsy can clone your dotfiles repository into a workspace and install it, so your shell and tool settings are there every time. Pass the repository with `--dotfiles`: ```sh devsy workspace up https://github.com/example/repo --dotfiles https://github.com/my-user/my-dotfiles-repo ``` Devsy then looks in the repository for one of these install scripts and uses the first that completes successfully, trying the next script if one fails: * `install.sh`, `install` * `bootstrap.sh`, `bootstrap`, `script/bootstrap` * `setup.sh`, `setup`, `setup/setup` If no script succeeds, Devsy links hidden files (names starting with `.`) into the container's `$HOME`. It skips directories, so `.config/` needs an install script. To run a different script, name it: ```sh devsy workspace up https://github.com/example/repo --dotfiles https://github.com/my-user/my-dotfiles-repo --dotfiles-script custom/location/install.sh ``` ## For all workspaces [#for-all-workspaces] Set the options on the context so every new workspace uses them: ```sh devsy context set -o DOTFILES_URL=https://github.com/my-user/my-dotfiles-repo -o DOTFILES_SCRIPT=bin/install.sh ``` # Auto-Inactivity Timeout (/docs/developing-in-workspaces/inactivity-timeout) Providers can stop workspaces that nobody is using, which saves cost. A machine stopped this way keeps its data, and Devsy restarts it when you start the workspace again. ## Configure the timeout [#configure-the-timeout] Each provider has an `INACTIVITY_TIMEOUT` option, for example `30m`. See [Set provider options](../managing-providers/manage-providers.mdx#set-provider-options). The provider's options show its default. ## How it works [#how-it-works] **Non-machine providers** such as Docker, Podman, Kubernetes, and Microsandbox run a process in the container that tracks activity. When nobody has connected for the timeout, it ends the container's main process. The container stops, its state is kept, and Devsy restarts it later. The Apple provider deletes the container instead of stopping it. Only data on mounted volumes survives. **Machine providers** run a Devsy daemon on the VM. When there is no activity for the timeout, the daemon shuts the machine down or deletes it, whichever costs less for that cloud. Devsy restarts or recreates it when you resume. See the [agent guide](../developing-providers/agent.mdx#machine-providers) for how providers implement this, and [Machine diagnostics](../managing-machines/machine-diagnostics.mdx) for debugging. # Agent Control via MCP (/docs/developing-in-workspaces/mcp-server) `devsy mcp serve` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio. AI clients that support MCP can use it to list, create, and run commands in workspaces. ```sh devsy mcp serve ``` Configure your MCP client to launch that command. The exact config format depends on the client. A typical shape: ```json { "mcpServers": { "devsy": { "command": "devsy", "args": ["mcp", "serve"] } } } ``` ## Tools [#tools] | Tool | Purpose | | ------------------ | ------------------------------------------------------------------------------------------- | | `workspace_list` | List workspaces with provider, IDE, and source. | | `workspace_status` | Get the saved workspace configuration. It does not report whether the workspace is running. | | `workspace_create` | Create and start a workspace. | | `workspace_start` | Start an existing workspace. | | `workspace_stop` | Stop a running workspace. | | `workspace_delete` | Delete a workspace. Accepts `force=true`. | | `workspace_exec` | Run a one-shot command in a running workspace. | | `provider_list` | List configured providers. | | `provider_add` | Add a provider from a registry name, GitHub URL, or local path. | | `provider_delete` | Remove a provider. | | `provider_use` | Set the default provider. | To check whether a workspace is running, use `devsy workspace status --result-format json`. `workspace_exec` is limited by flags on `devsy mcp serve`: `--exec-timeout-default` (5m), `--exec-timeout-max` (30m), and `--exec-output-cap` (100 KiB per stream). `--mcp-max-concurrent-ops` (8) limits concurrent exec, create, and start calls. ## Agent skill [#agent-skill] Devsy also publishes an agent skill that teaches an agent how to pick workspaces and providers, run commands, and recover from timeouts. It is separate from the MCP server: installing it does not install Devsy or configure MCP. It needs Node.js for `npx`. ```sh npx skills add devsy-org/devsy --skill devsy ``` Add `-g` to install it for all projects, or `--agent` to target one agent. ## Security [#security] The MCP server has the same authority as your local Devsy CLI. A connected client can create and delete workspaces, run any command in them, and add or remove providers. Only use it with clients you trust. # Prebuild a Workspace (/docs/developing-in-workspaces/prebuild-a-workspace) A prebuild is an image built ahead of time from `devcontainer.json`, its features, and any Dockerfile. When a prebuild exists, Devsy starts from it instead of building, which saves time on complex environments. ## How it works [#how-it-works] Devsy hashes the devcontainer configuration and uses `devsy-` plus the hash as the image tag. When you point Devsy at an image repository, it looks for that tag and uses the image if found. Build and push a prebuild: ```sh devsy workspace build github.com/my-org/my-repo --repository ghcr.io/my-org/my-repo ``` Devsy skips the build if the repository already has an image with the matching tag. It uses your current provider, so you can prebuild on a remote provider or a dedicated one. ## Use a prebuild [#use-a-prebuild] Pass the repository when you create a workspace: ```sh devsy workspace up github.com/my-org/my-repo --prebuild-repo ghcr.io/my-org/my-repo ``` Or set it in `devcontainer.json`, which suits builds run from CI: ```json { "name": "my-project", "customizations": { "devsy": { "prebuildRepository": "ghcr.io/my-org/my-repo" } } } ``` If no matching image is found, or you lack credentials for the repository, Devsy skips it and builds as usual. # Secrets in a Workspace (/docs/developing-in-workspaces/secrets) Devsy can inject sensitive values into a workspace without putting plaintext in `devcontainer.json`, command-line arguments or dotenv files. Values come from two places: * **Devsy-managed secrets**, stored in your OS keyring or in an encrypted local file. * **External sources**, such as a SOPS-encrypted file. Devsy reads them when needed and never copies them into its own store. Both use the same delivery paths: environment variables for lifecycle commands, in-memory files under `/run/secrets`, and build secrets. ## Devsy-managed secrets [#devsy-managed-secrets] Secrets are scoped to the active context. With the default `auto` backend, Devsy uses the OS keyring when there is one (Keychain, Credential Manager, or Secret Service on Linux). Otherwise it uses an age-encrypted `secrets.enc` file in the Devsy config directory. Only metadata such as names and timestamps is stored in plaintext. ```shell devsy secret set DB_PASSWORD # hidden prompt printf '%s' "$MY_VALUE" | devsy secret set DB_PASSWORD --stdin # for scripts devsy secret set TLS_KEY --from-file ./tls.key devsy secret list # never prints values devsy secret get DB_PASSWORD devsy secret delete DB_PASSWORD ``` In Desktop, **Workspace Variables** manages secrets and environment variables. Secret values are never shown. ## External sources (SOPS) [#external-sources-sops] Devsy reads [SOPS](https://github.com/getsops/sops)-encrypted YAML, JSON and dotenv files. It does not need the `sops` executable. The files must be flat top-level key/value pairs: ```yaml DATABASE_PASSWORD: ENC[...] API_TOKEN: ENC[...] ``` Register a local file as a named source. Devsy checks that it can decrypt the file first, and stores only the name, type, path and format override: ```shell devsy secret source add sops project ./secrets.enc.yaml devsy secret source list devsy secret source remove project ``` A source cannot be removed while a context secret attachment still references it. ### Sources declared by a repository [#sources-declared-by-a-repository] A repository can declare sources in `customizations.devsy` in its `devcontainer.json`: ```json { "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "customizations": { "devsy": { "secretSources": [ { "name": "project", "type": "sops", "path": "./secrets.enc.yaml" } ], "secrets": ["sops:project/DATABASE_PASSWORD"] } } } ``` Paths are relative to the repository root. Paths that escape it, including through symlinks, are rejected. This works for a local checkout (`devsy workspace up .`) and a remote repository, where Devsy reads the files from the same Git revision. Credentials needed to clone a private repository must exist before Devsy can read the repository's SOPS files. A repository's own secret cannot authenticate the clone of that repository. `--git-token` can reference a managed secret or another source that is already available locally. ### Credentials for SOPS [#credentials-for-sops] Devsy leaves key discovery to SOPS. For age, set `SOPS_AGE_KEY` or `SOPS_AGE_KEY_FILE`, or use the standard age key location. For AWS KMS, GCP KMS, Azure Key Vault and PGP, use the same configuration you would use with SOPS. ## Referencing secrets [#referencing-secrets] An unqualified name is a Devsy-managed secret: `DB_PASSWORD`. An external secret is source-qualified: `sops:project/DB_PASSWORD`. Devsy never searches other sources when a reference does not resolve. If any requested value cannot be resolved, `workspace up` fails. ### Environment variables [#environment-variables] `--secret` is repeatable and exposes the value to lifecycle commands during that `workspace up`. It does not appear in later terminal sessions. ```shell devsy workspace up . --secret DB_PASSWORD devsy workspace up . --secret sops:project/DATABASE_PASSWORD,target=DB_PASSWORD ``` `target=` renames the variable. Repository `secrets` bindings have the same scope. ### Files [#files] `type=mount` writes the value to an in-memory file under `/run/secrets`: ```shell devsy workspace up . --secret sops:project/TLS_KEY,type=mount,target=tls.key ``` The file is `/run/secrets/tls.key`. Providers without an in-memory mount reject this with an error. ### Build secrets [#build-secrets] ```shell devsy workspace up . --build-secret sops:project/NPM_TOKEN ``` The build secret ID is the key (`NPM_TOKEN`), not the full reference. Use it in a Dockerfile with `RUN --mount=type=secret,id=NPM_TOKEN ...`. ### Attach a secret to a context [#attach-a-secret-to-a-context] An attached secret is injected during workspace setup and into every new Devsy-managed SSH or terminal session, including `devsy workspace ssh` and the Desktop terminal: ```shell devsy secret attach DB_PASSWORD devsy secret attach sops:project/API_TOKEN devsy secret detach DB_PASSWORD ``` Only the reference is stored. Already-running sessions do not change. After detaching, run `workspace up` or recreate the workspace so new sessions lose the value. If names collide, a session uses: explicit session environment (`workspace ssh --set-env`), then attached secret, then the container's own environment. Terminal secrets are copied to `/run/devsy/secrets-env`, an owner-only `tmpfs` mount. If the provider cannot create that mount, setup fails instead of writing to the container's disk. Devsy may recreate an existing managed workspace to add it. ### Where each kind is available [#where-each-kind-is-available] | Source | Lifecycle environment | New SSH or terminal sessions | File mount | Image build | | ---------------------------- | --------------------- | ---------------------------- | ----------------------- | ------------- | | Attached secret | Yes | Yes | No | No | | `--secret NAME` | Yes | No | No | No | | `--secret NAME,type=mount` | No | No | `/run/secrets/` | No | | `--secrets-file` | Yes | No | No | No | | Repository `secrets` binding | Yes | No | No | No | | `--build-secret NAME` | No | No | No | BuildKit only | Environment variables are inherited by child processes. Use `type=mount` for a narrower scope. ## How secrets are protected [#how-secrets-are-protected] * SOPS sources are decrypted in memory and never written to disk. * Secrets are delivered as environment variables or memory-backed files. * Known secret values are redacted from logs and terminal output. * Values are kept out of client-server transport and workspace metadata. ## Storage backends [#storage-backends] Metadata is in `secrets.yaml`. Each secret records its backend when created, and it does not move if you change the preference later. Backends: * `keyring`: the OS keyring. * `file`: the age-encrypted `secrets.enc`. * `auto`: use `keyring` when available, otherwise `file`. This is only a preference for new secrets. Set the preference for a context, or for one command with `DEVSY_SECRETS_BACKEND`: ```shell devsy context set -o SECRETS_BACKEND=file ``` ### Passphrase for the file store [#passphrase-for-the-file-store] The file store is protected either by an automatically managed key or by a passphrase. The mode applies to the whole file, across all contexts, and is independent of the backend preference. ```shell devsy secret protection status devsy secret protection set-passphrase devsy secret protection change-passphrase devsy secret protection remove-passphrase ``` Devsy looks for the passphrase in this order, and a credential that fails is an error, with no fallback: 1. Explicit input from the UI or process. 2. `DEVSY_SECRETS_PASSPHRASE`. 3. `DEVSY_SECRETS_PASSPHRASE_FILE`. 4. A remembered credential in the OS keyring. 5. An interactive prompt, where supported. For automation, first run `devsy secret protection set-passphrase`, then point `DEVSY_SECRETS_PASSPHRASE_FILE` at a file of up to 64 KiB. The file only unlocks an existing passphrase-protected store. It does not choose the protection mode for a new store. Devsy strips one trailing newline, rejects an empty passphrase, and on Unix requires owner-only permissions such as `0600`. Remembering the passphrase is optional and shared by the CLI and Desktop: ```shell devsy secret protection remember devsy secret protection forget ``` `forget` removes only the saved credential. It does not change encryption or delete secrets. In Desktop, use **Manage security** on the **Secrets** tab. Changing protection needs confirmation in a native dialog. ### Recovery [#recovery] Without the passphrase or a valid remembered credential, the file contents cannot be decrypted. As a last resort, reset the whole file store: ```shell devsy secret protection reset-file-store ``` This lists every file-backed secret across all contexts and asks you to type `RESET`, or pass `--yes`. It moves `secrets.enc` to a quarantine file, removes the file-store metadata, and removes the remembered credential when the keychain is reachable. It leaves keyring secrets and `env.yaml` alone. You must recreate the lost secrets. ## Managed environment variables [#managed-environment-variables] For non-sensitive settings, `devsy env` stores plaintext values in `env.yaml`, separate from secrets. It works even when the secret store is locked. Never put a sensitive value here. ```shell devsy env set LOG_LEVEL=debug devsy env set REGION --value us-east-1 devsy env list devsy env get LOG_LEVEL devsy env delete LOG_LEVEL ``` Inject them at startup. The optional right-hand side renames the variable: ```shell devsy workspace up ... --env LOG_LEVEL --env REGION=AWS_REGION ``` `--env` accepts only managed environment variables, because it is not the protected delivery path. Attach a variable to the context to inject it on every start or recreate: ```shell devsy env attach LOG_LEVEL devsy env detach LOG_LEVEL ``` An explicit `--env` with a target overrides the attachment for that run. Attaching or detaching does not change a running workspace. Deleting a value also removes its attachment. ### Converting between the two [#converting-between-the-two] Detach a name before converting it between an environment variable and a secret. Converting an environment variable to a secret finishes when the plaintext entry is removed. If the process stops midway, the name can exist in both stores. Retry `devsy secret set`, or run `devsy env delete` to remove the plaintext entry. # Stop or Delete a Workspace (/docs/developing-in-workspaces/stop-and-delete-a-workspace) ## Stop a workspace [#stop-a-workspace] Stopping pauses a workspace so it stops using CPU and memory. Depending on the provider, Devsy stops the container or shuts down the VM it runs on. Its data is kept. In Devsy Desktop, open the workspace's actions menu and choose **Stop**. With the CLI: ```sh devsy workspace stop my-workspace ``` Start it again with: ```sh devsy workspace up my-workspace ``` Some providers stop idle workspaces on their own. See [Auto-inactivity timeout](./inactivity-timeout.mdx). Stopping or deleting a workspace first cancels any running `devsy workspace up --detach` task for it. To cancel a task yourself, use `devsy workspace task cancel `. ## Delete a workspace [#delete-a-workspace] Deleting removes the workspace and everything Devsy stores about it. Depending on the provider, that means removing a container or deleting a VM. In Devsy Desktop, open the workspace's actions menu, choose **Delete**, and confirm. If the action fails, the error appears on the workspace detail page, where you can also open the logs. With the CLI: ```sh devsy workspace delete my-workspace ``` `devsy workspace down` and `devsy workspace rm` are aliases for the same command. If the provider is unreachable, you can force the delete: ```sh devsy workspace delete my-workspace --force ``` This only removes the workspace on the Devsy side and ignores provider errors, so it can leave cloud resources behind. # Workspace Snapshots (/docs/developing-in-workspaces/workspace-snapshots) A snapshot captures a workspace at a point in time: the container's filesystem, such as installed packages and config changes, and the contents of its bind-mounted volume, including uncommitted code. Snapshots are stored as OCI artifacts in a registry you control. You can restore one later or use it to move a workspace to another provider. Snapshots are pushed to and pulled from an OCI registry, not stored locally. You need push and pull access to one. ## Limitations [#limitations] * Only workspaces with exactly one bind mount are supported. * Only local providers, such as Docker and Podman, can create snapshots. Machine-provider workspaces cannot. ## Create a snapshot [#create-a-snapshot] The workspace must already exist and be running. ```sh devsy snapshot create my-workspace --registry ghcr.io/my-org/snapshots ``` The command prints the snapshot reference, which you need to restore it. Add `--message` to describe the snapshot. To avoid passing `--registry` each time, set a default for the context: ```sh devsy context set -o SNAPSHOT_REGISTRY=ghcr.io/my-org/snapshots ``` ## List snapshots [#list-snapshots] ```sh devsy snapshot list my-workspace --registry ghcr.io/my-org/snapshots ``` This shows every snapshot of that workspace, newest first. ## Restore a snapshot [#restore-a-snapshot] ```sh devsy snapshot restore ``` This creates a workspace from the saved filesystem and volume and skips the devcontainer build. It reuses the original workspace ID. Use `--workspace-id` to pick another one, or `--target-provider` to restore onto a different provider. You can also restore as part of `devsy workspace up`. This cannot be combined with a source argument or `--source`. ```sh devsy workspace up --from-snapshot ``` To move a workspace to another provider, restore its snapshot with `--target-provider`. ## Delete a snapshot [#delete-a-snapshot] ```sh devsy snapshot delete ``` This removes the manifest from the registry. The registry's own garbage collection removes the image and volume blobs. ## Export and import [#export-and-import] `devsy workspace export` and `devsy workspace import` carry the snapshot reference for a workspace that was restored from one. The import restores the real filesystem and volume only if the manifest still exists and the importing machine can pull from the registry. Otherwise only the workspace metadata carries over. ## What is included [#what-is-included] The committed container image carries the label `sh.devsy.snapshot=true`, so you can find it with `docker images --filter label=sh.devsy.snapshot`. `.devsyignore` rules apply to the workspace mount and not to the container image. Files they exclude are missing from the snapshot. If snapshot creation reports missing build-context metadata, run `devsy up` to refresh it. # Provider Agent (/docs/developing-providers/agent) When Devsy connects to an environment through a provider, it injects itself there. This counterpart is the Devsy agent. It is the same binary, run as `devsy agent`. The agent deploys the container, forwards credentials, runs the SSH server and stops idle environments. The `agent` section of `provider.yaml` controls how Devsy works with it. ```yaml agent: path: ${DEVSY} local: true driver: docker inactivityTimeout: 10m containerInactivityTimeout: 10m injectGitCredentials: ${INJECT_GIT_CREDENTIALS} injectDockerCredentials: ${INJECT_DOCKER_CREDENTIALS} binaries: MY_BINARY: - os: linux arch: amd64 path: https://example.com/my-binary checksum: exec: shutdown: |- ${MY_BINARY} stop ``` Option values can be used in this section. * **path**: where the agent is placed on the remote machine. * **local**: set to `true` when Devsy runs directly on this machine instead of reaching a remote one. The built-in providers do this. * **driver**: how the container is deployed. One of `docker` (default), `kubernetes`, `apple`, `microsandbox`, `custom` or `external`. See [Drivers](./driver.mdx). * **inactivityTimeout**: how long to wait before stopping an idle machine. For machine providers. * **containerInactivityTimeout**: how long to wait before stopping an idle container. For non-machine providers. * **injectGitCredentials**, **injectDockerCredentials**: whether to copy those credentials into the environment. * **exec.shutdown**: the command that stops the machine once it is idle. Option values are available in it. * **binaries**: helper binaries for `exec.shutdown`. Same format as [provider binaries](./binaries.mdx). ## Stopping idle environments [#stopping-idle-environments] Devsy tracks whether anyone is connected and stops the environment after the timeout. Devsy starts it again when the user returns. State persists only if the provider preserves the storage. A shutdown command that deletes the machine must keep workspace data separately, for example on a persistent volume. ### Non-machine providers [#non-machine-providers] The agent ends the container's main process, which stops the container. Set the timeout with `containerInactivityTimeout`. ### Machine providers [#machine-providers] Stopping only the container would leave the VM running and billed. Define `agent.exec.shutdown` with the command that stops or deletes the machine on the cloud side. Set the timeout with `inactivityTimeout`. The official cloud providers each do this their own way, for example with a cloud API call using a token saved in an option. # Provider Binaries (/docs/developing-providers/binaries) The `binaries` section lists helper binaries Devsy downloads for a provider, such as a cloud CLI. Each entry is a name with one item per platform: ```yaml binaries: MY_BINARY: - os: linux arch: amd64 path: https://example.com/my-binary-linux-amd64 # URL or local path checksum: archivePath: bin/my-binary # only if path is an archive name: my-binary # optional, defaults to the file name - os: darwin arch: arm64 path: https://example.com/my-binary-darwin-arm64 checksum: ``` Supported archives are `.tgz`, `.tar`, `.tar.gz` and `.zip`. When a user adds the provider, Devsy picks the item that matches their OS and architecture, downloads it and verifies the checksum. See the official providers, such as [devsy-provider-aws](https://github.com/devsy-org/devsy-provider-aws), for complete examples. Not every provider needs `binaries`. Built-in providers that wrap a CLI the user already has, such as `docker`, expose an option like `DOCKER_PATH` instead of downloading one. ## Use a binary [#use-a-binary] Devsy exposes each binary's path as an environment variable with the same name. In `exec`: ```yaml exec: init: ${MY_BINARY} init ``` In an option `command`: ```yaml options: MY_OPTION: command: ${MY_BINARY} retrieve-option ``` On the agent side, declare the binary under `agent.binaries` and use it in `agent.exec`: ```yaml agent: binaries: MY_BINARY: - os: linux arch: amd64 path: https://example.com/my-binary-linux-amd64 checksum: exec: shutdown: ${MY_BINARY} stop ``` # Drivers (/docs/developing-providers/driver) A driver decides how Devsy deploys the workspace container. Set it with `driver` in the [agent section](./agent.mdx). The default is `docker`. Built-in drivers: `docker`, `kubernetes`, `apple`, `microsandbox` and `custom`. A sixth, `external`, runs a runtime plugin and is [described below](#external-driver). ## Docker Driver [#docker-driver] The Docker driver is the default driver that Devsy uses to deploy the workspace container. This container (specified through a [devcontainer.json](../developing-in-workspaces/devcontainer-json.mdx)), is executed through Docker inside the provider environment, for example in a VM [in case of Machine Providers](../managing-providers/what-are-providers.mdx). Some optional configs are available: * **path**: where to find the Docker CLI or a replacement, such as Podman. Defaults to `docker`. * **install**: whether to install Docker or not in the target environment * **builder**: which docker builder to use * **runtime**: explicitly select the container runtime (`docker`, `podman`, or `nerdctl`). When empty, the runtime is auto-detected from the binary at `path`. * **elevation**: optionally run docker commands through a privilege-elevation helper for rootful daemons whose socket the current user can't access. One of `pkexec`, `sudo`, `doas`, or `none` (default). * **helperImage**: overrides the helper image used for volume operations. Empty falls back to the `DEVSY_HELPER_IMAGE` environment variable, then a built-in default. * **env**: a map of environment variables to set when running docker commands, e.g. `DOCKER_HOST` Example config: ```yaml agent: containerInactivityTimeout: 10m docker: path: /usr/bin/docker install: false elevation: none env: DOCKER_HOST: "" ``` ## Kubernetes Driver [#kubernetes-driver] Instead of Docker, Devsy is also able to use Kubernetes as a Driver, which allows you to deploy the workspace to a Kubernetes cluster instead. For example, this makes it possible to create a provider that spins up a remote Kubernetes cluster (or just a namespace), connects to it, and creates a workspace there. Devsy also has a default Kubernetes provider that uses the local Kubernetes config file to deploy the workspace. The allowed options for the Kubernetes driver are: * **kubernetesContext**: which kube context to use (if empty will use current kube context) * **kubernetesConfig**: path to which kube config to use (if empty will use default kube config, or `$KUBECONFIG`) * **kubernetesNamespace**: which namespace to use (if empty will use current namespace or default) * **kubernetesPullSecretsEnabled**: if true, Devsy will create Kubernetes pull secrets from injected Docker credentials for private registries * **podTimeout**: how long the provider waits for the workspace pod to come up, e.g. `10m` * **createNamespace**: if true, Devsy will try to create the namespace * **clusterRole**: if defined, Devsy will create a role binding for the given cluster role for the workspace container. This is useful if you need Kubernetes access within the workspace container * **serviceAccount**: if defined, Devsy will use the given service account for the dev container * **architecture**: the CPU architecture to use for the workspace pod, e.g. `amd64`, `arm64`. If empty, Devsy inspects the cluster's nodes to auto-detect it (and errors out if the cluster has mixed architectures). * **inactivityTimeout**: after how much time to automatically stop the pod due to inactivity * **storageClass**: the storage class to use to create the persistent volume claim * **diskSize**: the default size for the persistent volume to use, e.g. `10Gi` * **pvcAccessMode**: the access mode to use for the persistent volume claim, e.g. `RWO`, `ROX`, `RWX` or `RWOP` * **pvcAnnotations**: annotations to add to the main workspace PVC * **nodeSelector**: the node selector to use for the workspace pod, e.g. `my-label=value,my-label-2=value-2` * **resources**: resource requests/limits for the workspace container, e.g. `requests.cpu=500m,limits.memory=5Gi` * **workspaceVolumeMount**: overrides the path where the workspace volume is mounted. Defaults to the root of your workspace source code. * **podManifestTemplate**: a pod manifest template (inline YAML or a file path) used as the base to build the Devsy pod * **labels**: labels to add to the workspace pod, e.g. `devsy.sh/example=value,devsy.sh/example2=value2` * **strictSecurity**: Clears the hardcoded `runAsUser`/`runAsGroup`/`runAsNonRoot` fields (retaining capabilities and `privileged`), letting the cluster assign the container's UID/GID instead of forcing root. It sets `hostUsers: false` unless `podManifestTemplate` explicitly supplies `hostUsers`; this satisfies OpenShift `restricted-v3`. * **agentSecurityContext**: Inline YAML or a file path for a `corev1.SecurityContext` merged field by field onto the workspace and init containers. It can configure run-as, capabilities, privilege escalation, seccomp, and other supported security-context fields, overriding Devsy's defaults. It sets `hostUsers: false` unless `podManifestTemplate` explicitly supplies `hostUsers`. A matching named container in `podManifestTemplate` remains the highest-precedence override. * **agentInstallPath**: overrides where the agent binary is installed inside the devsy/devsy-init containers. Defaults to `/usr/local/bin/devsy`, which requires root to write; set this to a path under a writable mount (e.g. the workspace volume) when running non-root. * **kubernetesUserNamespaces**: Sets `hostUsers: false` (unless `podManifestTemplate` already set it), mapping the pod's UIDs into a Linux user namespace. `strictSecurity` and `agentSecurityContext` also opt in for OpenShift `restricted-v3`; provide `hostUsers` explicitly through `podManifestTemplate` on clusters without user-namespace support. UserNamespacesSupport is disabled by default in Kubernetes 1.30–1.32, enabled by default in 1.33–1.35, and becomes GA with the gate locked on from 1.36. Node-level support is also required (Linux kernel 6.3+, containerd 2.0+/CRI-O 1.25+). On OpenShift, the default container security context (fixed `runAsUser`/`runAsGroup`) is rejected by the `restricted-v2`/`restricted-v3` SCCs, which assign UIDs/GIDs from a per-namespace range. Set `strictSecurity: "true"` and/or `agentSecurityContext` (or override the container's `securityContext` via a named container in `podManifestTemplate`) to satisfy those SCCs. Also set `agentInstallPath` to a path under a writable mount, because a non-root container cannot write the default `/usr/local/bin/devsy`. Devsy also supports building images inside Kubernetes without Docker, via a `dockerless` build path (`agent.dockerless.disabled` / `agent.dockerless.image`). This replaces the previous buildkit-based building approach. ### Example Kubernetes Provider [#example-kubernetes-provider] Example Kubernetes provider that uses local kubectl to run a workspace in the current kube context: ```yaml name: simple-kubernetes version: v0.0.1 agent: containerInactivityTimeout: 10m # Pod will automatically kill itself after timeout path: ${DEVSY} driver: kubernetes kubernetes: # kubernetesContext: default # kubernetesNamespace: my-namespace-for-devsy # clusterRole: "" # serviceAccount: "" diskSize: 20Gi createNamespace: true exec: command: |- ${DEVSY} helper sh -c "${COMMAND}" ``` Then add the provider via `devsy provider add ./simple-kubernetes.yaml` ## Apple Driver [#apple-driver] The Apple driver runs the workspace as a Linux container using Apple's native `container` runtime (macOS 26+, Apple silicon). This is the driver used by the built-in `apple` provider. Available options: * **path**: where to find the `container` binary. Defaults to `container`. * **rosetta**: enable Rosetta for x86\_64 emulation inside the Linux guest. * **env**: a map of environment variables to set when running `container` commands ```yaml agent: containerInactivityTimeout: 10m driver: apple apple: path: container rosetta: "false" ``` ## Microsandbox Driver [#microsandbox-driver] The Microsandbox driver boots the devcontainer image as a hardware-isolated microVM using [microsandbox](https://github.com/microsandbox/microsandbox). It is used by the built-in `microsandbox` provider. Options: * **memory**, **cpus**: guest memory in MiB and virtual CPU count. Empty uses the runtime default. * **maxMemory**, **maxCpus**: ceilings for hotplugged memory and CPUs. * **storage**: root disk size in GiB. Falls back to the devcontainer's `hostRequirements.storage`. * **blockEgress**: deny the microVM outbound public network access. * **ephemeral**: boot from a tmpfs root disk, so disk state is discarded when the VM stops. * **workspaceHostPermissions**: `mirror` (default) mirrors guest permission changes on the workspace mount to the host. `private` keeps them in microsandbox metadata. * **workspaceStatVirtualization**: `strict` (default) requires host metadata support. `relaxed` tolerates filesystems without it. `off` exposes literal host ownership and modes, and cannot be combined with `mirror`. Changing the workspace permission policy or the developer identity requires `devsy up --recreate`. The VM root disk is discarded on recreate, so back up VM-local data first. The host workspace and named volumes persist. ```yaml agent: containerInactivityTimeout: 10m driver: microsandbox microsandbox: memory: "2048" blockEgress: "false" workspaceStatVirtualization: "strict" ``` ## Custom Driver [#custom-driver] The custom driver lets a provider implement the devcontainer lifecycle with its own shell commands. With `driver: custom`, these commands are required under `agent.custom`: * **findDevContainer**: find an existing devcontainer. * **commandDevContainer**: run a command inside it. * **targetArchitecture**: print the target architecture. * **runDevContainer**, **startDevContainer**, **stopDevContainer**, **deleteDevContainer**: run, start, stop and delete it. Optional: **getDevContainerLogs** prints the logs, and **canReprovision** says whether the devcontainer can be reprovisioned in place. ## External Driver [#external-driver] `driver: external` runs a runtime plugin that speaks [Runtime Protocol v1](./runtime-protocol.mdx). ```yaml agent: driver: external external: binary: RUNTIME_DRIVER args: ["serve"] imageBackend: docker binaries: RUNTIME_DRIVER: - os: linux arch: amd64 path: https://example.com/runtime-driver checksum: "" ``` * **binary**: the exact key in `agent.binaries`. It is not a path and is never searched on `PATH`. * **args**: a fixed argument list. No shell or option substitution is applied. * **imageBackend**: `docker` (default) or `none`. It picks the Devsy-side image backend, not a build capability in the plugin. With `none`, prebuild publishing is unavailable. Every platform declaration for the selected `agent.external.binary` key requires a SHA-256 checksum. Agent setup prepares the executable; the external host verifies its bytes before launching each operation and does not download or remove it. The selected path can be relative to the prepared agent-binaries directory or an absolute local path. It must resolve to a regular file, with effective execute access on Unix. This verification applies to the selected external runtime, not every arbitrary provider installation binary. The supervisor is Devsy's own executable. The plugin is trusted provider code and runs with the agent user's access. See the [runtime rollout decision](./external-runtime-rollout.mdx) for MicroSandbox migration and compatibility limits. # External MicroSandbox Runtime Rollout Decision (/docs/developing-providers/external-runtime-rollout) **Status: proposed and gated.** The external provider is validated for the covered Linux/KVM scenarios, but the fresh-install choice has not changed. Existing internal providers continue to use the embedded MicroSandbox driver. A reviewed Desktop preset change can select the published source for new installations after these documented compatibility and evidence boundaries are accepted. ## Current host and runtime contract [#current-host-and-runtime-contract] Devsy starts a supervised provider process for each runtime operation. It selects the executable from the selected `Agent.Binaries` entry, requires its SHA-256 declaration, verifies the resolved file and its bytes, then launches it through the Devsy runtime supervisor. The SDK transports protocol messages; it does not download or establish trust in the provider executable. The published provider is trusted host code and runs with the local agent user's authority. The external provider inherits the host process environment, then receives Devsy's resolved provider/workspace values; this path supplies no separate extra-environment map. This preserves existing provider behavior for settings such as `PATH`, `HOME`, proxy configuration, Docker credentials, and temporary directories. The current evidence does not justify an environment allowlist. Provider installation downloads and runtime image downloads are separate from the host's verification of the selected `Agent.Binaries` executable. Runtime Protocol v1 uses Info API 1.3 to negotiate recreation-on-run support. Negotiation does not grant permission: Devsy forwards the current operation's explicit recreation permission, and the runtime validates effective configuration before replacing a workspace. This is replacement, not in-place reprovisioning. Failures after replacement starts do not have a general rollback guarantee. ## Verified behavior [#verified-behavior] PR #1483 established the Linux/KVM parity foundation and is merged. The source-migration acceptance is [PR #1487](https://github.com/devsy-org/devsy/pull/1487), tested head [`0aeea936`](https://github.com/devsy-org/devsy/commit/0aeea936aba901a4d2a1622458cda1dfb9ac0cdf), implementation run [38628263920](https://github.com/devsy-org/devsy/actions/runs/38628263920). Its exact-head CI completed 88 jobs successfully. The Linux/KVM native catalogue passed 80 cases once: 37 built-in and 43 external. It includes three external-only registry-consumer cases, one remote-agent delivery case, and two existing-workspace source-migration cases. The change is merged at [`e4286137`](https://github.com/devsy-org/devsy/commit/e42861373711f28aea7262baa5561a5e97f5a17a) after completed Greptile and full CodeRabbit review, including supplemental security/architecture analysis. The registry cases exercise the published provider against an owned TLS registry and credential helper. They cover successful authenticated access, missing CA refusal, and missing credentials refusal. The remote-agent case checks provider runtime download and workspace-copy reuse when its runtime endpoint returns 404. Its separate Devsy-agent endpoint stays available; this is not an offline-agent guarantee. The source migration cases use the actual `provider set-source` command on an existing provider named `microsandbox`, once while its workspace is running and once while stopped. They check provider source and options, workspace association, VM generation, and guest root, volume, and host-bind markers. The running VM keeps its boot ID. The stopped VM remains stopped until ordinary `up`, then resumes with preserved markers. The separate Windows provider-update job exercises Go archive and provider-update tests; it is not evidence of a MicroSandbox VM running on Windows. Native provider/VM evidence is Linux/amd64 with KVM only. ## Proposed fresh-install change [#proposed-fresh-install-change] Change only the Desktop Microsandbox preset to install the pinned source [`github.com/devsy-org/devsy-provider-microsandbox@v0.1.8`](https://github.com/devsy-org/devsy-provider-microsandbox/releases/tag/v0.1.8), while retaining the provider name `microsandbox`. The existing Desktop setup path uses `provider add ... --use=false`; this preserves its current default-provider behavior. The explicit embedded source alias `microsandbox` remains available, as does the embedded manifest for parity tests and compatibility with existing internal provider records. This proposal is fresh-install only. Existing records change source only through the supported `provider set-source` command. Changing the embedded manifest does not migrate those records. If initialization fails after a source change is committed, the new source remains selected and the provider records `InitError`; the command does not promise rollback. The running and stopped migration cases provide evidence for the tested successful transition, not for rollback after arbitrary failures. ## Limits and decision conditions [#limits-and-decision-conditions] The performance artifact from run 38628263920 records one Linux/amd64 hosted run with SDK v1.6.0 and provider v0.1.8. It observed fresh and reused `up` wall times of 13.504s and 6.335s. Four direct/supervised/supervised/direct probe runs collected one first sample and 100 warm `Info` and `Find` samples each; all 808 launches were reaped. The artifact marks `cold_cache_measured=false`. These are observations, not thresholds, cold-start results, or evidence that supervision improves performance. No process pool is proposed. Other provider-supported host profiles remain untested here. Upstream requires Apple Silicon on macOS, KVM on Linux, and WHP on Windows; this campaign does not claim runtime parity on those other hosts. The registry tests do not cover every proxy, Docker daemon/context/socket, credential-store, or temp-directory configuration. Neither the built-in driver nor the external provider guarantees removal of every effect after a failed or interrupted create; use a fresh `Find` and explicit cleanup where recovery is needed. The exact native VM artifacts surviving a particular interruption remain unproven. The source-migration review and merge gates are complete. A fresh-install preset change remains a separate reviewed implementation and must preserve these platform, performance, environment, and recovery limits. Keep the embedded provider and existing records intact until the explicit migration path and compatibility window are accepted. # Provider Options (/docs/developing-providers/options) Options are settings the user can configure for a provider, such as an account, region, VM size or image. Define them in [`provider.yaml`](./quickstart.mdx#sections-of-provideryaml): ```yaml options: MY_OPTION: description: "this is my option" default: "default_value" required: false password: true ``` Devsy validates options when the provider is added and passes them to every command as environment variables. They can also be used in the `agent` section and in `agent.exec`. The provider is responsible for reading and validating its own variables. Put that check in the `init` command, which Devsy runs when options change. ## Attributes [#attributes] * `displayName`: name shown by tools such as the Desktop app, instead of the option name. * `description`: shown by `devsy provider get` and in the Desktop app. * `default`: default value, as a string. It can reference other options, for example `${OTHER}-suffix`. Devsy resolves them in the right order. * `required`: the value must be non-empty. Devsy prompts for it in the CLI, and the Desktop app asks for it. * `password`: treat the value as a secret. It is hidden in `devsy provider get` and entered in a password field in the Desktop app. * `type`: `string` (default), `multiline`, `duration`, `number` or `boolean`. Devsy validates the value against it. * `enum`: the only allowed values. Devsy rejects anything else. * `suggestions`: values offered as autocomplete in the Desktop app. Not enforced, and not shown in the CLI. * `validationPattern`: regex the value must match. * `validationMessage`: error shown when the pattern does not match. A generic message is used if empty. * `command`: command that fills the value. It can reference other options. On Windows it runs in an emulated shell. * `subOptionsCommand`: command that prints more `options`, in the same YAML shape, based on other option values. * `local`: fill the option separately for each machine or workspace. * `global`: reuse the option for every machine or workspace. Cannot be combined with `cache` or `mutable`. * `cache`: re-run `command` after this duration, for example `5m`. Use it for values that expire, such as tokens. * `hidden`: hide the option in the Desktop app and `devsy provider get`. Use it for internal values. * `mutable`: allow changing the value on an existing machine or workspace. Cannot be combined with `global`. ## Examples [#examples] Pass a variable from the user's machine: ```yaml AWS_ACCESS_KEY_ID: description: The AWS access key ID command: printf "%s" "${AWS_ACCESS_KEY_ID:-}" ``` Run a helper binary and cache the result: ```yaml AWS_TOKEN: local: true hidden: true cache: 5m description: The AWS auth token to use command: ${AWS_PROVIDER} token ``` ## Built-in options [#built-in-options] These can be used in `default` and `command`. Some exist only for `local` options, because the machine or workspace does not exist yet when others are resolved. * **DEVSY**: absolute path to the Devsy binary. Also available on the agent side. * **DEVSY\_OS**: `linux`, `darwin` or `windows`. * **DEVSY\_ARCH**: `amd64` or `arm64`. * **MACHINE\_ID**, **MACHINE\_FOLDER**, **MACHINE\_CONTEXT**, **MACHINE\_PROVIDER**: the machine's ID, local folder, Devsy context and provider. Machine providers only. * **WORKSPACE\_ID**, **WORKSPACE\_FOLDER**, **WORKSPACE\_CONTEXT**, **WORKSPACE\_PROVIDER**: the same for a workspace. Non-machine providers only. * **PROVIDER\_ID**, **PROVIDER\_CONTEXT**, **PROVIDER\_FOLDER**: the provider's name, context and config folder. The folder can hold provider-wide data such as session tokens. ## Option groups [#option-groups] Option groups organize options in the Desktop app. They have no effect in the CLI. ```yaml optionGroups: - name: "AWS options" options: - AWS_ACCESS_KEY_ID - AWS_INSTANCE_TYPE defaultVisible: true - name: "Agent options" options: - AGENT_PATH - INACTIVITY_TIMEOUT ``` Groups are collapsed unless `defaultVisible` is `true`. # Quickstart Guide (/docs/developing-providers/quickstart) A provider is a small program described by a `provider.yaml`. Devsy reads the manifest and calls the commands it defines to create and reach an environment. Read [What are providers](../managing-providers/what-are-providers.mdx) and [Add a Devsy Provider](../managing-providers/manage-providers.mdx) first. ## A minimal provider [#a-minimal-provider] This provider runs workspaces on the local machine: ```yaml name: my-first-provider # required version: v0.0.1 # required agent: path: ${DEVSY} # path to the current Devsy binary exec: # How to run a command in the environment. Here: a local shell. command: |- sh -c "${COMMAND}" ``` Save it as `provider.yaml`, add it, and start a workspace: ``` devsy provider add ./provider.yaml devsy workspace up github.com/microsoft/vscode-remote-try-go ``` ## Sections of provider.yaml [#sections-of-provideryaml] * `name`, `version`: required. `description` and `icon` are optional and shown in the Desktop app. * [`exec`](#exec): the commands Devsy runs to reach and manage the environment. * [`options`](./options.mdx): settings the user can configure. Devsy passes them to commands as environment variables. * [`binaries`](./binaries.mdx): helper binaries Devsy downloads for the provider. * [`agent`](./agent.mdx): how the agent runs in the environment, including the driver, inactivity timeout and credentials. ## exec [#exec] Each entry is a POSIX shell script. Only `command` is required. * **command**: runs a command in the environment. Devsy uses it to inject itself and sends all traffic through its standard input and output. `${COMMAND}` is a placeholder Devsy fills in. * **init**: checks that the options are valid and the provider is ready. Devsy runs it when options change. * **create**, **delete**: create and delete a machine. Defining `create` makes the provider a machine provider, and `delete` is then expected too. * **start**, **stop**: start and stop a machine. Machine providers only. * **status**: prints one of `Running`, `Busy` (wait and check again), `Stopped` or `NotFound`. * **describe**: prints a text description of the machine. A provider without `create` is a non-machine provider and uses only `command` and `init`. On Windows, Devsy runs these scripts in an [emulated shell](https://github.com/mvdan/sh), where tools such as `grep` and `sed` are not available. Move that logic into a helper binary. Example from the [SSH provider](https://github.com/devsy-org/devsy-provider-ssh): ```yaml exec: init: |- OUTPUT=$(ssh -oStrictHostKeyChecking=no -p ${PORT} ${EXTRA_FLAGS} "${HOST}" 'sh -c "echo DevsyTest"') if [ "$OUTPUT" != "DevsyTest" ]; then >&2 echo "Unexpected ssh output. Check the SSH host and options." exit 1 fi command: |- ssh -oStrictHostKeyChecking=no -p ${PORT} ${EXTRA_FLAGS} "${HOST}" "${COMMAND}" ``` # Runtime Protocol v1 (/docs/developing-providers/runtime-protocol) Providers select this protocol with `agent.driver: external`. See the [external driver](./driver.mdx#external-driver). The canonical [protobuf schema](https://github.com/devsy-org/devsy-runtime-sdk/blob/main/proto/devsy/runtime/v1/runtime.proto) is maintained in the [Devsy Runtime SDK](https://github.com/devsy-org/devsy-runtime-sdk). The module path is `github.com/devsy-org/devsy-runtime-sdk`; the logical plugin name is `devsy-runtime`. HashiCorp application protocol 1 and Info API major 1/minor 3 are separate version checks. Same-major newer minor versions are accepted. Unknown mount/recreate enum values are rejected because they control host behavior. ## Runtime boundary [#runtime-boundary] Info, Preflight, ProvisioningPreflight, ReusePreflight, Find, TargetArchitecture, RunImage, Start, Stop, Delete, Exec, and Logs are the v1 RPC surface. Runtime state persists in the backend across plugin processes. Plugins do not own image build/tag/push, registry credentials, Compose, IDE configuration, snapshots, provider machine lifecycle, or updates. RunImage receives resolved intent. Its empty response acknowledges completion; Find queries state. `image_built_locally` is an image-origin hint, not permission to build. Optional privileged/init flags distinguish absent from explicit false. Environment and mounts may contain secrets and must not appear in diagnostic logs. `remote_user` carries the developer identity used for workspace ownership, separately from the container process `user`. The host forwards both values without substituting one for the other. Runtimes use `remote_user` when set, otherwise `user`, otherwise `root` for workspace ownership. `dockerless` indicates that the host will build the developer filesystem after the image starts, so that identity may not yet exist in the image. A runtime that resolves mount ownership from image contents must validate this case before changing workspace resources. These fields describe provisioning intent and do not authorize replacement of an existing workspace. Runtime name, driver name/version, and capabilities are required in Info. Runtime version may be empty when a backend cannot report it without expensive setup. An empty mount list means no supported mount types. ProvisioningPreflight may be a no-op when its capability is false. Logs may return Unimplemented when its capability is false. Reprovision means RunImage can update an existing workspace with complete resolved intent; it does not imply that an empty request is safe. The Devsy host does not enable in-place reprovisioning; workspace changes follow the negotiated recreation policy. TargetArchitecture returns canonical `amd64` or `arm64`. A runtime may require an existing workspace to answer; hosts must not require pre-start architecture discovery from such runtimes. ## Lifecycle [#lifecycle] Find returns `found=false` for ordinary absence, without a NotFound RPC error. A found response includes container details with normalized state `running` or `stopped`. Transport, permission, and backend errors remain errors. Start on an already running workspace succeeds; missing returns NotFound. Stop on an already stopped workspace succeeds; missing may return NotFound. Delete normalizes missing state to success for cleanup. Provisioning compatibility checks must precede destructive teardown. API 1.2 adds optional `capabilities.reuse_preflight`. Before reusing a workspace, the host calls ReusePreflight with its workspace ID and current resolved developer identity. The runtime validates its own creation-time contract, such as mount policy or ownership, without starting, stopping, deleting, or modifying the workspace. An incompatible contract returns structured FailedPrecondition with explicit `--recreate` guidance. The host propagates that error without scheduling replacement, preserving the existing VM. Missing workspaces return NotFound; backend, permission, and context failures remain errors. When the capability is absent, hosts skip the RPC and retain their existing identity-resolution behavior. Explicit recreation follows separate provisioning checks and the negotiated recreation policy. API 1.3 adds negotiated recreation during RunImage. A host opts in with `InfoRequest.supports_recreate_on_run`; a supporting runtime responds with `RECREATE_MODE_ON_RUN`. Devsy then leaves the existing VM in place and forwards the current operation's explicit recreation permission as `RunImageRequest.allow_recreate`. Negotiation alone grants no replacement permission, and a later request with false remains false. Existing explicit reset and rebuild operations also grant recreation permission. Automatic identity or secret migrations must request consent instead of silently enabling replacement. Older hosts and providers retain their negotiated DELETE or STOP behavior. ON\_RUN does not enable in-place reprovisioning: it delegates replacement to the runtime so final effective configuration can be validated before teardown. For MicroSandbox, explicit replacement requires v0.7.7 or newer even with workspace permission policy `off`. The built-in and supporting external driver pass `--replace` to that operation instead of stopping and removing the old VM first. The runtime validates the effective configuration, including managed overrides, before removing the target. This preserves the old VM when configuration is rejected; it does not roll back failures after replacement begins. Image preparation, cache imports or new volumes can precede final validation. Root preparation, launch failures, cancellation or crashes after teardown can still lose the old VM. ## Exec and Logs [#exec-and-logs] The first client frame is exactly one ExecStart containing argv. Later frames contain stdin bytes or exactly one CloseStdin; the client then closes its send side. Data after CloseStdin, repeated Start, unset payloads and empty stdin data frames, and unexpected EOF before CloseStdin are InvalidArgument. v1 Devsy callers use `tty=false`; runtimes reject unsupported TTY requests. Each side uses one send pump and one receive loop. Data chunks should be at most 32 KiB. Empty input is represented by CloseStdin with no preceding data frames. Stdout/stderr are separate byte streams, with no text decoding or PTY. The receiver drains output before exactly one terminal ExecExit. Command success requires `exit_code == 0` and an empty `signal`. A nonempty `signal` means command failure regardless of `exit_code`, including its protobuf default of zero. Ordinary nonzero or signal-terminated command exit is carried in ExecExit and the RPC succeeds. Setup/transport/backend failures are RPC errors; stream EOF without an exit is not command success. Context cancellation/deadlines terminate the operation and release its resources. Plugins that launch children must ensure child cleanup; server bootstrap and host-supervised process ownership are separate responsibilities. Logs uses merged binary OutputChunk frames. Output buffering must remain bounded. Do not call Send concurrently from stdout and stderr copiers. ## Errors and trust [#errors-and-trust] Use canonical gRPC status and attach RuntimeError details for stable categories, actionable messages, optional backend diagnostics, retryability, and structured context. Raw backend diagnostics must be redacted before display. Unknown detail fields remain forward-compatible; callers must not parse messages to classify errors. The plugin binary is trusted provider code. Devsy resolves the selected external runtime from its `Agent.Binaries` declaration and requires a SHA-256 checksum before launch, including cached and local absolute paths. The resolved executable must be a regular file; Unix hosts also check effective execute access. The runtime supervisor is the running Devsy executable's internal `runtime-supervisor` entry point, not another provider-distributed binary. The SDK transport runner does not download executables or establish checksum trust. A generic provider download or an `Agent.Binaries` path alone does not establish this trust, and the magic cookie is not authentication or sandboxing. ## Plugin process environment [#plugin-process-environment] Devsy inherits the agent host's environment, then appends resolved provider/workspace values. This external-driver path does not supply a separate extra-environment map. This is the environment of the trusted plugin and its runtime CLI children, separate from the guest environment carried in RunImage and ExecStart. The external driver preserves this behavior; it does not introduce an environment allowlist. `PATH` and home directories can select runtime CLIs and configuration. Proxy and CA settings, Docker configuration and credential helpers, and temporary-directory settings can affect the provider's image clients. Preserving those inputs does not prove every proxy topology, credential store or host profile works. The native consumer cases below cover their controlled registry/TLS/proxy/credential paths. Never log the full environment or secret-bearing runtime arguments; redact backend diagnostics before display. The host owns each supervised plugin session. The SDK server bootstrap does not itself own backend children; runtimes remain responsible for independently managed services and children outside the supervisor's process ownership. Cancellation does not promise rollback of backend effects that already committed. See the [rollout decision](./external-runtime-rollout.mdx) for the tested migration path, platform applicability and compatibility boundary. For SDK usage and development commands, see the [SDK README](https://github.com/devsy-org/devsy-runtime-sdk#readme). ## MicroSandbox parity gate [#microsandbox-parity-gate] MicroSandbox integration suites share a provider matrix and test-session setup. The dedicated `integration-tests-microsandbox` job defines the suite catalogue and common runtime installation once, while keeping each suite on an independent Linux runner. A CI-only Go utility built from the tested checkout installs and checks the pinned MicroSandbox bundle, executes one labelled E2E suite and retains its log and Ginkgo JSON report; it is not a Devsy release artifact. The utility's `verify-report` command checks the selected label in that report. Each native run must report the exact expected count, with every selected case passed once and no selected skips or retries. The default `provider-mode: parity` requires equal built-in and external provider counts. An explicitly external-only catalogue row uses `provider-mode: external` and requires every selected case to carry only the external provider label. Dry-run and programmatic-focus reports cannot satisfy this gate. Prior reports prove only the labels and cases they contain; the earlier 74-case parity baseline does not prove the added three-case consumer contract or the expanded 77-case catalogue. See [the test architecture guide](https://github.com/devsy-org/devsy/blob/main/e2e/tests/up/MICROSANDBOX.md) for fixture ownership and extension rules. The `up-provider-microsandbox` E2E label runs the same lifecycle and ownership scenario against the built-in provider and the external v0.1.8 release, each with isolated Devsy configuration. CI pins MicroSandbox v0.7.7 by checksum and requires access to KVM; unavailable virtualization fails this job instead of producing a passing skipped test. Each provider scenario has a ten-minute deadline and the CI job has a 25-minute deadline. The shared scenario exercises agent delivery, SSH, a 1 MiB binary stdin/stdout round trip with separate stderr and a nonzero guest exit, root workload versus developer identity, bind-mount ownership and mode mirroring, stop/start, recreation, rejection of an identity change without recreation while preserving VM-local data, and deletion of the VM. Run it on Linux with KVM or Apple silicon after installing MicroSandbox v0.7.7 or newer: ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox ``` The separate `up-provider-microsandbox-images` label runs three image cases against each provider: registry fallback with an image absent from Docker, loading a Docker-only image, and a Dockerfile build with a local Dev Container Feature. Guest checks verify the image's filesystem and environment markers and the Feature's installed file and environment. The registry case uses an isolated loopback registry and verifies manifest reads; no external test registry credentials are required. CI gives this matrix a 20-minute test deadline and a 25-minute job deadline. The image suite also requires the Docker CLI and a running Docker daemon, in addition to the MicroSandbox and virtualization prerequisites above. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-images ``` The `up-provider-microsandbox-mounts` label runs five mount scenarios against each provider. It checks bidirectional bind access, read-only write rejection, named-volume persistence through stop/start and recreation, tmpfs reset, and strict/relaxed/off stat virtualization with private host permissions. Permission checks use host-created files after workspace setup so recursive chown cannot mask the guest ownership fallback. Host mode changes under `off` must become visible after MicroSandbox's five-second guest attribute cache expires. Each scenario has a ten-minute deadline; CI gives the matrix a 20-minute test deadline and a 25-minute job deadline. Test-owned named volumes are removed after workspace cleanup. This suite needs the same MicroSandbox and virtualization prerequisites as the lifecycle suite: ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-mounts ``` The `up-provider-microsandbox-resources` label runs six scenarios against each provider: default CPU/memory sizing, explicit provider options overriding `hostRequirements`, zero provider values falling back to `hostRequirements`, effective resources below larger boot ceilings, and rejection of CPU or memory ceilings below the initial allocation. It checks the running VM's active configuration and the guest's online CPU count and `MemTotal`, allowing 128 MiB for kernel reservations rather than treating persisted configuration as proof of guest capacity. The ceiling scenario uses `msb modify` on the test-owned VM to grow CPU and memory to their boot ceilings, then checks guest convergence within 60 seconds. Requests beyond either ceiling must fail with a restart-required plan while preserving active resources, VM creation time, guest boot ID, and a filesystem marker. This exercises the capacity provisioned by each driver; Devsy does not expose a live-resize API. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. The suite has the same runtime and virtualization prerequisites as lifecycle testing: ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-resources ``` The `up-provider-microsandbox-storage` label runs three persistent-root scenarios against each provider: the runtime's default capacity, an explicit provider storage option overriding `hostRequirements.storage`, and a zero provider option falling back to a fractional-GiB host requirement rounded up to whole GiB. It checks active root-disk configuration and guest filesystem capacity, allowing for filesystem metadata instead of comparing free space with the raw disk size. Each scenario verifies that a VM-local file survives stop/start and disappears after recreation, while a host workspace file persists through both operations. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. This suite uses the same runtime and virtualization prerequisites as the lifecycle suite: ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-storage ``` The `up-provider-microsandbox-ephemeral` label runs three scenarios against each provider. An unsized ephemeral root delegates to the runtime default (half the initial memory on the pinned runtime); an explicit storage size overrides that default. The suite checks the active tmpfs backing and guest capacity, root-file loss after stop/start, retained sandbox identity, and persistent host workspace content. The default-size scenario also checks named-volume persistence and recreation. A fresh creation with a root larger than initial memory must fail with the runtime's size diagnostic and leave no test-labeled VM or source-file changes. Ephemeral roots use a RAM-backed upper layer beneath the guest's overlay filesystem. They do not enable the runtime's separate sandbox-record removal flag. These tests do not establish preservation of an existing VM when an invalid recreation is requested; that requires separate validation before teardown. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-ephemeral ``` The `up-provider-microsandbox-egress` label checks default public TCP access and explicit egress denial against each provider, at creation, after stop/start, and after recreation. A test-owned Linux interface exposes a controlled destination that the pinned runtime classifies as public. Positive host and default-policy VM probes bracket each denial check; the blocked VM must retain SSH access, report connection refusal or reset, and send no request to the destination. Active network configuration and VM identity checks accompany the probes. The fixture requires Linux, root access with `CAP_NET_ADMIN`, and `iproute2`; CI fails when these prerequisites are unavailable. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-egress ``` The `up-provider-microsandbox-prebuilds` label publishes a Dockerfile with a local Feature to an isolated registry, then checks matching reuse and changed-Dockerfile invalidation against each provider. Matching reuse must read the published hash without invoking a Docker build or entering build-strategy selection. Changed input must miss a different hash, build the new image, and retain the old published manifest. Guest file and environment markers verify the selected image and Feature contents. The fixture removes only its own image tags and requires a Linux host with a local Docker daemon, buildx, and loopback HTTP registry access. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-prebuilds ``` The host starts its generated bootstrap SSH helper with literal arguments when the runtime supports argv execution, so setup can begin before the Dockerless filesystem provides `/bin/sh`. Runtimes without that capability retain command-string execution. The `up-provider-microsandbox-dockerless` label runs four scenarios against each provider with host Docker unavailable. It explicitly selects the existing Dockerless path; it does not test automatic fallback. Both the Docker CLI and Go client are directed to test-owned failures, while a real guest build must install a Dockerfile and local Feature. Image PATH, credentials-directory configuration, Feature environment and a bare-name executable are checked through setup hooks and SSH. The root scenario repeats up, stop/start and recreation, checking build reuse, VM identity, root-file persistence and preserved host data. A fixed non-root developer must succeed with `off` stat virtualization and private host permissions; fresh creation under `strict` or `relaxed` must fail before VM or build-volume creation, preserving host data and permissions. This suite requires Linux with KVM and public access to the base and Dockerless builder images. It does not require a usable host Docker daemon. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. Cleanup removes only the fixture's workspace, VM, build volume and provider. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-dockerless ``` The `up-provider-microsandbox-recreate` label tests rejected explicit replacement for running and stopped VMs against both providers. Each case checks the unchanged VM generation and active configuration before resuming a stopped original, then checks persistent root, bind and named-volume data together with the workspace and SSH records. A subsequent valid replacement must change the VM generation, reset the root marker and preserve the external data. These request-invalid cases do not establish preservation for managed-policy conflicts or failures after replacement begins. Each scenario has a ten-minute deadline, with a 20-minute CI test deadline and a 25-minute job deadline. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-recreate ``` The Linux-only `up-provider-microsandbox-managed-recreate` label checks a valid 2048 MiB replacement with an explicit 2048 MiB boot ceiling. A managed policy raises initial memory to 3072 MiB, making the effective configuration invalid. The workspace retains its original provider options throughout; changing global provider defaults would not override those saved workspace values. Running and stopped originals must retain their VM generation, root and persistent mount data, workspace record and SSH files for both providers. Resetting the policy must allow the same replacement request. The CI wrapper overlays `/etc` in a private mount namespace before creating any policy file; its scratch remains in the disposable runner for job teardown. These checks require that isolated wrapper and do not establish rollback after replacement has begun. Each scenario and the suite retain the request-invalid suite's time bounds. On a disposable Linux host with the same prerequisites, prepare the E2E binaries and run the managed suite through its isolated wrapper. The scratch directory is retained for host teardown; running the label without the wrapper does not exercise these cases. ```sh task cli:test:e2e:build (cd e2e && go test -c -o e2e.test .) (cd e2e && sudo env PATH="$PATH" RUNNER_TEMP="$(mktemp -d)" \ DEVSY_REQUIRE_MICROSANDBOX=true DEVSY_REQUIRE_MSB_MANAGED_POLICY_FIXTURE=true \ ../hack/run-managed-policy-e2e.sh ./e2e.test -test.v -ginkgo.v \ -test.timeout 1200s -ginkgo.fail-on-empty -ginkgo.flake-attempts=1 \ -ginkgo.label-filter=up-provider-microsandbox-managed-recreate) ``` The `up-provider-microsandbox-logs-cancel` label checks completed stdout/stderr workload logs while running and stopped, direct runtime Exec cancellation of a ready parent and child, and cancellation with blocked owned stdin against each provider. A separate SSH observer verifies process identity using PID, kernel start time, and a unique command marker. Caller return and process death must both occur within ten seconds of cancellation; the VM's creation time, boot ID, and source files must remain intact. Logs do not start a stopped VM. These checks do not establish log ordering, binary log fidelity, TTY cancellation, or cancellation of arbitrary nonclosable input. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-logs-cancel ``` The `up-provider-microsandbox-compatibility` label checks preservation of a running workspace when a test-owned shim returns old, malformed, or failing version responses. It then restores the supported CLI and requires explicit recreation to succeed. The shim injects version-command faults while retaining a real supported VM; it does not emulate an actual historical MicroSandbox release. External-provider discovery may report a version-command failure earlier than the built-in runtime. These cases run on native Linux and macOS, not Windows. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-compatibility ``` For built-in Exec, cancellation closes non-file `io.ReadCloser` input whose `Close` unblocks `Read`; ordinary completion and caller-owned file input retain their ownership. Agent binary delivery owns its acquired source and closes it when the individual transfer attempt is canceled, before retrying with a fresh source. Cleanup joins cancellation callbacks before returning. The Linux-only `up-provider-microsandbox-registry-consumer` label selects exactly three cases from the published external provider's Runtime Protocol `RunImage` path. Its positive case uses an owned TLS registry, fixture CA, restricted CONNECT proxy and private Docker credential helper, verifies the provider executable digest and exact authenticated manifest/blob response bytes, then checks a guest marker. Independent missing-CA and missing-credential cases require concrete refusal and no created VM. Each case owns fresh image and runtime state. This suite does not prove ordinary `up` image metadata resolution, remote agent binary preparation, or general egress denial. Reports from the earlier 74-case parity baseline remain useful for those existing cases, but do not count as runs of this external-only row. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-registry-consumer ``` The Linux-only `up-provider-microsandbox-remote-agent-binaries` label uses the published external provider with `agent.local: false` and a test-owned remote agent container. It separately counts the Devsy agent executable transfer and the runtime binary download through `Agent.Binaries`. After the first successful `up`, it removes only the agent's provider-download cache, makes the runtime source return 404, and requires a later `up` to reuse the retained workspace binary with unchanged checksum and inode. A guest command verifies the workspace remains usable. The Devsy agent endpoint remains available; this does not promise offline reuse of that separate executable or ordinary `up` image metadata resolution. This added one-case external-only row brings the configured catalogue to 78 cases; earlier 77-case reports do not prove it. Acceptance requires current-head native Linux/KVM reports, not documentation or registration alone. ```sh DEVSY_REQUIRE_MICROSANDBOX=true task cli:test:e2e:suite -- up-provider-microsandbox-remote-agent-binaries ``` For the existing two-case lifecycle label, the external-provider case can capture ordinary fresh and reused `up` wall time and filtered runtime startup events when CI supplies all five explicit inputs: `DEVSY_TEST_RUNTIME_SPAWN_PROBE`, `DEVSY_TEST_RUNTIME_SUPERVISOR`, `DEVSY_TEST_RUNTIME_PERFORMANCE_DIR`, `DEVSY_TEST_RUNTIME_REVISION`, and `DEVSY_TEST_RUNTIME_SDK_VERSION`. The built-in case keeps its existing path. After both ordinary commands pass, the helper stages a checksum-verified copy of the published runtime and runs direct/supervised/supervised/direct Info/Find comparisons, each with a first launch and 100 warm samples. The JSON artifact records tool/runtime provenance and reaped processes. Startup events measure launch, handshake and dispense before RPC; they exclude binary resolution, RPC and reaping. These measurements use warm host caches, add no latency threshold or VM case, and do not claim cold-cache performance or total plugin overhead. These scenarios do not establish complete parity. Compatibility shims verify preservation under injected version faults but do not establish behavior against an actual historical MicroSandbox release. Green lifecycle, image, mount, resource, persistent-storage, ephemeral-root, TCP-egress, prebuild, Dockerless, request-invalid and managed-policy recreation, logs, and cancellation checks alone do not authorize that cutover. Dockerless refusal checks cover fresh creation, not preservation after an invalid recreation request. Egress coverage does not establish UDP, DNS, TLS, ingress, or live-policy modification behavior. Prebuild invalidation coverage changes the Dockerfile; it does not independently establish hashing of local Feature script contents. # Install Devsy (/docs/getting-started/install) Devsy comes as a desktop app and a CLI. Both do the same things. Use the desktop app if you want a UI and the CLI for scripting. Install one, then follow the [Quick Start](./quickstart.mdx). ## Install Devsy Desktop [#install-devsy-desktop] Download the build for your platform: * [macOS Silicon/ARM](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_mac_arm64.dmg) * [macOS Intel/AMD](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_mac_x64.dmg) * [Windows](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_win_x64.exe) * [Linux AppImage](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_linux_x86_64.AppImage) * [Linux DEB (Debian/Ubuntu)](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_linux_amd64.deb) * [Linux RPM (Fedora/RHEL/openSUSE)](https://github.com/devsy-org/devsy/releases/latest/download/Devsy_linux_x86_64.rpm) * [Linux Flatpak](https://github.com/devsy-org/devsy/releases/latest/download/Devsy.flatpak) For earlier versions, see the [GitHub releases page](https://github.com/devsy-org/devsy/releases). The DEB and RPM packages declare their dependencies, and the AppImage and Flatpak bundle theirs. If the AppImage does not start, install FUSE 2 (`libfuse2` on Debian and Ubuntu) and try again. If the app does not open, install [WebView 2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/?form=MA13LH). Recent versions of Windows include it. ## Install Devsy CLI [#install-devsy-cli] On macOS or Linux, the quickest option is the install script: ```bash curl -L https://devsy.sh/install.sh | sh ``` The script downloads the latest CLI for your OS and architecture from [GitHub releases](https://github.com/devsy-org/devsy/releases), checks its SHA-256 checksum when a matching checksum entry and `sha256sum` or `shasum` are available (otherwise it skips verification), and installs `devsy` into `/usr/local/bin`. If it cannot write there and `sudo` is not available, it installs into `~/.local/bin`. Run it again to update. On Windows, use the PowerShell command in the Windows tab below. Two environment variables customize the script: * `DEVSY_VERSION` installs a specific release instead of the latest: `curl -L https://devsy.sh/install.sh | DEVSY_VERSION=vX.Y.Z sh` * `DEVSY_INSTALL_DIR` installs somewhere other than the default: `curl -L https://devsy.sh/install.sh | DEVSY_INSTALL_DIR="$HOME/bin" sh` On macOS or Linux, you can also install with [Homebrew](https://brew.sh): ```bash brew install devsy-org/homebrew-tap/devsy ``` Upgrade later with `brew upgrade devsy`. To download a binary yourself, use the command for your platform below. ```bash curl -L -o devsy "https://github.com/devsy-org/devsy/releases/latest/download/devsy-darwin-arm64" && sudo install -c -m 0755 devsy /usr/local/bin && rm -f devsy ``` ```bash curl -L -o devsy "https://github.com/devsy-org/devsy/releases/latest/download/devsy-darwin-amd64" && sudo install -c -m 0755 devsy /usr/local/bin && rm -f devsy ``` ```text md -Force "$Env:APPDATA\devsy"; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'; Invoke-WebRequest -URI "https://github.com/devsy-org/devsy/releases/latest/download/devsy-windows-amd64.exe" -OutFile $Env:APPDATA\devsy\devsy.exe; $env:Path += ";" + $Env:APPDATA + "\devsy"; [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::User); ``` ```bash curl -L -o devsy "https://github.com/devsy-org/devsy/releases/latest/download/devsy-linux-amd64" && sudo install -c -m 0755 devsy /usr/local/bin && rm -f devsy ``` ```bash curl -L -o devsy "https://github.com/devsy-org/devsy/releases/latest/download/devsy-linux-arm64" && sudo install -c -m 0755 devsy /usr/local/bin && rm -f devsy ``` Check that the CLI is on your `PATH`: ```bash devsy --version ``` # Quick Start (/docs/getting-started/quickstart) Go from a fresh install to a running workspace: add a provider, start a workspace, open it in your editor. First, [install Devsy](./install.mdx). Run `devsy ide list` to see every supported editor. VS Code Browser (`openvscode`) and `none` (SSH only) need nothing installed locally. For any other editor, install it first. JetBrains IDEs also need [JetBrains Gateway](https://www.jetbrains.com/remote-development/gateway/). ## Devsy CLI [#devsy-cli] ### 1. Add a provider [#1-add-a-provider] A provider decides where workspaces run. Add local Docker: ``` devsy provider add docker ``` For other backends, see [Add a Devsy Provider](../managing-providers/manage-providers.mdx). ### 2. Start a workspace [#2-start-a-workspace] Point Devsy at a repository and choose an editor with `--ide`: ``` devsy workspace up github.com/microsoft/vscode-remote-try-node --ide vscode ``` Devsy builds the dev container and opens the editor connected to it. With `--ide none`, connect over SSH: ``` ssh WORKSPACE_NAME.devsy ``` ## Devsy Desktop [#devsy-desktop] ### 1. Add a provider [#1-add-a-provider-1] ### 2. Start a workspace [#2-start-a-workspace-1] Go to **Workspaces** > **+ Create** and follow the wizard: provider, source (a repository URL or a template), editor, review, launch. When the workspace is ready, the editor opens connected to it. For a JetBrains IDE, Gateway opens with a prefilled form. Select **Check Connection and Continue** to start the IDE inside the workspace. If you chose **None**, connect over SSH as shown above. To use environment variables in `devcontainer.json`, see [Environment variables in devcontainer.json](../developing-in-workspaces/devcontainer-json.mdx#environment-variables-in-devcontainerjson). # Update Devsy (/docs/getting-started/update) ## Devsy Desktop [#devsy-desktop] Devsy Desktop checks GitHub for new releases while it runs. When one is available it downloads in the background and asks you to restart. If you installed from a `.deb` or `.rpm` package, [download the latest package](https://github.com/devsy-org/devsy/releases/latest/) and install it again. ## Devsy CLI [#devsy-cli] If you installed with Homebrew, upgrade with: ```bash brew upgrade devsy ``` Otherwise, run the install script again (`curl -L https://devsy.sh/install.sh | sh`) or repeat the manual command from [Install Devsy CLI](./install.mdx#install-devsy-cli). # How Devsy Deploys Workspaces (/docs/how-it-works/deploying-workspaces) `devsy workspace up` builds the devcontainer if needed and uses the provider to start it.
Devsy Up Sequence
Devsy up - Sequence Diagram
1. If the provider uses a machine, Devsy creates or starts it. 2. Devsy pulls the source and the `devcontainer.json` from Git or a local folder. 3. The agent builds the workspace image (see [Building](#building)). 4. The agent starts the devcontainer through the driver, for example the Docker daemon or the Kubernetes API. 5. Devsy starts a daemon that stops the machine or container when it is idle, sets up credentials, and opens your IDE. With `devsy workspace up --from-snapshot `, Devsy restores a [snapshot](../developing-in-workspaces/workspace-snapshots.mdx) and skips the build. ## Machines [#machines] Machine providers, such as AWS, GCP, and DigitalOcean, create a VM to host the container. Devsy uses the CLI tool and credentials on your computer, such as `aws` or `az`, to create it, then connects through the cloud's own tunnel: * AWS: [Instance Connect](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html) * Google Cloud: [Cloud IAP](https://cloud.google.com/security/products/iap) * Azure: [Azure Bastion](https://learn.microsoft.com/en-us/azure/bastion/bastion-overview) You can use [SSH tunneling](https://www.ssh.com/academy/ssh/tunneling-example) instead, if your setup supports it. The agent runs an SSH server over the tunnel, so the client can forward ports and connect your IDE.
Devsy Architecture
Devsy - Component Diagram
## Kubernetes [#kubernetes] Kubernetes works the same way, but the tunnel is the Kubernetes API (pod exec and attach), so nothing extra runs on the node.
Devsy Architecture
Devsy - Container Diagram Kubernetes
## Building [#building] Devsy reads `devcontainer.json`, adds any features as build stages on the base Dockerfile, and builds an OCI image. Features mean a build is often needed even when the file only names an image. Where there is a local container daemon, Devsy builds with `docker buildx` or its built-in BuildKit client. Where there is none, as with Kubernetes, it uses the dockerless builder, which builds in the workspace pod without a daemon. You can push the image to a registry to cache it. See [Reduce build times with a cache](../tutorials/reduce-build-times-with-cache.mdx).
Devsy Architecture
Devsy - Container Diagram
Devsy Architecture 2
Devsy - Component Diagram Kubernetes Build Process
# How it works (/docs/how-it-works/overview) Devsy has a client and an agent. The client is the CLI or desktop app on your machine. When you create a workspace, the client installs its own agent on the host and inside the container, so you only ever install the client. The agent forwards ports, forwards credentials, and streams logs, and it runs an SSH server that your IDE connects to.
Devsy Architecture
Devsy - Component Diagram
When you run `devsy workspace up`, Devsy picks a provider, starts the devcontainer, and deploys the agent into it. Machine providers create a VM first if needed. Providers wrap the tools you already use, such as `docker` or `gcloud`, or call an API directly, as the Kubernetes driver does. The client reaches the agent through a **tunnel**, and the transport depends on the provider. AWS uses Instance Connect and Kubernetes uses pod exec. The agent runs an SSH server over the tunnel, and Devsy opens your IDE over that SSH connection. Port forwarding works only while an IDE or SSH session is open. To skip the build, create a workspace from a saved [snapshot](../developing-in-workspaces/workspace-snapshots.mdx) with `devsy workspace up --from-snapshot`. For what happens during `up`, see [How Devsy deploys workspaces](./deploying-workspaces.mdx). # Machine Diagnostics (/docs/managing-machines/machine-diagnostics) Use diagnostics when a machine did not stop when you expected. They report on the inactivity daemon that runs on the machine. ```sh devsy machine diagnostics ``` The output separates the provider's machine state from the daemon's state, and lists the last patrol and each workspace. Add `--result-format json` for scripts. ## When a machine stops [#when-a-machine-stops] The daemon stops a machine only when every workspace on it is idle. A workspace that is busy, invalid, or has auto-stop turned off blocks the stop. An inactivity timeout must be a positive duration such as `30m`. The shutdown candidate shown for a workspace is when it becomes eligible, not a scheduled stop time. New activity moves it, and other workspaces can still block the machine. ## Fields [#fields] | Field | Meaning | | ------------------ | --------------------------------------------------------------------------------------------------- | | Provider state | The last machine state the provider reported. | | Daemon health | Whether the daemon's last operation succeeded. | | Snapshot freshness | Whether the daemon updated its snapshot recently. It does not prove the machine is reachable now. | | Last patrol | When the daemon last checked workspace inactivity. | | Last daemon error | The latest failure in this daemon session. It stays after recovery, so check its timestamp. | | Shutdown candidate | Every workspace check allows a stop. The provider state shows whether the machine actually stopped. | ## Events [#events] ```sh devsy machine logs ``` This shows recent daemon events: starts and stops, workspace discovery, inactivity deadlines, and shutdown results. Add `--follow` to keep polling for new ones. These are diagnostic events only, not shell, Docker, or provider logs. Older events are rotated out, and the output tells you when it detects a gap. `shutdown.succeeded` means the stop command returned successfully, not that the provider confirmed the machine stopped. A missing completion event does not prove failure, because an abrupt shutdown can prevent it from being written. Devsy does not start a stopped machine to collect diagnostics. It only collects while you ask for it, through the CLI or an open machine page in the desktop app, which keeps the last snapshot for a stopped machine. If diagnostics are stale or unavailable, check the provider state and connection first. # Manage Machines (/docs/managing-machines/manage-machines) A machine is a server created by a provider. See [What are machines](./what-are-machines.mdx). Run `devsy machine --help` for every command. ## Create and list [#create-and-list] ```sh devsy machine create --provider devsy machine list ``` ## Check and inspect [#check-and-inspect] ```sh devsy machine status devsy machine describe devsy machine inspect ``` `describe` prints the provider's own summary. `inspect` prints the machine configuration as JSON, with sensitive options redacted and hidden options left out. ## Connect [#connect] ```sh devsy machine ssh ``` ## Stop, start, delete [#stop-start-delete] ```sh devsy machine stop devsy machine start devsy machine delete ``` Deleting a machine cannot be undone. All workspace containers and data on it are lost. # What are Machines? (/docs/managing-machines/what-are-machines) A machine is the VM a [machine provider](../managing-providers/what-are-providers.mdx) creates to host your workspace. Devsy creates and deletes a dedicated machine with its workspace. A shared machine can host several workspaces and remains until its last workspace is deleted. To work with machines directly, use: ```sh devsy machine ``` # Manage Providers (/docs/managing-providers/manage-providers) Providers you can add: * [Docker (docker)](https://github.com/devsy-org/devsy/tree/main/providers/docker) * [Podman (podman)](https://github.com/devsy-org/devsy/tree/main/providers/podman) * [Kubernetes (kubernetes)](https://github.com/devsy-org/devsy/tree/main/providers/kubernetes) * [Apple (apple)](https://github.com/devsy-org/devsy/tree/main/providers/apple) * [Microsandbox (microsandbox)](https://github.com/devsy-org/devsy/tree/main/providers/microsandbox) * [Lima (lima)](https://github.com/devsy-org/devsy-provider-lima) * [OrbStack (orbstack)](https://github.com/devsy-org/devsy-provider-orbstack) * [SSH (ssh)](https://github.com/devsy-org/devsy-provider-ssh) * [AWS (aws)](https://github.com/devsy-org/devsy-provider-aws) * [Google Cloud (gcloud)](https://github.com/devsy-org/devsy-provider-gcloud) * [Azure (azure)](https://github.com/devsy-org/devsy-provider-azure) * [Digital Ocean (digitalocean)](https://github.com/devsy-org/devsy-provider-digitalocean) List what is available from the CLI: ```sh devsy provider list --available ``` ## Add a provider [#add-a-provider] ### Devsy Desktop [#devsy-desktop] Open **Providers** and click **Add**. The wizard has four steps: 1. **Select.** Pick a preset, or enter a GitHub link, a `provider.yaml` URL, or a local path under **Provider Source**. 2. **Configure.** Fill in the options. The form comes from the provider's schema. 3. **Initialize.** Devsy runs the provider init and streams the logs. 4. **Done.** Review the result. A failure shows the underlying error. Click a provider in the list to edit its settings, switch version, update, re-initialize, rename, set it as default, or delete it. ### Devsy CLI [#devsy-cli] ```sh devsy provider add docker ``` Use `--name` to add the same provider more than once with different options: ```sh devsy provider add aws --name aws-gpu -o AWS_INSTANCE_TYPE=p3.8xlarge ``` **From GitHub.** Devsy downloads the `provider.yaml` from the latest release. It uses your local HTTPS credentials, so private repositories work. Add `@` and a tag for another release. ```sh devsy provider add devsy-org/devsy-provider-terraform devsy provider add my-org/my-repo@v0.0.1 ``` **From a local path or URL.** ```sh devsy provider add ../devsy-provider-mock/provider.yaml devsy provider add https://github.com/devsy-org/devsy-provider-ssh/releases/download/v0.0.3/provider.yaml ``` ## Set provider options [#set-provider-options] Each provider has its own options. In the desktop app, open **Providers** and click **Edit** on a provider. With the CLI, set options when you add or initialize: ```sh devsy provider add -o KEY=value devsy provider init -o KEY=value ``` List the options and their current values: ```sh devsy provider get ``` Change one later: ```sh devsy provider set --option KEY=VALUE ``` ## Docker provider [#docker-provider] Devsy follows the Docker CLI's rules for contexts and daemon endpoints. It keeps your existing Docker `config.json` and contexts, so contexts from Docker Desktop, OrbStack, or a remote endpoint keep working. Devsy picks the connection in the same order as the Docker CLI: 1. `DOCKER_CONTEXT` 2. `DOCKER_HOST` 3. The active context in `$DOCKER_CONFIG/config.json`, or `~/.docker/config.json` when `DOCKER_CONFIG` is not set 4. The local socket, `unix:///var/run/docker.sock` Set `DOCKER_CONTEXT` or `DOCKER_HOST` as a provider option or as an environment variable: ```sh devsy provider set docker -o DOCKER_CONTEXT= devsy provider set docker -o DOCKER_HOST=ssh://user@docker-host ``` For a remote TCP daemon with mutual TLS, set `DOCKER_TLS_VERIFY=1` and point `DOCKER_CERT_PATH` at your client certificates. ### Rancher Desktop [#rancher-desktop] Rancher Desktop works with the Docker provider when it uses the Moby engine. If you use `containerd`, switch to Moby. On macOS Devsy looks for the CLI at `~/.rd/bin/docker` when `docker` is not on your `PATH`. You can set both values yourself: ```sh devsy provider set docker -o DOCKER_PATH="$HOME/.rd/bin/docker" devsy provider set docker -o DOCKER_CONTEXT=rancher-desktop ``` ## Single machine [#single-machine] By default each workspace gets its own machine. To share one machine between all workspaces on a provider, turn on **Reuse machine** in the provider's options in the desktop app, or run: ```sh devsy provider init --single-machine ``` ## Default provider [#default-provider] Devsy uses the default provider for workspaces that do not name one. Set it in the provider's options in the desktop app, or run: ```sh devsy provider use ``` ## Update a provider [#update-a-provider] Show available versions. Add `--prerelease` to include pre-releases and `--no-cache` to skip the version cache. ```sh devsy provider versions ``` Fetch the provider's source again. Registry entries and unpinned GitHub repositories resolve to the latest release; a pinned tag fetches that version. Local paths and direct `provider.yaml` URLs reload that source. ```sh devsy provider set-source ``` Pin a version, or replace the source: ```sh devsy provider set-source --version v0.2.0 devsy provider set-source my-org/my-repo devsy provider set-source https://path/to/provider.yaml devsy provider set-source ../path-to/provider.yaml ``` In the desktop app, use the provider's **Select version**, **Update**, and **Initialize** actions. Devsy prepares the replacement provider’s installation binaries (`binaries`) before changing the installed provider. Runtime binaries declared under `agent.binaries` are prepared later by workspace operations. A download, checksum, archive, or cancellation error during preparation keeps the installed binaries, provider settings, initialization state, and workspace associations intact. Absolute local binary paths are checked without modifying the referenced files. References into the managed binaries directory must also match the prepared result; ambiguous aliases into that directory are rejected before publication. If publishing the prepared update fails, Devsy attempts to restore the previous files. If restoration fails, the error identifies retained recovery artifacts; keep those files for recovery. This does not guarantee recovery after a process or machine crash. Initialization runs after publication: an initialization failure leaves the new provider installed and available for another initialization attempt. Existing workspaces keep the provider version they were created with. Recreate one with `devsy workspace up --recreate` to pick up the new version. ## Remove a provider [#remove-a-provider] In the desktop app, click the trash icon on the provider. If a workspace uses it, Devsy asks you to remove that workspace first. With the CLI: ```sh devsy provider delete ``` Delete or move the provider's workspaces first. Otherwise you cannot use them until you add the provider again. ## Rename a provider [#rename-a-provider] ```sh devsy provider rename ``` Workspaces, machines, options, and the default setting follow the new name. If any step fails, Devsy rolls the rename back. * The new name must be unique and can use lowercase letters, numbers, and dashes, up to 32 characters. * Pro providers cannot be renamed. * Stop the provider's workspaces first. In the desktop app, edit **Provider Name** in the provider's configuration and click **Update Options**. # What are Providers? (/docs/managing-providers/what-are-providers) A provider tells Devsy where and how to run workspaces: your machine, a cloud, Kubernetes, or an SSH host. Each provider is a `provider.yaml` that declares its options, binaries, and commands. To use one, see [Manage providers](./manage-providers.mdx). To build one, see [How to develop a provider](../developing-providers/quickstart.mdx). ## Provider types [#provider-types] * **Machine providers** create a VM for the workspace, run the container on it, and start, stop, and delete the VM as needed. AWS is an example. * **Non-machine providers** run the container directly on the target. Docker, Podman, Kubernetes, and SSH are examples. # Linux Troubleshooting (/docs/troubleshooting/linux-troubleshooting) Known issues and fixes on Linux. ### Files owned by an unknown user after using a local folder [#files-owned-by-an-unknown-user-after-using-a-local-folder] When you start a workspace from a local folder and `devcontainer.json` sets `remoteUser` or `containerUser`, Devsy changes the folder's ownership to that user so the container can write to it. On the host the owner then shows as an unknown user. After the workspace stops, restore it: ```sh sudo chown -R $(id -un):$(id -gn) . ``` Local folders suit early work. Once the devcontainer works, push it to a Git repository and use that instead. ### Fish shell [#fish-shell] Anything in `config.fish` runs on every `fish -c` call, which can interfere with `devsy agent workspace up`. Move your customizations inside the `if status is-interactive` block: ``` if status is-interactive eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" # customizations end ``` ### SELinux [#selinux] With SELinux, a mounted volume can fail with "Permission denied" even when ownership is correct. Add `:Z` to the volume: ```json { "workspaceMount": "", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "runArgs": [ "--volume=${localWorkspaceFolder}:/workspaces/${localWorkspaceFolderBasename}:Z" ] } ``` ### `ENAMETOOLONG` when opening a workspace in VS Code [#enametoolong-when-opening-a-workspace-in-vs-code] Some distributions have a very long `PATH`, which makes the SSH connection string too long. Set the SSH binary explicitly in VS Code. See [issue #1045](https://github.com/devsy-org/devsy/issues/1045). ### Docker permission denied or daemon unreachable [#docker-permission-denied-or-daemon-unreachable] If `devsy workspace up` fails with a permission error on `docker.sock`, add your user to the `docker` group and log in again: ```sh sudo usermod -aG docker $USER ``` If the daemon is not running, start it: ```sh sudo systemctl start docker ``` ### Podman [#podman] #### Rootless socket [#rootless-socket] Rootless Podman uses the socket `$XDG_RUNTIME_DIR/podman/podman.sock`. If Devsy cannot connect, start the user socket: ```sh systemctl --user start podman.socket ``` Or point Devsy at it: ```sh devsy provider set podman --option PODMAN_HOST=unix:///run/user/$(id -u)/podman/podman.sock ``` #### `podman compose` [#podman-compose] `podman compose` hands off to an external provider, either `podman-compose` or a standalone `docker-compose`. Install one: ```sh sudo apt-get install -y podman-compose ``` Install this on the host that runs Podman to support Compose-based workspaces. It does not install Compose inside the workspace container. If workspace scripts call `docker-compose` or `podman compose`, install those tools separately in the container. #### BuildKit syntax [#buildkit-syntax] `podman build` uses buildah, so Dockerfiles with the `# syntax=docker/dockerfile:1` line or `RUN --mount` fail. Remove the syntax line and avoid BuildKit mounts. #### Volume permissions in rootless mode [#volume-permissions-in-rootless-mode] Rootless Podman maps user IDs, so files created in the container can appear owned by `nobody` on the host. With SELinux, use `:Z` as above. Without it, fix ownership: ```sh podman unshare chown -R $(id -u):$(id -g) /path/to/volume ``` # Troubleshooting (/docs/troubleshooting/troubleshooting) Known issues and workarounds. For Linux-specific problems, see [Linux troubleshooting](./linux-troubleshooting.mdx). ### Utilities not found in PATH [#utilities-not-found-in-path] If Devsy Desktop cannot find tools such as `docker` in your `PATH`, start it through your shell. On macOS: ```sh #!/usr/bin/env sh exec "$SHELL" -lic 'exec /Applications/Devsy.app/Contents/MacOS/Devsy' ``` ### Port forwarding does not work without an IDE [#port-forwarding-does-not-work-without-an-ide] Port forwarding needs an open SSH session. With `--ide none`, keep one open with `devsy workspace ssh WORKSPACE_NAME`. Ports declared in Docker Compose do not need it. ### Reading CLI errors [#reading-cli-errors] Pass `--log-output json` to get errors as JSON with a stable `code` and a `message`. Some errors also include a `hint` and a `context` object. ```json { "code": "UNKNOWN", "message": "exit status 1: something failed" } ``` | Code | Meaning | | ---------------------------------- | ------------------------------------------------------------------------------------------ | | `RATE_LIMITED` | An upstream API rate-limited the request. Wait and retry, or authenticate. | | `BUILD_FAILED_RECOVERABLE` | The devcontainer build failed in a way that can be retried, for example with `--recovery`. | | `docker_daemon_unreachable` | Docker is not running or not reachable. | | `canceled` | The operation was canceled. | | `deadline_exceeded` | The operation timed out. | | `unlock_required`, `unlock_failed` | The secrets store is locked, or unlocking it failed. | | `secret_backend_unavailable` | The secrets backend could not be reached. | | `secret_store_corrupt` | The secrets store could not be read. | | `secret_not_found` | The requested secret does not exist. | | `PANIC` | Devsy hit an internal error. Please report it. | | `UNKNOWN` | The error was not classified. The original text is in `message`. | Run with `--debug` to see the full error chain. ### Windows: `line 2: $'\r': command not found` [#windows-line-2-r-command-not-found] Windows line endings break shell scripts inside the container. Add this to `.gitattributes`: ``` *.sh eol=lf ``` ### Neovim: `$TERM` issues over SSH [#neovim-term-issues-over-ssh] See [issue #1187](https://github.com/devsy-org/devsy/issues/1187) for a workaround. # Docker provider via WSL (/docs/tutorials/docker-provider-via-wsl) This tutorial runs a Docker engine inside WSL2 and connects Devsy, running on Windows, to it over SSH. Docker runs inside WSL, while Devsy and the editor run on Windows. Keep local project files on a Windows drive so Devsy can translate their mount paths into WSL paths. ## Install Docker in WSL [#install-docker-in-wsl] ### Enable WSL2 [#enable-wsl2] In PowerShell as Administrator, skip this step if WSL2 is already installed: ```powershell wsl --install ``` This enables the required features and installs a default Linux distribution. Without `wsl --install`, enable both the Windows Subsystem for Linux and the Virtual Machine Platform features by hand, restart, and run `wsl --set-default-version 2`. Without the Virtual Machine Platform, WSL falls back to WSL1. To install a specific distribution, run `wsl --install Ubuntu-24.04` and set a username and password. Open its shell with `wsl`. The steps below use `systemctl`, which needs systemd inside WSL. Distributions installed through `wsl --install` enable it by default. If it is missing, add this to `/etc/wsl.conf`, then run `wsl --shutdown` from PowerShell and start `wsl` again: ```ini [boot] systemd=true ``` ### Install Docker Engine [#install-docker-engine] Follow the [official Docker Engine installation for Ubuntu](https://docs.docker.com/engine/install/ubuntu/) inside WSL. Then add your user to the `docker` group and start the services: ```bash sudo usermod -aG docker $USER sudo systemctl enable --now docker.service containerd.service ``` Behind a corporate proxy, also [configure the Docker daemon proxy](https://docs.docker.com/engine/daemon/proxy/). ### Install an SSH server [#install-an-ssh-server] The Windows Docker client reaches the daemon over SSH, not an open TCP port: ```bash sudo apt-get update sudo apt-get install -y openssh-server sudo systemctl enable --now ssh.service ``` WSL2 forwards `localhost` to Windows, so the server is reachable there. To avoid password prompts, run `ssh-keygen` on Windows and append the public key to `~/.ssh/authorized_keys` in WSL. ## Connect Windows to the daemon [#connect-windows-to-the-daemon] Download the Docker CLI from the [Docker static binaries for Windows](https://download.docker.com/win/static/stable/x86_64/), extract it, and add its folder to `PATH`. Then create a context that uses SSH. Replace `` with your WSL username: ```powershell docker context create lin --docker "host=ssh://@localhost" docker context use lin docker run hello-world ``` The first connection asks you to accept the SSH host key. ## Connect Devsy [#connect-devsy] Add the Docker provider and point it at the WSL daemon: ```bash devsy provider add docker -o DOCKER_HOST=ssh://@localhost -o DOCKER_PATH=docker ``` If the provider already exists, use `devsy provider set docker -o ...` with the same options. Check the result with `devsy provider get docker`. `DOCKER_HOST` accepts anything the Docker CLI accepts. When `DOCKER_HOST` is not a local socket, Devsy converts Windows drive paths in mounts to their WSL form. A project at `C:\Users\me\repo` is mounted as `/mnt/c/Users/me/repo`. Keep project files on a Windows drive. Paths under `\\wsl.localhost\...` are not supported for mounts. ## Connect your editor [#connect-your-editor] Create the workspace. Devsy adds an SSH config entry named `.devsy`: ```bash devsy up ``` In VS Code or VSCodium with the [Open Remote - SSH](https://marketplace.visualstudio.com/items?itemName=jeanp413.open-remote-ssh) extension, choose **Open Folder in Remote...** and select that host. Devsy tunnels the session into the dev container, so no jump host setup is needed. The tunnel reuses your local SSH keys and agent. If the editor log shows `write EPIPE`, run `devsy up` with `DEVSY_DEBUG=true`. The real cause, such as the WSL SSH server not running or an untrusted host key, is otherwise hidden behind that generic error. ## Next steps [#next-steps] See [Create a Workspace](../developing-in-workspaces/create-a-workspace.mdx). # Kubernetes Provider with Minikube and VS Code Browser (/docs/tutorials/minikube-vscode-browser) ## Purpose [#purpose] The purpose of this quickstart is to provide a small scale representation of on-premise Kubernetes and guide a new user through the configuration of a Kubernetes provider. ## Install Minikube on Ubuntu 22.04 [#install-minikube-on-ubuntu-2204] Install dependencies for installing minikube and vscode ```sh sudo apt update sudo apt install git vim gpg wget -y ``` ### Install VSCODE [#install-vscode] Follow instructions from VSCODE Docs > [Linux Setup](https://code.visualstudio.com/docs/setup/linux) ### Install Minikube [#install-minikube] Clone Sander van Vugt's Ubuntu CKAD repository for his CKAD course [Certified Kubernetes Application Developer (CKAD), 2nd Edition, 4.3 Installing Minikube on Ubuntu. O'Reilly Course](https://learning.oreilly.com/videos/certified-kubernetes-application/9780137841509/9780137841509-CKAD_01_04_03/) ```sh git clone https://github.com/sandervanvugt/ckad.git cd ./ckad ./minikube-docker-setup.sh ``` #### Create startup script [#create-startup-script] ```sh cd ~ touch minikube-start.sh sudo chmod u+x minikube-start.sh ``` Add the following contents to minikube-start.sh: ```bash #!/bin/bash minikube start --vm-driver=docker --cni=calico ``` Now to start minikube from home: ```sh ~/minikube-start.sh ``` Start minikube again after every VM shutdown or reboot. #### Test your install [#test-your-install] Test your install with the following command ```sh kubectl get all ``` Example Result: ```sh kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 443/TCP 4d14h ``` ## Install Devsy [#install-devsy] Install Devsy from the Linux DEB package, as described in [Install Devsy](../getting-started/install.mdx). Download the deb install ```sh cd ~/ wget https://github.com/devsy-org/devsy/releases/latest/download/Devsy_linux_amd64.deb sudo dpkg -i Devsy_linux_amd64.deb ``` ### Test the Devsy install [#test-the-devsy-install] Start Devsy: ```sh devsy ``` ## Configure Minikube for the provider [#configure-minikube-for-the-provider] Before configuring the kubernetes provider we need to create a persistent volume for the devcontainer and ensure we have a kubeconfig for the provider. ### Create Kubernetes Persistent Volume for devcontainers [#create-kubernetes-persistent-volume-for-devcontainers] Kubernetes persistent volume [docs](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) ``` mkdir ~/devsys cd ~/devsys touch devsy-pv.yml vim devsy-pv.yml # or your preferred text editor ``` Edit devsy-pv.yml and save ```yaml kind: PersistentVolume apiVersion: v1 metadata: name: devsy-pv labels: type: devsy spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path: "/mnt/data/devsy" ``` A `hostPath` volume must exist inside the Minikube node itself (the VM or container that runs Kubernetes), not on your tutorial host machine. Create the directory inside the node using `minikube ssh`: ```sh minikube ssh -- sudo mkdir -p /mnt/data/devsy ``` Create the persistent volume for the Kubernetes Provider. ```sh kubectl create -f ~/devsys/devsy-pv.yml ``` Confirm the persistent volume has been created. ```sh kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE devsy-pv 1Gi RWO Retain Available 12d ``` ### Locate your kubeconfig [#locate-your-kubeconfig] List `~/.kube` ```sh ls ~/.kube cache completion.bash.inc config ``` The `config` file in `~/.kube` is the kubeconfig that can be used by Devsy kubernetes provider. In the Kubernetes provider, the kubeconfig path is ``` /home/dev/.kube/config ``` **Note** This is a simplification for a production setup where a kubeconfig should be provided for a kubernetes context, namespace and users. You will also need kubectl installed where your Devsy client is running from. The kubernetes provider uses the underlying kubectl providing the locally stored kubeconfig to access a remote cluster. This setup requires kubernetes administration support. ### Identify your namespaces [#identify-your-namespaces] The kubernetes provider requires a namespace and this example will use the 'default' namespace ```sh kubectl get ns NAME STATUS AGE default Active 12d kube-node-lease Active 12d kube-public Active 12d kube-system Active 12d ``` ## Add Provider [#add-provider] Add a provider via 'Providers' > '+ Add'. Select the kubernetes provider.
Devsy Flow
Select Provider
### Configure the Kubernetes Provider [#configure-the-kubernetes-provider] | Setting | Value | | -------------------- | ---------------------- | | Kubernetes Namespace | default | | Disk Size | 1Gi | | Kubernetes Config | /home/dev/.kube/config | Select **Add Provider** ### Add a Workspace [#add-a-workspace] #### Start a Workspace with VS Code Browser [#start-a-workspace-with-vs-code-browser] Navigate to 'Workspaces' > '+ Create'. Enter your project url or choose one of the quickstart examples. Make sure to select 'VS Code Browser' under 'Default IDE' Then press 'Create Workspace'. A new window appears showing Devsy starting the workspace. After the workspace was created, VS Code should open automatically connected to the DevContainer. # Podman Provider Setup (/docs/tutorials/podman-provider-setup) Podman is a built-in Devsy provider. It runs containers without a background daemon and is OCI-compatible, so most `devcontainer.json` files work unchanged. See [known differences](#known-differences) for the exceptions. This page covers installing Podman, adding it as a provider, and starting a workspace. ## Install Podman [#install-podman] ### Linux [#linux] Use your package manager: ```bash sudo apt-get install -y podman # Debian / Ubuntu sudo dnf install -y podman # Fedora / RHEL / CentOS sudo pacman -S podman # Arch ``` ### macOS [#macos] ```bash brew install podman podman machine init podman machine start ``` [Podman Desktop](https://podman-desktop.io) also works. ### Windows [#windows] Install the [Windows Podman client](https://podman.io/docs/installation) and its WSL2-backed machine. Run Podman and Devsy from Windows, not from an executable installed only inside a WSL distribution. In PowerShell, initialize and start the machine if it does not already exist: ```powershell podman machine init podman machine start podman info ``` Use the Windows named pipe from `podman machine inspect` for `PODMAN_HOST`, as described below. A Unix socket inside WSL is not a Windows endpoint. Check `podman system connection list` and make the connection for this machine the default: Devsy uses the native Podman client as well as the Docker-compatible API. ## Add the provider [#add-the-provider] ```bash devsy provider add podman ``` ## Options [#options] | Option | Default | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PODMAN_PATH` | `podman` | Path to the `podman` binary, if it is not on `PATH`. | | `PODMAN_HOST` | unset | Podman API endpoint. On Windows, use the machine named pipe in `npipe:////./pipe/` form, not a Unix socket inside WSL. On Linux and macOS, use `unix://` followed by the socket path. | | `PODMAN_ELEVATION` | `none` | Run `podman` through `pkexec`, `sudo` or `doas` to reach a rootful socket the current user cannot access. Leave it as `none` for rootless Podman. `pkexec` needs a desktop session with a polkit agent, so use `sudo` or `doas` on headless hosts. | | `INACTIVITY_TIMEOUT` | unset | Stop the container after this idle time, for example `10m` or `1h`. | For a provider you already added, update its options: ```bash devsy provider set podman --option PODMAN_PATH=/usr/local/bin/podman devsy provider get podman ``` Alternatively, set the option during the initial add instead of running the plain add command above: ```bash devsy provider add podman -o PODMAN_PATH=/usr/local/bin/podman ``` ## Rootless and rootful [#rootless-and-rootful] Rootless is the default and the recommended setup. Containers run as your user. Use rootful only when a container must bind-mount root-owned paths or needs network setups such as macvlan. On macOS and Windows, switch the Podman machine mode: For a new machine: ```bash podman machine init --rootful my-rootful-machine podman machine start --update-connection my-rootful-machine ``` Or switch the existing default machine: ```bash podman machine stop podman machine set --rootful podman machine start ``` Switching mode does not delete images, containers or volumes. They are hidden while the other mode is active and return when you switch back. On Linux, to reach a rootful socket without running everything as root, set `PODMAN_ELEVATION` to `sudo` or `doas`. After switching modes, run `podman machine inspect ` for the machine you started and update `PODMAN_HOST`: * **macOS:** use `unix://` followed by `.ConnectionInfo.PodmanSocket.Path`. * **Windows:** read `.ConnectionInfo.PodmanPipe.Path` and use `npipe:////./pipe/`, replacing `` with the name after `\\.\pipe\` in that path. Check `podman system connection list` and select the connection for the same machine and rootful/rootless mode with `podman system connection default `. `PODMAN_HOST` selects the Docker-compatible API; the native Podman CLI uses its own default connection. Keep `PODMAN_ELEVATION` as `none`, because the machine connection is already authenticated. ## Start a workspace [#start-a-workspace] ```bash devsy workspace up --provider podman --id my-workspace https://github.com/my-org/my-repo devsy workspace ssh my-workspace ``` ## Troubleshooting [#troubleshooting] ### Socket not found or permission denied [#socket-not-found-or-permission-denied] Devsy cannot reach the Podman socket. On macOS and Windows, check that the machine is running with `podman machine list`, and start it with `podman machine start`. On Linux, check the user socket: ```bash systemctl --user status podman.socket ``` If the socket path is not the default, set it: ```bash devsy provider set podman --option PODMAN_HOST=unix:///run/user/$(id -u)/podman/podman.sock ``` ## Known differences [#known-differences] ### Dockerfile builds [#dockerfile-builds] Podman builds with Buildah, not BuildKit. BuildKit-only syntax, such as `RUN --mount=type=cache` under the `buildkit` frontend, can fail. Remove the `# syntax=docker/dockerfile:1` line or rewrite those steps. ### Compose [#compose] `podman compose` hands off to an external provider: the `podman-compose` package or a standalone `docker-compose` binary. Install one: ```bash sudo apt-get install -y podman-compose ``` The `docker-compose-plugin` package does not work here. It adds the `docker compose` subcommand to the Docker CLI and pulls in Docker. Check what is available: ```bash podman compose version ``` Use `podman compose` instead of `docker-compose` in your scripts. # Reduce build times with remote caching (/docs/tutorials/reduce-build-times-with-cache) Devsy can use a container registry as a remote cache for builds. Prebuild once, and everyone who builds after that reuses the cached layers. ## Set up [#set-up] Point Devsy at a registry path, in the form `domain/project/repo`: ``` devsy context set -o REGISTRY_CACHE=gcr.io/my-project/my-dev-env ``` With the Docker provider, the Docker daemon needs the containerd image store. Merge this into your existing `/etc/docker/daemon.json`, or use **Settings > Docker Engine** in Docker Desktop, and keep any other settings already in the file: ```json { "features": { "containerd-snapshotter": true } } ``` Restart Docker, then check that the containerd image store is active. The output should include `driver-type io.containerd.snapshotter.v1`: ``` docker info --format '{{.DriverStatus}}' ``` Switching the storage backend does not delete images or containers, but items stored under the old backend are not visible while the new one is active. ## Populate the cache [#populate-the-cache] Build the workspace image once to fill the cache: ``` devsy workspace build my-workspace ``` Later builds pull from the cache and build only what changed. ## How Devsy detects changes [#how-devsy-detects-changes] Devsy reads your Dockerfile and `devcontainer.json`, finds the files that affect the build, and hashes their contents into the image tag. When a file changes, the hash changes and Devsy rebuilds, reusing the cached layers it can. Follow Docker's Dockerfile best practices to keep rebuilds small. ## Builds in Kubernetes [#builds-in-kubernetes] With the Kubernetes driver and no container runtime on your machine, Devsy builds the image in the cluster with Kaniko. Kaniko builds in userspace without a Docker daemon. Whether it can run without root depends on the base image and Dockerfile commands. # What is Devsy? (/docs/what-is-devsy) Devsy creates development workspaces from a [devcontainer.json](https://containers.dev/) that lives in your repository. A workspace runs as a container, a Kubernetes pod, or a microVM, and your IDE connects to it.
Animated demo of launching Devsy, connecting to a workspace, and opening it in an IDE
Devsy
Where a workspace runs is decided by a [provider](./managing-providers/what-are-providers). Use a provider for your own machine, an SSH host, Kubernetes, or a cloud. You can also [write your own](./developing-providers/quickstart). Every workspace is created and managed the same way regardless of provider. ## Why Devsy? [#why-devsy] * **One environment definition.** `devcontainer.json` is the open standard, so the same file works on every provider. * **Your infrastructure.** Run workspaces on machines you already pay for. Machine providers can stop idle machines with an [inactivity timeout](./developing-in-workspaces/inactivity-timeout). * **No lock-in.** Recreate a workspace on another provider with the same definition. Transferring container and volume state requires a snapshot; a metadata-only import does not copy that state. * **Your editor.** VS Code, the JetBrains IDEs, and any editor that can connect over SSH. * **Desktop app and CLI.** The desktop app is for everyday use. The CLI is for automation.