Pinning VS Code extension versions in devcontainer.json

customizations.vscode.extensions installs the latest version of each extension by default, so two engineers creating the same DevContainer a week apart can get different linters, formatters, or debuggers — and a breaking extension update silently changes everyone’s environment. This page pins extensions to exact versions for reproducibility. It extends the Managing VS Code Extension Caches cluster.

Prerequisites

  • VS Code with the Dev Containers extension, or the devcontainer CLI
  • Extension IDs and the versions you want (from the Marketplace “Version History”)

How-To Steps

  1. Append @version to each extension ID to pin it:

    {
      "image": "mcr.microsoft.com/devcontainers/typescript-node:20@sha256:7c3e...",
      "customizations": {
        "vscode": {
          "extensions": [
            "dbaeumer.vscode-eslint@3.0.10",
            "esbenp.prettier-vscode@11.0.0",
            "ms-python.python@2024.8.1"
          ]
        }
      },
      "remoteUser": "vscode"
    }
    

    Verify: the Extensions view shows the pinned version, not “latest”.

  2. Disable auto-update inside the container so a pinned extension is not silently upgraded:

    {
      "customizations": {
        "vscode": {
          "settings": {
            "extensions.autoUpdate": false,
            "extensions.autoCheckUpdates": false
          }
        }
      }
    }
    
  3. Cache the VSIX layer to avoid re-downloading pinned versions on every rebuild — covered by the parent cluster’s named-volume strategy:

    { "mounts": ["source=vscode-extensions,target=/home/vscode/.vscode-server/extensions,type=volume"] }
    
  4. Confirm the resolved set matches the pins across machines:

    devcontainer read-configuration --workspace-folder . \
      | jq '.configuration.customizations.vscode.extensions'
    

Common Pitfalls

SymptomRoot CauseRemediation
Extensions drift between engineersIDs without @version install latestPin every extension to an exact version
Pinned extension upgrades itselfAuto-update enabledSet extensions.autoUpdate and autoCheckUpdates to false
Re-download on every rebuildExtensions in the ephemeral layerMount the server extensions dir as a named volume
Pin rejected as invalidVersion doesn’t exist for that extensionCopy the exact version from Marketplace Version History

Conclusion

The invariant: every extension carries an explicit @version and auto-update is off, so the editor tooling is as reproducible as the image itself. Pin the versions, cache the VSIX layer, and confirm with read-configuration that every machine resolves the identical set.

FAQ

Does version pinning work in Codespaces too? Yes. The @version syntax is honoured by Codespaces and the devcontainer CLI, not just local VS Code, so the pin is portable.

How do I find a valid version string? Open the extension’s Marketplace page and use “Version History” — copy the exact version (e.g. 3.0.10) into the ID after an @.