Sharing VS Code Extensions and Settings Across a Team
Pin VS Code extensions and settings in devcontainer.json so every developer gets the same linters, formatters, and editor config. Keep the toolchain consistent team-wide.
Sharing VS Code Extensions and Settings Across a Team
New hires keep formatting files differently and linting locally with different rules because each developer installs their own extensions and tweaks their own settings. The fix is to pin the editor's extensions and settings in devcontainer.json so opening the project provisions an identical toolchain for everyone, following the same Devcontainer Configuration Standards that pin base images and features.
Diagnostic
Confirm the drift before standardizing. Each developer's extension list and effective settings differ:
#!/usr/bin/env bash
# audit-extensions.sh — list what each dev actually has installed
set -euo pipefail
code --list-extensions --show-versions
# BAD: two developers, two different toolchains
# Dev A
[email protected]
[email protected]
# Dev B
[email protected]
# (no prettier — formats with editor default, producing diff noise)
Workspace settings that are not committed live only in each user's profile, so editor.formatOnSave and editor.tabSize vary per machine.
Root cause
VS Code stores extensions and user settings in the per-user profile, not in the repository. Without a committed .devcontainer/devcontainer.json customizations.vscode block (or a .vscode/ folder for non-container setups), nothing forces consistency. The result is divergent linters, formatters, and editor behavior that surface as noisy diffs and inconsistent lint results.
Resolution
- Pin extensions and settings inside
customizations.vscodeindevcontainer.json. Use exact extension version pins to prevent silent breaking updates.
// .devcontainer/devcontainer.json
{
"name": "Platform Baseline",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "app",
"workspaceFolder": "/app",
"customizations": {
"vscode": {
"extensions": [
"[email protected]",
"[email protected]",
"[email protected]"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },
"files.eol": "\n"
}
}
},
"postCreateCommand": "npm ci"
}
- Pin language runtimes and CLIs as
featuresso the linters and formatters resolve the same binaries everywhere.
// .devcontainer/devcontainer.json (features excerpt)
{
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "20" },
"ghcr.io/devcontainers/features/git:1": { "version": "latest" }
}
}
- Recommend the same extensions for engineers who do not open the folder in a container by committing
.vscode/extensions.json. VS Code prompts them to install the set.
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker"
]
}
- Commit workspace settings in
.vscode/settings.jsonfor host-side editors, mirroring the containersettingsblock so both paths converge.
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n"
}
- Reopen the folder in the container so VS Code installs the pinned extensions and applies the settings.
#!/usr/bin/env bash
# rebuild the devcontainer to apply pinned extensions
set -euo pipefail
devcontainer up --workspace-folder . --remove-existing-container
Expected output
After reopening in the container, the installed set matches the pin exactly on every machine:
[email protected]
[email protected]
[email protected]
Prevention
Gate the configuration in CI so a malformed or unpinned change cannot merge:
# .github/workflows/devcontainer-lint.yml
name: devcontainer-lint
on:
pull_request:
paths: [".devcontainer/**", ".vscode/**"]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Reject floating extension pins
run: |
! grep -E '"[a-z0-9-]+\.[a-zA-Z0-9-]+"\s*[,\]]' .devcontainer/devcontainer.json \
| grep -vqE '@[0-9]+\.[0-9]+\.[0-9]+'
macOS (Docker Desktop): extension install runs inside the Linux VM on first open; expect a one-time delay, not a recurring cost. WSL2: keep the repo on the Linux filesystem (
~/code, not/mnt/c) so the Dev Containers extension resolves${localWorkspaceFolder}to a native path. Apple Silicon (ARM64): a few extensions ship architecture-specific native binaries; verify they have arm64 builds or the language server may fail to start.
Rollback
#!/usr/bin/env bash
set -euo pipefail
git checkout -- .devcontainer/devcontainer.json .vscode/
devcontainer up --workspace-folder . --remove-existing-container