Using DevContainers in JetBrains IDEs

DevContainers aren't VS Code-only — JetBrains IDEs support the same spec. This page opens a devcontainer from a JetBrains IDE using the identical committed .devcontainer/, so a mixed-editor team shares one environment definition regardless of who uses which IDE.

This matters most on teams where editor choice is genuinely split: a couple of developers live in IntelliJ IDEA or GoLand, others prefer VS Code, and nobody wants to maintain two parallel environment definitions that inevitably drift apart. The moment you have a Java service pinned to JDK 21, a Go toolchain at a specific version, and a set of postCreateCommand steps that seed the database, that setup deserves to live in exactly one place. The devcontainer spec is that place. Because IntelliJ, PyCharm, GoLand, and the rest read the same devcontainer.json — the same image digest, the same features block, the same lifecycle hooks — the JetBrains user and the VS Code user land in byte-for-byte the same container, differing only in the editor that attaches to it.

The mental model to hold is the client/backend split. JetBrains, like the VS Code Dev Containers extension, runs a thin client on your host machine and a full IDE backend inside the container. The backend indexes the code, resolves the toolchain, and runs the language services against the real filesystem and real interpreters that the .devcontainer/ provisioned; the host client is only a rendering and input surface. Reach for this whenever a JetBrains developer needs the exact same reproducible environment the CI pipeline and the VS Code users already get, rather than approximating it with a locally installed SDK that happens to match today and diverges next month.

Prerequisites

You need a JetBrains IDE with Gateway/dev containers support and an engine.

  • A recent JetBrains IDE (or JetBrains Gateway).
  • A container engine reachable by the IDE.
  • A committed .devcontainer/devcontainer.json.

JetBrains prerequisitesYou need a supporting IDE, the same config, and a container backend.JetBrains IDEdev containers supportSame config.devcontainer/Build + attachIDE backend incontainerVerifytools resolve

The "recent JetBrains IDE" requirement is worth taking literally: dev container support has moved quickly, and older builds either lack it or ship an earlier iteration of the integration that reads a narrower slice of the spec. If you can, use a current release of the full IDE (2023.3 or later has the most stable Dev Containers entry point) or the standalone JetBrains Gateway client, which is purpose-built for connecting to remote and containerized backends. The container engine must be reachable by the process that launches the build — typically Docker or a compatible engine exposing the same socket. On Linux that is usually the local Docker daemon; on macOS or Windows it is whatever engine backs your Docker CLI, and the IDE needs to see the same socket the docker command does.

The detail people most often get wrong is treating the JetBrains connection as if it needs its own environment definition. It does not. The single .devcontainer/devcontainer.json you already commit for VS Code is the whole contract — you do not fork it, rename it, or add a JetBrains-specific variant. If the config builds and runs correctly for a VS Code teammate, the same file is what JetBrains consumes. Any per-editor difference belongs in the editor, not in a second copy of the config.

Step-by-Step Implementation

  1. Open the project's devcontainer from the IDE (Remote Development → Dev Containers) using the committed config.

    Point the IDE at the repository's existing .devcontainer/devcontainer.json rather than creating a new one from a template. The Remote Development → Dev Containers path is deliberately the same entry point a VS Code user reaches through "Reopen in Container": both ask the engine to build (or reuse) an image from that one config and then start a backend inside it. Choosing the committed file, not a scaffolded default, is what guarantees the JetBrains developer gets the identical image digest and Feature set the rest of the team already runs, avoiding the silent divergence that starts the moment two definitions exist.

  2. Keep the config editor-agnostic — Features and hooks are shared; editor extensions differ.

{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu@sha256:PINNED",
  "features": { "ghcr.io/devcontainers/features/java:1": { "version": "21" } },
  "remoteUser": "vscode"
}

Everything in this snippet is portable across editors. The image field pins a digest rather than a floating tag such as :ubuntu, so every IDE — and CI — resolves the exact same layers instead of whatever the registry happens to serve that day. The features block installs JDK 21 through the standard java Feature, which runs at build time inside the container and is therefore invisible to the editor layer entirely; JetBrains sees a real java on the PATH exactly as VS Code does. Note "remoteUser": "vscode" — despite the name it is not a VS Code-only setting. It names the non-root user the container process runs as, and JetBrains honors it too. Leaving the config free of any customizations.vscode assumptions here is what prevents editor-specific state from leaking into the shared build.

  1. Let the IDE run its backend in the container (JetBrains client/backend split, analogous to VS Code).

    When you connect, JetBrains downloads and launches a matching IDE backend inside the running container while the client stays on your host. That backend is what indexes the project, resolves the JDK the java Feature installed, and runs inspections against the real container filesystem — not a mirror on your laptop. This is the same architecture VS Code uses, and it is the reason the environment is genuinely reproducible: the language services execute against the pinned toolchain the config provisioned, so a "works on my machine" difference between the JetBrains user and everyone else has nowhere to hide. The first connection is slower because the backend has to be provisioned; subsequent opens reuse it.

  2. Verify the toolchain resolves inside the container.

    Confirm the tools actually resolve from inside the backend, not from your host. Open a terminal in the IDE — which drops you into a shell inside the container — and check that java -version reports 21 and that the interpreter the project uses points at a path under the container, not /usr/lib/jvm on your host. This last step catches the common trap where the IDE appears to open successfully but is silently indexing against a host SDK because the backend never attached correctly; if the versions match the config's Feature and the paths sit inside the container, the split is working as intended.

Shared vs editor-specificThe container definition is shared; only editor plugins/settings differ per IDE.Shared (in config)Base imageFeaturesLifecycle hooksEditor-specificVS Code extensionsJetBrains pluginsIDE settings

Common Pitfalls

JetBrains issues are editor-specific customizations or an unreachable engine. The JetBrains model mirrors VS Code's: a thin host client and a backend running inside the container against the real toolchain, driven by the identical .devcontainer/.

The most persistent trap is expecting the customizations.vscode block to do anything for JetBrains. It won't — that namespace is scoped to VS Code, so any extensions, settings, or recommended plugins listed there are simply skipped by a JetBrains backend. This is not a bug to work around; it is the spec drawing a deliberate line between the shared container and the per-editor layer. The fix is never to move VS Code customizations into the shared build in the hope that JetBrains will pick them up, because that pollutes the definition everyone depends on. Instead, install the equivalent JetBrains plugins through the IDE for that editor and leave the container config unaware of either.

The other recurring failure is connectivity to the engine. Because the IDE has to build the image and start the backend itself, it needs to reach the same Docker socket your docker CLI uses; if it can't, the connection fails before any Feature runs and the symptom looks like a broken config even though the config is fine. This is where the ownership angle bites teams that share a build cache or a named volume across editors: the container runs as the remoteUser from the config (here vscode), so anything the JetBrains backend writes into a mounted cache is owned by that user. If a host process or a differently-configured editor later touches the same volume as root, you get permission errors that read like toolchain failures. Keep the remoteUser consistent across every editor that mounts the shared cache, and let the container — not the host — own the paths inside it.

JetBrains triageA triage path from an editor-specific failure to a shared environment across IDEs.Does the IDE reach the engine?NOConfigure the container backendDo Features/hooks apply?YESEditor plugins are separateShared env, any IDE

SymptomRoot CauseRemediation
VS Code extensions don't load in JetBrainscustomizations.vscode is VS Code-onlyUse JetBrains plugins for that IDE
IDE can't reach the engineBackend not configuredPoint the IDE at the container engine
Toolchain missingFeature not appliedConfirm the config builds the same Features
Config drift between editorsEditor-specific baked into shared partsKeep base/Features/hooks editor-agnostic

Conclusion

DevContainers are a spec, not a VS Code feature — JetBrains IDEs consume the same .devcontainer/. Keep the base image, Features, and hooks editor-agnostic, and let each IDE layer its own plugins, so a mixed-editor team shares one reproducible environment.

The strategic payoff is that editor choice stops being an environment decision. A developer can move from VS Code to GoLand, or a new hire can bring PyCharm to a repo that was previously all-VS-Code, and the environment they build is identical down to the pinned image digest — because the same image, the same features, and the same lifecycle hooks are the only source of truth, and both editors read it the same way. That is the same reproducibility guarantee you get from pinning a digest and caching a Feature: the container is defined once, provisioned deterministically, and consumed by whatever attaches to it. Adding JetBrains support does not weaken that guarantee; it extends the audience for it.

Concretely, this ties the pin-and-cache discipline the rest of the environment already follows to the editor question directly. The digest pin in image is what makes the JetBrains backend and the CI runner agree; the build-time Features are what keep the toolchain out of the editor layer entirely; and the shared remoteUser is what keeps a cross-editor cache from turning into a permissions minefield. Treat customizations.vscode and its JetBrains equivalents as the only editor-specific surface, keep everything else in the config free of editor assumptions, and the reproducible-environment story holds no matter which IDE a teammate opens tomorrow.

One env, many editorsThe container is shared across IDEs; each editor brings its own plugins.Any IDE getsSame base imageSame FeaturesSame hooksPer IDEIts own pluginsIts own settingsIts own client

FAQ

Do devcontainers only work with VS Code? No. DevContainers are an open spec, and JetBrains IDEs (via Gateway/Remote Development) consume the same .devcontainer/devcontainer.json. The base image, Features, and lifecycle hooks apply identically; only the editor layer — extensions vs plugins — differs per IDE. The naming can mislead here because the reference tooling and much of the documentation grew up alongside VS Code, but the file format, the Feature registry, and the lifecycle model are all editor-neutral. Any client that implements the spec, JetBrains included, builds the same container from the same fields.

What about the customizations.vscode block in JetBrains? It's ignored by JetBrains, since it's VS Code-specific. The container definition (image, Features, hooks) still applies. Configure JetBrains plugins and settings through the IDE for that editor, keeping the shared parts of the config editor-agnostic. Do not try to relocate those settings into the container build to force JetBrains to honor them — that only couples the shared definition to one editor. The customizations namespace exists precisely so per-editor preferences stay out of the parts every editor shares, and a JetBrains backend simply reads past a block addressed to a different client.

Can a team mix VS Code and JetBrains on one repo? Yes — that's a strength of the spec. Everyone builds the same container from the committed config; each developer's editor adds its own plugins on top. Keep the base image, Features, and hooks free of editor-specific assumptions and both work. In practice this means resisting the urge to bake an editor's expectations into postCreateCommand or the base image: if a hook only makes sense for one IDE, it belongs in that editor's configuration, not the shared build. Done that way, a pull request that changes the environment changes it for everyone at once, which is exactly the property you want.

Do I need JetBrains Gateway, or can I use the full IDE? Either works. The full desktop IDE ships the Dev Containers entry point under Remote Development, and standalone JetBrains Gateway is a thin client dedicated to connecting to remote and containerized backends. Gateway is useful when you want a lightweight launcher that manages several remote backends, while the full IDE is convenient if it is already installed. Both end up running the same backend inside the same container built from the same config, so the choice is about the host-side launcher, not the environment.

Why is the first JetBrains connection so much slower than reopening? The first time you connect, the IDE has to build or pull the image, run the Features and lifecycle hooks, and then download and start a matching IDE backend inside the container before it can index anything. That backend provisioning is the bulk of the wait. Once it exists it is reused, so later opens skip straight to attaching. A pinned image digest and a warm build cache shorten the first run considerably, which is another reason to pin rather than float the image tag.

Does the toolchain need to be installed on my host at all? No, and installing it there defeats the purpose. The JDK, Go toolchain, or interpreter your project uses is provisioned inside the container by the features block and lives only there; the backend resolves against it. A host SDK is at best redundant and at worst a source of confusion, because it can make a misconfigured connection look like it is working when the IDE is quietly indexing the host copy instead of the container's.