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.keyListing, Reading, and Deleting
devsy secret list
devsy secret get DB_PASSWORD
devsy secret delete DB_PASSWORDdevsy 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.yamlDevsy 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 projectA 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.yamlExample 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/projectFor 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 secretsA 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_PASSWORDAn external source uses a source-qualified reference:
sops:project/DB_PASSWORDDevsy 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_PASSWORDUse target= to change the environment-variable name:
devsy workspace up . \
--secret sops:project/DATABASE_PASSWORD,target=DB_PASSWORDAn 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.keyThe value is available at:
/run/secrets/tls.keyProviders 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_TOKENThe 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_TOKENDetach it with:
devsy secret detach DB_PASSWORD
devsy secret detach sops:project/API_TOKENAttachments 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 environmentDevsy 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
| Source | Lifecycle environment | New Devsy SSH/terminal sessions | File mount | Image build |
|---|---|---|---|---|
| Context-attached secret | Yes | Yes | No | No |
workspace up --secret NAME | Yes | No | No | No |
workspace up --secret NAME,type=mount | No (file only) | No | /run/secrets/<target> | No |
workspace up --secrets-file ... | Yes | No | No | No |
Project customizations.devsy.secrets binding | Yes | No | No | No |
workspace up --build-secret NAME | No | No | No | BuildKit 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-encryptedsecrets.encstored alongside the Devsy config.
Choose a backend preference for newly created secrets:
auto- prefer the keyring when available and otherwise usefile.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=fileOr 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-passphrasestatus 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 forgetremember 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-storeThe 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_LEVELInject them with:
devsy workspace up ... --env LOG_LEVEL --env REGION=AWS_REGIONUse 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_LEVELAttachments 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.