DevContainer/Secrets/Report/26/01/Secrets Management in JetBrains DevContainers
Secrets Management in JetBrains DevContainers (Clone from VCS)
Challenges of Secret Management in JetBrains DevContainers
- When using JetBrains IDEs (like RustRover or PyCharm) with the "Clone from VCS" devcontainer workflow, managing secrets (API keys, credentials, etc.) is a known pain point. In this mode, the IDE clones your Git repository into a Docker volume and then builds/starts the Dev Container from that source[1] [2]. This ensures a reproducible environment, but it also means that any secret not in the repo (by design, secrets should be excluded) won't be present in the container unless you take extra steps. Unlike fully managed cloud dev environments, JetBrains' local devcontainer setup does not prompt for or automatically inject secrets during this process. Developers often face a choice:
- Manually adding secrets after clone: The simplest (though not very automated) approach is to manually create or copy a secrets file (e.g. a .env file) once the container is up. For example, a team might commit a placeholder .env.example to the repo and instruct developers to create a .env inside the dev container with actual secret values after the container starts. This is effective but labor-intensive and prone to being forgotten.
- Baking secrets into config (not recommended): Technically one could put secrets into the devcontainer.json or Dockerfile, but this is bad practice since it would commit sensitive data to source control (violating the principle "never commit secrets into the image"[3]). So this is avoided.
- Automating via configuration: The Dev Container spec allows referencing host environment variables or files so that secrets can be provided without hardcoding them. For example, you can set "containerEnv": { "API_KEY": "${localEnv:API_KEY}" } in devcontainer.json[4]. This means "take the API_KEY value from my local machine's env and set it inside the container." In practice, however, using this in JetBrains' Clone-from-VCS flow is tricky – if you're creating a devcontainer from scratch via the IDE's wizard, you might not have an opportunity to supply those local env vars through the UI. It can work if you pre-add a devcontainer config to the repo (or after an initial build) and then relaunch the container with the proper host env variables set. But JetBrains' UI doesn't yet have a dedicated prompt for injecting environment variables at container startup (unlike, say, Codespaces which injects user secrets automatically).
- In summary, JetBrains developers using devcontainers today often resort to manual secret setup. This could be as basic as opening a terminal in the container and editing a .env file with their secrets, or running a script to pull secrets from somewhere once the container is running. There isn't a built-in, one-click secret provisioning mechanism in the JetBrains devcontainer flow at this time. A JetBrains support request confirms that features like passing secrets via the UI/CLI are still missing – users have requested the ability to set secret env vars during the Docker build/run, but currently "if the UI supports setting secrets in any way" it's not yet implemented (feature request in YouTrack)[5].
- Why is this an issue? The Clone-from-VCS mode creates an ephemeral containerized dev environment that is isolated from your local machine. It's great for consistency, but it means your local machine's environment (where you might have stored credentials) isn't automatically shared. Without additional tooling, the container won't have, for example, your AWS keys or database passwords. Below we explore ways to automate this process and how other platforms handle it.
Automating Secrets with a Two-Phase DevContainer Workflow
- One concept floated by developers is a "two-phase" devcontainer workflow: first build the dev environment image (with all necessary tools but no secrets), then inject secrets into the running container as a second step. In effect, this mimics how cloud dev environments handle secrets (separating the image build from runtime configuration).
- JetBrains Current Behavior: In the JetBrains flow, the devcontainer build is tightly coupled with cloning the repo. The steps happen in one sequence – the repo is cloned into a volume, then used as build context for Docker, then the container is started[1] [2]. There's no built-in pause to load secrets between cloning and running. The Dev Container CLI provided by JetBrains (which is a command-line tool to automate building devcontainers) similarly doesn't yet have flags for injecting secrets or env vars at runtime – it focuses on producing the container image/artifact[6] [7].
- However, you can implement two-phase setup manually:
- Phase 1: Build Base Image – Ensure your devcontainer/Dockerfile doesn't require any secret at build time. Build the devcontainer normally (which creates an image with your IDE, languages, and tools installed). For example, if you need a private package or dependency during build, consider redesigning so that build can proceed without needing credentials (or use build-time ARGs that you supply in a safe way, though Docker secrets for build are not fully exposed in JetBrains UI).
- Phase 2: Post-Clone Secret Injection – Once the container is running (or at first startup), run a script to fetch and load secrets. The devcontainer spec supports lifecycle hooks like "postCreateCommand" or "postStartCommand" which JetBrains respects. You could set, in your devcontainer.json, something like:
"postStartCommand": "/workspaces/myproject/scripts/load-secrets.sh"where load-secrets.sh might pull secrets from an API or copy from a mounted volume. This script could populate environment variables or create a .env file inside the container.
- The limitation is that the script itself needs access to the secret values somehow. Typically this means the script would reach out to a secrets manager (which requires credentials/tokens), or you mount an external file containing secrets. If you simply prompt the user, you're back to a manual step (albeit inside the IDE UI).
- JetBrains CodeCanvas example: JetBrains' own cloud-hosted dev environment platform, CodeCanvas, uses a two-phase approach under the hood. Administrators can define a Dev Environment Template that includes an image and then specify Personal Parameters (user-specific secret env vars or files) to inject at startup[8] [9]. CodeCanvas also supports "Cloud policy" credentials – e.g., auto-injecting cloud resource keys – which are applied to the container when it's created[10]. This means the base container is prepared in advance (and even warmed up), and when a developer launches it, their personal secrets get layered in just for their session. In essence, CodeCanvas achieves the ideal two-phase workflow: an admin provides the base devcontainer (phase 1) and the platform injects secrets at runtime (phase 2) in a secure way[11] [12].
- For standalone JetBrains IDE users (not on CodeCanvas), replicating this experience requires custom scripting. One could script the JetBrains Dev Container CLI to first build an image, then run a container from it with docker run -e VAR=value or similar to pass secrets. But this is outside the scope of the JetBrains UI – you'd be dropping to Docker commands.
- Best Practice: Treat JetBrains devcontainers similar to any Docker environment – don't bake secrets into the image. Instead, plan for a post-start injection. Many developers simply document this: e.g. "After opening the devcontainer, run ./dev-setup.sh which will pull required secrets." That script might use a CLI for a vault (1Password, AWS, HashiCorp Vault, etc.) to download secrets into the container. It's not as slick as Codespaces, but it ensures no secrets in git and still automates the process to some degree. We'll discuss a popular option (1Password CLI) next.
Integrating 1Password with DevContainers
- 1Password CLI (Outside the Container): One approach to avoid manual secret entry is using the 1Password CLI to populate environment variables before the container launches. Developers have tried using op (1Password's CLI) to inject secrets into the VS Code devcontainer startup process. For instance, one can store secret references in a .env file (e.g. API_KEY="op://vault/item"), and then launch VS Code with op run --env-file .env -- code .. The 1Password CLI reads the file, fetches the real secrets, and runs VS Code with those secrets exported in the environment[13] [14]. In theory, if those env vars propagated, the devcontainer would start with them available.
- The reality: by default devcontainers do not inherit the parent process environment in VS Code. A user found that even running VS Code under op run did not pass the variables into the container's environment[15]. They attempted a direct hack by constructing a vscode-remote://dev-container+... launch command with op run, but it still didn't inject the vars. This led to a feature request to support launching devcontainers with the parent environment[16] [17]. As of early 2026, this isn't solved in VS Code yet – and JetBrains IDEs have a similar limitation. JetBrains doesn't provide a built-in way to say "include my host env when creating the Dev Container." So using op run on the JetBrains client or ijdevc CLI likely won't magically populate the remote container's variables.
- 1Password CLI (Inside the Container): Another strategy is to install and run the CLI within the devcontainer. For example, you could add to your Dockerfile or devcontainer features an installation of op. A community member described scripting the install of 1Password CLI inside the container as part of setup[18] [19]. Once op is in the container, you face the issue of authentication – normally you'd have to do an interactive op signin (entering email, secret key, etc.), which is not ideal in an automated setup[20].
- To avoid interactive login each time, 1Password recommends using a Service Account token for non-interactive use. If you create a 1Password Service Account, you get an API token that can be used to authenticate the CLI in headless environments. The suggestion from 1Password forums was: provide the service account token to the container via an env var like OP_SERVICE_ACCOUNT_TOKEN, then the op CLI can operate without any prompts[21]. In practice, that means you still need to smuggle that one token into the container (which could be via an environment variable on launch, a Kubernetes secret if you were on K8s, or even a manual paste the first time followed by caching it). But once OP_SERVICE_ACCOUNT_TOKEN is set, you can run commands like op read "op://vault/secret" or use op inject to replace placeholders in files. For example, you might keep a template config file in the repo with op:// references and have a post-start command op inject -i config.template -o config.json to produce a file with real secrets[22].
- JetBrains 1Password Plugin: JetBrains IDEs have a community plugin ("1Password for IDEA") that integrates with 1Password CLI[23] [24]. This plugin is primarily aimed at making the IDE experience nicer – e.g., you can right-click on a file in the editor to preview secrets (resolving any op:// references), or use a macro in Run Configurations to fetch a secret at launch. It's important to note the plugin runs on the client side (the JetBrains frontend). In a remote devcontainer scenario, the plugin can help you insert secret values into, say, your run configuration environment variables or Docker run config, but it doesn't automatically provision secrets to the container without your action. Also, certain features (like hover-to-reveal secret reference) don't work over remote connections due to the need for local biometric unlock and the separation between local UI and remote backend[25].
- Summary: 1Password can be used, but it requires setup:
- If you're automating via CLI, using a service account token and running op within the container startup script is the most hands-off method (no human typing creds each time). This turns 1Password into your secrets backend for the devcontainer: the container pulls what it needs on boot.
- If you prefer to use the CLI outside, you currently hit limitations in passing those env vars into the JetBrains container creation. A potential workaround is to launch the Docker container with env vars (outside the JetBrains flow). For instance, you could manually do docker run -e VAR1=... -e VAR2=... <devcontainer-image> to start the container with secrets, and then connect the IDE to that container. But that foregoes the "Clone from VCS" one-click experience; instead, it becomes a manual two-step (launch container, then use "Connect to Running Dev Container" in JetBrains).
- The JetBrains plugin is helpful for interactive use (filling in values for local run or providing quick access to vault items) but it's not a full solution for automating container environment setup.
Comparing Secrets Management Across Dev Environments
- Different development container platforms handle secrets differently. Below is a comparison of JetBrains devcontainers with other common environments (VS Code, Codespaces, etc.), highlighting how secrets are managed:
- JetBrains DevContainer (Clone from VCS): No built-in secret injection. Developers must supply secrets manually or via custom scripts. Commonly, a .env file is created inside the container (not in VCS). The devcontainer config can reference host env vars (e.g. ${localEnv:VAR})[4], but this requires the user to set those vars before launch. JetBrains' UI/CLI doesn't yet offer a flag or prompt to pass secret files or variables at container startup (a feature under discussion). In practice, many rely on third-party tools like 1Password CLI or AWS CLI to fetch secrets once the container is running.
- JetBrains CodeCanvas (Cloud): Platform-managed secrets. CodeCanvas templates allow admins to define Personal Parameters – user-specific secret env vars or files – which are automatically injected into the devcontainer when it starts[8] [9]. Also supports Cloud Policies that auto-inject cloud provider credentials into the environment[10]. This means developers using CodeCanvas get their secrets without manual steps, and secrets stay in the platform's secure storage. (CodeCanvas is analogous to Codespaces for JetBrains, and it addresses secrets as first-class citizens.)
- VS Code DevContainers (Local): User-managed secrets. VS Code's Dev Containers extension does not have a centralized secrets store – instead it relies on the user's setup. Typically, you keep an uncommitted .env file on your machine with needed secrets, and use it via Docker Compose or devcontainer config. For example, you might have a docker-compose.yml for your devcontainer that includes an env_file: .env (which loads those vars into the container), or use containerEnv/remoteEnv in devcontainer.json referencing ${localEnv:...} to pull in host environment variables[26] [27]. These approaches let you avoid committing secrets while still injecting them at runtime. They are somewhat manual (you must ensure the env file or host env is set up). VS Code has recently added a "secrets" property in the devcontainer spec for Codespaces compatibility, but if using local Docker, the onus is on the developer to provide those values (e.g., via .env or extension settings). Integrations like the 1Password VS Code extension can replace secrets in files with references and resolve them on the fly, but again, that happens at the editor level rather than automatically populating container env vars.
- GitHub Codespaces: Built-in secrets injection. Codespaces provides a seamless way to handle secrets: you add secrets in your GitHub account or repository settings, and whenever a Codespace is created, those secrets are injected as environment variables in the container[28] [29]. For example, if you set a secret named API_KEY in GitHub, your Codespace container will have an env var API_KEY available. In devcontainer.json, you can just expect it or explicitly reference it via ${localEnv:API_KEY} (Codespaces treats its secret as "local env" during container config)[26] [27]. This means no .env files are needed and no manual steps – secrets are managed by the platform securely. One limitation is that these secrets are available at runtime, but not during the build phase of the container (to avoid secrets leaking into images). Codespaces encourages patterns like using the secret at application startup or using devcontainer Features that know how to retrieve secrets at build without exposing them.
- Cursor (VS Code Fork): Similar to VS Code local. Cursor is an IDE based on VS Code that supports devcontainers. It doesn't have its own cloud secret service. So, secrets are handled in similar ways: through environment files or external tools. If using Cursor locally, you'd likely use a .env or direnv to load secrets, and the devcontainer config can pull them in just like VS Code's (since Cursor's devcontainer extension is a fork, though note that not all features may be on par – e.g., one user noted limitations in repository config path support in Cursor's devcontainer extension). In essence, working in Cursor means you'll manage secrets just as you would in a local VS Code scenario. There's no special integration for 1Password or others beyond what VS Code provides, so the same techniques (env files, op run, etc.) would apply.
- Note: Other platforms and tools exist too. For example, GitHub DevContainers CLI (which underlies VS Code's implementation) has a --secrets-file option, but as of 2024 it only injected those into the VS Code server process (not all container processes)[5] – highlighting how evolving this space is. Third-party solutions like Infisical provide devcontainer Features to inject secrets from a vault at runtime (so no file on disk)[30]. And some self-hosted development platforms (like Coder or DevPod) also have their own way to supply secrets. Generally, the trend is toward not storing secrets in any repo or image, but injecting at container start in a secure manner.
Patterns for Multi-Environment Projects
- In teams where developers use multiple environments – e.g. some use JetBrains with devcontainers, others use VS Code or GitHub Codespaces – it's important to establish a pattern that keeps secrets out of version control while still accessible in each dev setup. Here are some common patterns and tips for multi-environment compatibility:
- Use Environment Variables as the interface: Design your application to pull secrets from environment variables (or a well-known config file) rather than hardcoding anything. For instance, your app might expect a DATABASE_URL env var. This way, each environment just needs to ensure that variable is set – the source can differ (host .env, Codespaces secret, etc.). If all tools/environments use the same variable names, you can reuse the configuration. For example, a project might document that "the API_TOKEN variable must be set in your dev environment." In Codespaces you'd add API_TOKEN in Secrets; locally you'd put it in your .env or export it in your shell; in JetBrains, you might add it to the devcontainer config via ${localEnv:API_TOKEN} so it grabs from your host OS, or just manually export it in the container.
- Commit a Template and Use Vaults: It's common to include a non-secret template in the repo – e.g. an .env.template file listing the required keys (but with dummy values). Developers can use this as a checklist. From there, they might use a secrets vault CLI to populate it. For instance, you could have a script fetch-secrets.sh that reads from a service (1Password, Vault, AWS SecretsManager, etc.) and creates a real .env. This script can be written to detect the environment: if it's running in Codespaces, maybe skip because Codespaces already set env vars; if running locally or in JetBrains, use the CLI. Some sophisticated setups even use the presence of certain env vars to detect environment (e.g., Codespaces sets CODESPACES=true internally).
- Direnv and EnvFile plugins: In purely local development, tools like direnv or JetBrains' EnvFile plugin have been used to load env vars automatically. In a devcontainer, these can still help – for example, you could install direnv inside the container and include an .envrc that pulls in your secrets file. When you cd into the workspace directory in the container, direnv would then load those into the shell. This is an extra setup step but can unify the workflow: any time the project directory is active, the needed vars are in the environment. Keep in mind JetBrains remote terminals might need extra config to use a login shell so that direnv triggers. This approach is mostly beneficial if you have a shared way to distribute an encrypted env file or use something like Mozilla SOPS to encrypt a file in the repo that can be decrypted to actual secrets with a key (this gets complex, though).
- Multiple devcontainer configs: Some monorepos or multi-environment projects maintain separate devcontainer configurations for different IDEs. For example, they might have a .devcontainer/devcontainer.json (optimized for VS Code) and also documentation or config for JetBrains. It's generally preferable to have one devcontainer spec that works for all, but small differences (like needing remoteEnv vs containerEnv or differences in extensions) can creep in. If you do separate them, ensure both are kept in sync regarding how they load secrets. For instance, you might have one devcontainer that uses a Docker Compose file with an env_file, which works in both VS Code and JetBrains (JetBrains does support Docker Compose in devcontainers). Make sure that env file is listed in .gitignore and each environment knows how to populate it (maybe via the above-mentioned script).
- Example – Unified Approach: Let's illustrate a hypothetical pattern: Suppose your app needs DB_URL and API_KEY. You decide those will be environment vars. You commit an .env.template with:
DB_URL=<insert database URL> API_KEY=<insert API key>In your devcontainer.json (used by both VS Code and JetBrains), you reference these vars if available:
"containerEnv": { "DB_URL": "${localEnv:DB_URL}", "API_KEY": "${localEnv:API_KEY}" }This means: if the developer has those set on their host, the container will get them. In Codespaces, the host env is pre-populated by Codespaces secrets (so it works automatically)[26] [27]. In JetBrains, if the developer exports those in their terminal before launching the container (or sets them in their OS user env and restarts the IDE), the Dev Containers plugin will substitute them in[4]. As a fallback, if not provided, the app might fail until they supply them – but it won't accidentally use a wrong value.
- Now, for local VS Code users, you could provide a script set-secrets.ps1 or .sh that reads a secure store. Or instruct them to use direnv/EnvFile to load a private .env file on their system (which sets those two vars) before opening the devcontainer. For JetBrains users, you might write in docs: "Before creating the Dev Container, ensure you have DB_URL and API_KEY in your environment, or be prepared to add them to the running container." This isn't perfect, but it at least provides a path that doesn't require code changes per environment.
- Some teams using 1Password will do something like: store all dev secrets in a 1Password vault, and have a script that uses op CLI to export all of them to env. The StackOverflow snippet we saw was exactly that idea – they had all secrets for all projects set as host OS env vars, and were migrating to a model of using op run --env-file per project[16] [13]. While the VS Code integration wasn't fully there yet for passing through, the concept stands: one source of truth (1Password vault), and a project-specific env file referencing those secrets by name.
- Beware of limitations: Each environment might impose subtle differences. For example, JetBrains remote dev might not propagate your local environment the way VS Code does. Codespaces will uppercase all secret names (as noted in one case where MyVar became MYVAR) and you might need to adjust for case sensitivity[31] [32]. Also, what works in a local container (like binding a file with secrets) might not work in a cloud IDE that doesn't support mounting local files. So design with the lowest common denominator – typically treating everything as ephemeral and pulling from a network service or environment variables.
- In summary, multi-environment projects manage secrets by: not committing them, using environment variables as the common mechanism, and leveraging each platform's strengths (GitHub Secrets for Codespaces, external vault CLI for local/JetBrains, etc.). The process isn't yet uniform across IDEs, which is why we see developers creatively combining tools to fill the gaps. As of 2026, JetBrains' Clone-from-VCS devcontainers still require a bit of DIY for secrets, whereas platforms like Codespaces and CodeCanvas have more automated injections. Keeping an eye on updates (e.g., new Dev Container CLI features or JetBrains IDE improvements) is wise – this area is actively evolving to reduce friction and improve security for secret management in dev environments.
Footnotes
[1] https://www.jetbrains.com/help/rust/dev-container-limitations.html
[2] https://www.jetbrains.com/help/rust/dev-container-limitations.html
[4] https://devcontainers.github.io/implementors/json_reference/
[6] https://www.jetbrains.com/help/idea/dev-container-cli.html
[7] https://www.jetbrains.com/help/idea/dev-container-cli.html
[8] https://www.jetbrains.com/help/codecanvas/configure-dev-environments.html
[9] https://www.jetbrains.com/help/codecanvas/configure-dev-environments.html
[10] https://www.jetbrains.com/help/codecanvas/configure-dev-environments.html
[11] https://www.jetbrains.com/help/codecanvas/configure-dev-environments.html
[12] https://www.jetbrains.com/help/codecanvas/configure-dev-environments.html
[13] https://github.com/microsoft/vscode-remote-release/issues/9339
[14] https://github.com/microsoft/vscode-remote-release/issues/9339
[15] https://github.com/microsoft/vscode-remote-release/issues/9339
[16] https://github.com/microsoft/vscode-remote-release/issues/9339
[17] https://github.com/microsoft/vscode-remote-release/issues/9339
[18] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[19] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[20] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[21] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[22] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[25] https://www.1password.community/discussions/developers/secrets-in-a-devcontainer-setup/85811
[26] https://stackoverflow.com/questions/73052779/using-github-codespaces-secrets-in-devcontainer-json
[27] https://stackoverflow.com/questions/73052779/using-github-codespaces-secrets-in-devcontainer-json
[30] https://www.danz.blog/blog/infisical-devcontainers-feature
[31] https://stackoverflow.com/questions/73052779/using-github-codespaces-secrets-in-devcontainer-json
[32] https://stackoverflow.com/questions/73052779/using-github-codespaces-secrets-in-devcontainer-json