Devsy
Developing in a Workspace

Secrets in a Workspace

Devsy can inject sensitive values into workspaces without placing plaintext credentials in devcontainer.json, command-line arguments, or ordinary dotenv files.

Secrets can come from two places:

  • Devsy-managed secrets - values owned by Devsy and stored in the operating system keyring or Devsy's encrypted local file.
  • External secret sources - values owned by another system, such as a SOPS-encrypted file. Devsy resolves these values when they are needed and does not import them into the local Devsy secret store.

Both use the same protected workspace delivery paths: lifecycle environment variables, in-memory files under /run/secrets, and supported build-secret mechanisms.

Devsy-Managed Secrets

With the default auto storage backend, Devsy stores sensitive values in your operating system's keyring when one is available:

  • macOS - Keychain
  • Windows - Credential Manager
  • Linux - Secret Service (libsecret / GNOME Keyring / KWallet)

When no keyring is available, or when the file backend is selected, Devsy stores values in an age-encrypted secrets.enc file in the Devsy config directory. Only non-sensitive metadata such as names and timestamps is written in plaintext. Devsy-managed secrets are scoped to the active context.

Creating a Secret

# Interactive input; the value is not echoed.
devsy secret set DB_PASSWORD

# Standard input is recommended for scripts.
printf '%s' "$MY_VALUE" | devsy secret set DB_PASSWORD --stdin

# Or read a value from a file.
devsy secret set TLS_KEY --from-file ./tls.key

Listing, Reading, and Deleting

devsy secret list
devsy secret get DB_PASSWORD
devsy secret delete DB_PASSWORD

devsy secret list never prints values and can show secret metadata while a backend is locked. Its availability status is separate from the backend that owns the value. Operations that need plaintext, including get and workspace startup, still fail when a required value cannot be read.

Managing Variables in Desktop

Open Workspace Variables to manage configuration in two tabs: Secrets and Environment Variables. Each table shows the name, context, and workspace injection setting. Secret rows also show their storage backend and availability. Secret values are never displayed.

Use Add secret or Add variable to create an entry. To change an existing entry, open its details and choose Replace secret value for a secret or edit the environment value. Names are scoped to a context, so the same name in another context is a separate entry. Desktop checks for existing names when adding; the CLI set commands continue to create or update values.

Locked secrets remain visible, and you can change their Inject into workspaces setting without unlocking. This changes the context attachment, not the stored value. Reading, replacing, or deleting file-backed values can still require unlocking. A locked file store does not prevent access to available keyring-backed secrets or managed environment variables.

Environment values are masked by default in Desktop and can be revealed for an individual row. Masking only hides the display: environment variables are stored in plaintext and are intended for non-sensitive configuration.

External Secret Sources

An external secret source lets Devsy resolve a value at workspace startup without making Devsy a second source of truth for that value.

SOPS

Devsy supports SOPS-encrypted YAML, JSON, and dotenv files. Devsy uses the SOPS Go implementation; installing a sops executable is not required.

SOPS source documents use flat top-level key/value pairs in the initial implementation:

DATABASE_PASSWORD: ENC[...]
API_TOKEN: ENC[...]

Registering a Local SOPS Source

If the encrypted file is already available on the machine running Devsy, add a named source:

devsy secret source add sops project ./secrets.enc.yaml

Devsy validates that the file can be decrypted before saving the source. The configuration stores the source name, type, file path, and (when set via --format) the document format override only. Decrypted values are not copied into the keyring or Devsy's secrets.enc file.

List or remove locally registered sources with:

devsy secret source list
devsy secret source remove project

A source cannot be removed while context-level secret bindings still reference it.

Repository-Owned SOPS Sources

A repository can declare SOPS sources in either the root-level .devcontainer.json or .devcontainer/devcontainer.json layout under customizations.devsy.secretSources.

project/
├── .devcontainer/
│   └── devcontainer.json
└── secrets.enc.yaml

Example devcontainer.json:

{
  "name": "My Project",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "customizations": {
    "devsy": {
      "secretSources": [
        {
          "name": "project",
          "type": "sops",
          "path": "./secrets.enc.yaml"
        }
      ],
      "secrets": [
        "sops:project/DATABASE_PASSWORD"
      ]
    }
  }
}

Repository source paths are resolved relative to the repository root. Devsy rejects paths, including symlink targets, that escape the repository root.

Repository-owned sources work with a local checkout:

devsy workspace up .

and with a remote Git source:

devsy workspace up https://github.com/acme/project

For a remote source Devsy first acquires repository data to read the requested Git revision, then reads customizations.devsy from the effective Dev Container configuration and the referenced encrypted SOPS files from that same revision.

Bootstrap Authentication for Private Repositories

Credentials needed to acquire a private repository must be available before Devsy can inspect repository-owned SOPS files.

For example, this is valid:

local Devsy secret / Git credential / SSH agent
                │
                ▼
        authenticate repository clone
                │
                ▼
     repository SOPS runtime secrets

A repository-owned SOPS secret cannot authenticate the clone of the same repository that contains it.

--git-token may reference a Devsy-managed secret or another external source that is already locally available before repository acquisition.

Referencing Secrets

An unqualified name continues to mean a Devsy-managed local secret:

DB_PASSWORD

An external source uses a source-qualified reference:

sops:project/DB_PASSWORD

Devsy does not search other sources if a reference cannot be resolved. Explicit source selection prevents one source from silently shadowing another.

Lifecycle Environment Variables

--secret is repeatable. By default, Devsy exposes the requested value as an environment variable to lifecycle commands:

devsy workspace up . --secret DB_PASSWORD
devsy workspace up . --secret sops:project/DATABASE_PASSWORD

Use target= to change the environment-variable name:

devsy workspace up . \
  --secret sops:project/DATABASE_PASSWORD,target=DB_PASSWORD

An explicit --secret value is available to lifecycle commands during that workspace up. It does not become an environment variable in later terminal sessions. Repository customizations.devsy.secrets bindings have the same lifecycle-only scope.

Mounted Secret Files

Use type=mount to write the secret to the existing in-memory secret mount:

devsy workspace up . \
  --secret sops:project/TLS_KEY,type=mount,target=tls.key

The value is available at:

/run/secrets/tls.key

Providers that cannot offer an in-memory secret mount reject type=mount with an error.

Build Secrets

Source-qualified references can also be used with Devsy's existing build-secret path:

devsy workspace up . --build-secret sops:project/NPM_TOKEN

The build secret ID is the secret key (NPM_TOKEN), not the complete source-qualified reference. Builds continue to consume it through the existing BuildKit secret mechanism, for example:

RUN --mount=type=secret,id=NPM_TOKEN ...

Attaching a Secret to a Context

Attach a locally available secret reference to the active context so it is injected during workspace setup and into new Devsy-managed terminal/SSH sessions:

devsy secret attach DB_PASSWORD
devsy secret attach sops:project/API_TOKEN

Detach it with:

devsy secret detach DB_PASSWORD
devsy secret detach sops:project/API_TOKEN

Attachments are context-scoped and contain only the secret reference, never the value. The Desktop Workspace Variables Secrets tab shows and changes the same attachment state. Devsy resolves an attached secret on workspace up, makes it available to lifecycle commands, and supplies it to each new Devsy-managed SSH session and its child processes. This includes devsy workspace ssh and the Desktop workspace terminal. It does not set a global container environment variable or change processes and sessions that are already running. After detaching, run workspace up or recreate the workspace for future sessions to lose the value.

An attached secret is a context default. An explicit session environment such as workspace ssh --set-env NAME=value takes precedence over the attached value for that session. When names collide, new sessions use this order:

explicit session environment > attached secret > base container/user environment

Devsy recreates an existing managed workspace when it needs to add the terminal-secret runtime mount. Setup verifies that /run/devsy/secrets-env is itself a tmpfs mount before writing any attached terminal secret. If the provider cannot create that mount, setup fails instead of writing the value to the container's writable layer.

Devsy stores only the reference for an external secret. Repository-owned customizations.devsy can declare project-specific automatic bindings with its secrets list. If any requested or attached value cannot be resolved, workspace up fails rather than silently starting without it.

Secret Delivery Scope

SourceLifecycle environmentNew Devsy SSH/terminal sessionsFile mountImage build
Context-attached secretYesYesNoNo
workspace up --secret NAMEYesNoNoNo
workspace up --secret NAME,type=mountNo (file only)No/run/secrets/<target>No
workspace up --secrets-file ...YesNoNoNo
Project customizations.devsy.secrets bindingYesNoNoNo
workspace up --build-secret NAMENoNoNoBuildKit only

Environment-injected secrets are inherited by child processes started in the lifecycle hook or terminal session. Use type=mount when file-based access is appropriate and a narrower process-environment scope is preferred. Build secrets are available only to build steps that request them.

SOPS Credential Discovery

Devsy delegates key and KMS credential discovery to SOPS instead of creating a parallel credential system.

For age-encrypted files, normal SOPS mechanisms apply, including SOPS_AGE_KEY, SOPS_AGE_KEY_FILE, and the standard SOPS age-key location. For example:

export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt"
devsy workspace up .

For AWS KMS, GCP KMS, Azure Key Vault, PGP, and other key services supported by SOPS, use the same credentials and key configuration you would use with SOPS itself.

How Secrets Are Protected

Devsy workspaces protect sensitive values by:

  • Decrypting SOPS sources in memory at workspace startup without persisting plaintext to disk
  • Injecting secrets directly as environment variables or memory-backed files under /run/secrets
  • Redacting known secret values from logs and terminal outputs
  • Excluding secret values from client-server transport and workspace metadata

Context attachments store secret references only. Values remain in their configured secret backend and are transferred during workspace setup through Devsy's protected secret-delivery channel. For new Devsy-managed terminal sessions, selected values are copied to an owner-only, memory-backed runtime location and read only when the SSH server starts the session. This location is /run/devsy/secrets-env, must be an exact tmpfs mount, is not the container's global environment, and is excluded from workspace metadata and snapshots.

Choosing a Devsy Storage Backend

Devsy stores managed secrets and managed environment variables separately. Secret metadata lives in secrets.yaml; file-backed secret values share the encrypted secrets.enc file. Managed environment values are plaintext in the separate env.yaml file. Environment commands read and write that file without opening or unlocking any secret backend, so a locked secret store does not prevent devsy env operations. Environment values are not protected secrets; use devsy secret for sensitive values.

Devsy-managed secrets use one of two storage backends:

  • keyring - the OS keyring (Keychain / Credential Manager / libsecret).
  • file - an age-encrypted secrets.enc stored alongside the Devsy config.

Choose a backend preference for newly created secrets:

  • auto - prefer the keyring when available and otherwise use file.
  • keyring - store new secrets in the OS keyring.
  • file - store new secrets in the encrypted file store.

Set a persistent preference per context with:

devsy context set -o SECRETS_BACKEND=file

Or override it for one command with DEVSY_SECRETS_BACKEND.

These preferences select storage only when a new Devsy-managed secret is created. After creation, its concrete backend is recorded in secrets.yaml and does not change when the preference changes. auto is never recorded as the backend for an existing secret.

The file backend uses one protection mode for the complete secrets.enc store: an automatically managed key or a passphrase. This protection mode is independent of the backend preference. Changing SECRETS_BACKEND affects where future secrets are stored; it does not change how existing file-backed secrets are protected.

When a new file store is initialized, an explicit passphrase or DEVSY_SECRETS_PASSPHRASE can select passphrase protection. The passphrase-file and remembered-keychain sources unlock a store that is already passphrase protected; they do not choose the protection mode for a new store. To set the mode deliberately before storing secrets, run devsy secret protection set-passphrase.

Protecting the File Store with a Passphrase

Configure passphrase protection with the devsy secret protection commands:

devsy secret protection status
devsy secret protection set-passphrase
devsy secret protection change-passphrase
devsy secret protection remove-passphrase

status reports the file store's protection mode and availability, including whether a remembered credential is available.

In Desktop, open Manage security from the Secrets tab or the compact Secret Security section in Settings. The security sheet shows the file store's protection and availability and whether a credential is remembered on this device. Protection applies to file-backed secrets across all contexts.

Changing file-store protection or its remembered credential requires confirmation in a native application dialog. Canceling the dialog leaves protection and the remembered credential unchanged. Choosing to remember a passphrase while unlocking also requires native confirmation and an active unlock request.

Use Remember on this device to save a verified passphrase in the operating system credential store. Forget this device removes that saved credential; it does not change file-store protection or delete secrets. A passphrase already cached by the running Desktop application may still provide access.

The advanced Clear session passphrase action removes the Desktop's in-memory passphrase. Other available credentials may still unlock the store. An approved protection change already running may finish, but it will not restore the cleared session credential. Remembered OS-keychain credentials remain until you choose Forget this device.

While an unlock submission is running, another credential submission for that request is rejected. You can still cancel unlocking. If an approved Remember action then succeeds, Desktop reports that the credential was saved even though unlocking was canceled. The main process retains this notice across a page reload until Desktop displays and acknowledges it. Use Forget this device to remove the credential.

Passphrase protection applies to all values in the file backend. It is not a secret storage backend or a per-secret setting. To supply the passphrase, Devsy checks sources in this order: explicit input from the invoking UI or process, DEVSY_SECRETS_PASSPHRASE, DEVSY_SECRETS_PASSPHRASE_FILE, an opt-in remembered credential in the operating-system keyring, then an interactive prompt when the command supports prompting. A supplied credential that fails to unlock the store is an error; Devsy does not fall through to a lower priority source.

For automation, set DEVSY_SECRETS_PASSPHRASE_FILE to a file containing the passphrase. Devsy accepts files up to 64 KiB, strips at most one trailing newline, and rejects an empty passphrase. On Unix, the file must have restrictive permissions (readable only by its owner, such as mode 0600). Configure projected container or Kubernetes secret files with a similarly restrictive mode. The file path is host-specific and is not saved in context configuration.

Remembering a passphrase is opt-in and stores it in the operating-system credential store shared by the CLI and Desktop:

devsy secret protection remember
devsy secret protection forget

remember verifies the current passphrase before saving it. forget removes only the remembered credential; it does not change encryption or delete secrets.

In Desktop, Recovery options explains how to restore access and provides the reset command to copy. A remembered device credential may still unlock the store if you have forgotten the passphrase. Without a valid credential, the encrypted file contents cannot be decrypted. Resetting removes every file-backed secret entry across all contexts; use it only after reviewing the recovery options. Reset the complete file-backed store from the CLI with explicit confirmation:

devsy secret protection reset-file-store

The reset lists all file-backed secret names across contexts. Confirm interactively by typing RESET, or use --yes for explicit non-interactive confirmation. If secrets.enc exists, Devsy moves it to a quarantine filename for possible manual recovery. Reset removes file-backed metadata and removes the remembered credential when the operating-system keychain is accessible. If the keychain is unavailable during reset, restore access and run devsy secret protection forget to remove that credential. Reset leaves keyring-backed secrets and the independent plaintext env.yaml environment store untouched. Recreate any lost file-backed secrets afterward. External sources such as SOPS continue to use their own credentials and are not affected by this local file-store setting.

Managed Environment Variables

For non-sensitive configuration, devsy env stores managed environment variables in plaintext in the separate env.yaml file:

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 with:

devsy workspace up ... --env LOG_LEVEL --env REGION=AWS_REGION

Use devsy secret or an external secret source for sensitive values. --env is deliberately restricted to non-sensitive Devsy-managed values because that path is not the protected secret-delivery channel.

Attach a stored variable to the active context to inject it automatically when workspaces start or are recreated:

devsy env attach LOG_LEVEL
devsy env list
devsy env detach LOG_LEVEL

Attachments are context-scoped and contain only the variable name. The Desktop Workspace Variables Environment Variables tab shows and changes the same attachment state. Managed environment variables are non-sensitive plaintext values; use devsy secret for secrets.

An explicit devsy workspace up --env LOG_LEVEL still works for one invocation. An explicit target, such as --env LOG_LEVEL=APP_LOG_LEVEL, overrides the automatic attachment for that reference and injects the stored value as APP_LOG_LEVEL. Attaching or detaching does not mutate an already-running workspace; apply changes through the normal workspace start or recreate lifecycle.

An attached environment variable must be detached before converting the same stored name into a secret. Likewise, a locally attached secret must be detached before converting it into an environment variable. This keeps the non-sensitive environment path separate from protected secret delivery. If a stored value is deleted, Devsy removes its context attachment as part of the same operation.

Environment-to-secret conversion finishes only after the plaintext entry is removed. If the process stops between saving the secret and removing that entry, both stores can contain the name. Restore backend access and retry devsy secret set, or use devsy env delete to remove the remaining plaintext entry. Conversion does not provide crash-atomic recovery across the two stores.

Sensitive values use devsy secret attach instead; attached secrets are injected through the protected secret-delivery paths described above, and the Desktop Workspace Variables Secrets tab manages their attachment state.

On this page