📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

Your team uses Visual Studio, VS Code and Rider across Windows, macOS and Linux. How do you standardise the developer environment so builds and behaviour match CI?

A senior plan for a reproducible multi-IDE, multi-OS .NET 10 environment: CLI and CI as the source of truth, global.json, .editorconfig, Directory.Build.props, shared IDE files, Dev Containers and secrets hygiene.

Senior RealWorldScenario Advanced dev-containers developer-experience editorconfig global-json onboarding

🎯 Key points

  • Make the .NET CLI and CI the source of truth; IDEs are clients of the SDK
  • Pin the SDK with global.json and an explicit rollForward policy
  • Share formatting and analyser severities in .editorconfig, enforced in CI
  • Centralise build settings in Directory.Build.props and package versions with Central Package Management
  • Commit shared IDE files (.vsconfig, .vscode/extensions.json, launchSettings.json) and ignore per-user state
  • Offer a Dev Container or setup script for reproducible onboarding
  • Keep secrets out of the repo: user secrets locally, Azure Key Vault and managed identity in Azure
  • Measure it: time to first successful build for a new starter and environment-related incidents

⏱ 30-second answer

I standardise below the IDE rather than forcing one IDE. The CLI and CI are the source of truth. I pin the SDK with global.json, put style and analyser severities in .editorconfig, centralise build properties in Directory.Build.props and versions with Central Package Management, and commit shared IDE files such as .vsconfig, .vscode/extensions.json and launchSettings.json while ignoring personal state. I offer a Dev Container or a setup script for onboarding, keep secrets in user secrets and Key Vault, and CI runs the same dotnet format, build and test commands developers run locally.

🗣 Full answer (senior)

How I would answer this in an interview

"I standardise below the IDE rather than mandating one, because the CLI and CI are the source of truth and every IDE is just a client of the same SDK; forcing one IDE is the mistake I see, and it still doesn't guarantee identical builds. So I pin the SDK with global.json and an explicit rollForward policy, put style and analyser severities in .editorconfig enforced in CI, centralise build properties in Directory.Build.props and versions with Central Package Management behind a trusted-source nuget.config, and commit shared IDE files such as .vsconfig, .vscode/extensions.json and launchSettings.json while ignoring personal state. Onboarding gets a Dev Container or a setup script, secrets go to user secrets locally and Key Vault with managed identity in Azure, and CI runs the same build script developers run.

For example, on [our team of twelve], onboarding took [about two days] and builds regularly passed locally but failed in CI. I took ownership of making the environment reproducible without forcing anyone to change IDE. Comparing dotnet --info across machines, I found [four] different SDKs including a preview, so I pinned the SDK, moved style into .editorconfig, centralised build settings and package versions, moved local secrets into user secrets, and added a Dev Container plus a single build.sh used by both developers and CI. The next new starter reached a green test run in [about an hour], and local-versus-CI failures dropped to [your number].

I measure it with time to first green build and environment-related incidents, roll it out incrementally starting with global.json and .editorconfig, and put SDK bumps on a schedule, because pinning means someone owns the upgrades."


Principle

Editors differ; the SDK does not. If every developer and CI agent runs the same SDK against the same project files with the same rules, the IDE becomes a personal preference. So I standardise the layers underneath.

The plan

  1. SDK: global.json at the repo root pins 10.0.100 with rollForward: latestFeature, so everyone gets the newest installed .NET 10 SDK feature band and patch, with its security fixes. I also set allowPrerelease: false explicitly, because when it isn't set the command line allows preview SDKs, so a machine with a .NET 11 preview installed could otherwise pick it.
  2. Build settings: Directory.Build.props sets Nullable, ImplicitUsings, TreatWarningsAsErrors, EnforceCodeStyleInBuild and AnalysisLevel once for every project.
  3. Packages: Directory.Packages.props with Central Package Management, plus nuget.config with <clear /> and only trusted sources, so restores are identical and protected from dependency confusion.
  4. Style: .editorconfig with root = true; CI runs dotnet format --verify-no-changes.
  5. Shared IDE files: .vsconfig for Visual Studio workloads, .vscode/extensions.json, Properties/launchSettings.json profiles that every IDE and dotnet run understand. Ignore .vs/, .idea/ workspace files and *.user.
  6. Onboarding: a .devcontainer/devcontainer.json (VS Code and Rider can open it) and a short setup script for Visual Studio users that installs the SDK and applies .vsconfig.
  7. Secrets: no secrets in launch profiles or appsettings.Development.json; local values in dotnet user-secrets, Azure values in Key Vault accessed with managed identity.
  8. CI parity: the pipeline runs exactly dotnet restore, dotnet format --verify-no-changes, dotnet build -c Release, dotnet test. Developers can run the same script locally.
  9. Upgrades and previews: .NET 10 is the LTS baseline. .NET 11 is in preview, so it is evaluated deliberately: a spike branch with its own global.json (with allowPrerelease: true) or a separate Dev Container image builds and tests against the preview SDK, and the main branch moves only when the team decides to adopt the next release. Routine SDK patch bumps happen on a schedule.

Measuring success

Track time to first successful build and test for a new starter, and the count of environment-related support requests. Both are easy to measure and show whether the investment worked.

Production example

On a team of twelve building services hosted in Azure Australia East, onboarding took about two days and builds sometimes passed locally but failed in CI. After the changes above, a new starter on macOS cloned the repo, opened the Dev Container and ran the tests within an hour, and local-versus-CI failures became rare enough that each one was treated as a bug.

🔍 What the interviewer is checking

The interviewer is assessing whether you can lead technical standards for a team without turning them into IDE wars, and whether you think about reproducibility, security and measurement.

Green flags

  • "Standardise below the IDE" as the principle.
  • Names concrete files and what each controls.
  • Includes secrets, package source trust and CI parity.
  • Proposes a way to measure success and an incremental rollout.

Red flags

  • "Everyone must use Visual Studio."
  • Only talks about IDE settings.
  • No mention of CI or of how to keep the pinned SDK current.

📚 Learn in depth

1. What lives where

repo/
+-- global.json                 <- which SDK (all IDEs + CLI + CI)
+-- nuget.config                <- which package sources (trusted only)
+-- Directory.Build.props       <- shared MSBuild properties
+-- Directory.Packages.props    <- central package versions
+-- .editorconfig               <- style + analyser severities
+-- .vsconfig                   <- Visual Studio workloads
+-- .vscode/extensions.json     <- VS Code recommendations
+-- .devcontainer/devcontainer.json
+-- .gitignore                  <- excludes .vs/, .idea workspace, *.user, bin/, obj/
+-- build.sh                    <- the exact commands CI runs
+-- src/Orders.Api/Properties/launchSettings.json  <- run profiles for every IDE

            developer IDE --+
                            +--> same SDK + same props + same rules --> same output
              CI pipeline --+

2. Which file controls which kind of drift

Drift Symptom File that fixes it
Different SDKs New warnings on one machine, "works on mine" global.json
Different build settings Nullable warnings only in some projects Directory.Build.props
Different package versions Runtime MissingMethodException, restore differences Directory.Packages.props (+ lock files)
Different package sources Restore works locally, fails in CI; dependency confusion nuget.config with <clear /> and source mapping
Different style Formatting churn in reviews .editorconfig + dotnet format in CI
Different IDE components Missing templates or designers .vsconfig, .vscode/extensions.json
Different OS toolchains Linux-only bugs Dev Container matching the CI image
Different commands CI fails on things local builds skip One build.sh used by both

3. SDK pinning details

  • rollForward: latestFeature keeps everyone on .NET 10 while taking the newest feature band and patch installed. Stricter teams use latestPatch and bump the band deliberately.
  • Set allowPrerelease: false explicitly. When it isn't set, the command line allows preview SDKs (Visual Studio follows its own preview setting), so an agent with a preview installed could pick it.
  • CI installs from the file (actions/setup-dotnet with global-json-file, or UseDotNet@2 with useGlobalJson: true) and prints dotnet --info first, so the SDK in use is visible in every log.

4. Deterministic builds

  • Same SDK, same package versions (lock files with RestorePackagesWithLockFile and --locked-mode in CI if you need stronger guarantees), same compiler options.
  • CI sets ContinuousIntegrationBuild=true so outputs are deterministic and don't embed local paths.
  • Build once, then test and publish the same artefacts (--no-build, --no-restore).

5. Package source security

nuget.config with <clear /> removes machine-level sources nobody reviewed. Package source mapping pins each package id pattern to one source, so an attacker can't publish a same-named package on nuget.org to replace an internal one (dependency confusion):

<packageSourceMapping>
  <packageSource key="nuget.org"><package pattern="*" /></packageSource>
  <packageSource key="internal"><package pattern="Contoso.*" /></packageSource>
</packageSourceMapping>

6. Dev Containers trade-offs

Benefit Cost
Same Linux toolchain as CI and production containers Windows desktop workloads (WPF, WinForms) can't run in them
Dependencies (SQL Server, Azurite) as sibling containers Bind mounts can be slow on macOS and Windows for large repos
Onboarding in minutes Visual Studio doesn't open them
Needs Docker Desktop (paid for larger companies) or an alternative, and images must be kept patched

So Windows Visual Studio users still need the setup-script path.

7. Rolling it out without disruption

  1. global.json and .editorconfig first: cheap, high impact.
  2. One reformatting commit, excluded from blame.
  3. Directory.Build.props and Central Package Management.
  4. nuget.config hardening.
  5. Dev Container and setup script.
  6. A shared build.sh adopted by CI.

Measure time to first green build and environment-related CI failures before and after.

8. Keeping it healthy

  • A monthly SDK bump (after Microsoft's Patch Tuesday release) in one pull request that also updates CI images and the Dev Container.
  • Changes to shared build files reviewed by an owner (CODEOWNERS).
  • An onboarding script that prints dotnet --info and checks IDE versions.

9. .NET 11 awareness

To try .NET 11 previews, use a branch with its own global.json (allowPrerelease: true) or a separate container image, never a developer's global install, so the main branch stays on .NET 10 LTS.

10. Try it yourself

  1. Add the global.json, Directory.Build.props and .devcontainer/devcontainer.json from the code example to a small solution.
  2. Open it in VS Code with Dev Containers: Reopen in Container and run dotnet test.
  3. Open the same repo on the host in Visual Studio or Rider and confirm the build is identical.
  4. Install a preview SDK, remove allowPrerelease from global.json, and run dotnet --version to see the preview get picked; put the setting back.
  5. Time how long a fresh clone takes to reach a green test run.

💻 Code

global.json:

{
  "sdk": {
    "version": "10.0.100",
    "rollForward": "latestFeature",
    "allowPrerelease": false
  }
}

Directory.Build.props:

<Project>
  <PropertyGroup>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <AnalysisLevel>latest-recommended</AnalysisLevel>
  </PropertyGroup>
</Project>

nuget.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>

.devcontainer/devcontainer.json:

{
  "name": "orders-service",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/dotnet:2": { "version": "10.0" }
  },
  "customizations": {
    "vscode": { "extensions": ["ms-dotnettools.csdevkit"] }
  },
  "postCreateCommand": "dotnet restore"
}

The same commands locally and in CI (build.sh):

#!/usr/bin/env bash
set -euo pipefail
dotnet restore
dotnet format --verify-no-changes --no-restore
dotnet build -c Release --no-restore
dotnet test -c Release --no-build

⚠ Common mistakes

  • Mistake: mandating a single IDE. It costs productivity for people fastest in another tool, and doesn't guarantee identical builds anyway. Instead: standardise the SDK, build settings, package versions and rules underneath, and let people choose their editor.

  • Mistake: no global.json. Each machine and agent uses whatever SDK is newest, including a .NET 11 preview someone installed to experiment. Instead: commit global.json with a version, a rollForward policy and allowPrerelease: false.

  • Mistake: leaving allowPrerelease unset. Unset, the command line allows preview SDKs, so an agent with a preview installed picks it while Visual Studio may not. Instead: set "allowPrerelease": false explicitly on main branches.

  • Mistake: choosing a roll-forward policy without thinking about it. latestFeature lets machines use different feature bands (10.0.1xx and 10.0.2xx), which can bring analyser or MSBuild changes. Instead: choose deliberately: latestFeature for most teams, latestPatch for stricter ones, and confirm CI and developers match by printing dotnet --version in the pipeline log.

  • Mistake: pinning the SDK and never updating it. The team misses security patches and falls behind on fixes. Instead: bump it on a schedule (for example monthly after Patch Tuesday) in one pull request that also updates CI images and the Dev Container.

  • Mistake: trying previews on the main branch. Preview SDKs bring new warnings and behaviour changes to everyone at once. Instead: evaluate previews on a spike branch with its own global.json, or in a separate container image.

  • Mistake: CI that doesn't fail on test failures. continueOnError on the test step, or a script without set -e, lets broken builds go green. Instead: let dotnet test's non-zero exit code fail the run: use set -euo pipefail in scripts and no continueOnError on the test step.

  • Mistake: committing personal IDE state (.vs/, .idea/workspace.xml, *.user). It causes constant conflicts and leaks local paths. Instead: ignore those files and commit only shared ones such as .vsconfig, .vscode/extensions.json and the team-shared .DotSettings.

  • Mistake: different commands in CI and locally. For example, CI builds Release with warnings as errors while developers build Debug without, so problems appear only in CI. Instead: put the exact commands in one build.sh that both CI and developers run.

  • Mistake: secrets in launchSettings.json or appsettings.Development.json "because it is only local". Both are committed, so every clone and fork holds the secret. Instead: use dotnet user-secrets locally and Azure Key Vault with managed identity in Azure.

  • Mistake: treating containers as a universal answer. Windows desktop workloads can't run in them, Visual Studio doesn't open them, and bind mounts can be slow. Instead: offer a Dev Container for macOS and Linux users and container-friendly projects, plus a setup script for Visual Studio users.

🏛 Senior insight: trade-offs

This is a developer-experience investment with measurable return. A senior engineer frames it in numbers (onboarding time, CI failures caused by environment drift, review time lost to style) and rolls it out incrementally: global.json and .editorconfig first because they are cheap and high impact, then shared props and Central Package Management, then Dev Containers. The trade-off to name is flexibility versus control: pinning the SDK means someone owns regular upgrades, so put SDK bumps on a schedule (for example monthly with Patch Tuesday) rather than letting the pin go stale.

At Staff level the same files become governance across many repos, published from a template repository or shared build package.

🔗 Follow-ups the interviewer may ask

  1. When: The candidate mentions committing a .vsconfig. Why: Checks the Visual Studio-specific part of the standard setup.
    How to answer

    For .NET 10 you need Visual Studio 2026. The edition depends on licensing and features: Community is free for individuals, open source and small organisations; Professional is the normal paid edition; Enterprise adds tools like IntelliTrace and Live Unit Testing. You install workloads such as ASP.NET and web development. To keep the team consistent, export a .vsconfig from the Visual Studio Installer and commit it at the repo root: Visual Studio then prompts anyone missing a component to install it, and the installer can apply the same file on build agents.

  2. When: The candidate proposes pinning the SDK with global.json. Why: Checks how SDK resolution and roll-forward actually work.
    How to answer

    The dotnet host looks for a global.json from the current folder upwards. If there isn't one, it uses the latest installed SDK, even a preview. If there is one, it reads sdk.version and applies the rollForward policy to choose from the installed SDKs. With a version and no policy, the default is latestPatch, meaning the same feature band with the newest patch. allowPrerelease controls whether previews count. If nothing matches, every SDK command fails. I pin the SDK in every repo so developers, IDEs and CI build with the same compiler, analysers and defaults. dotnet --info shows which SDK was picked and why.

  3. When: The candidate mentions centralising build settings in Directory.Build.props. Why: Checks how shared MSBuild files are imported and what belongs in each.
    How to answer

    Directory.Build.props and Directory.Build.targets are ordinary MSBuild files that the SDK imports automatically into every project below them. For each project, MSBuild searches upwards and uses the nearest one it finds. .props is imported early, before the project's own content, so it's for default properties and items: target framework, Nullable, analysers, package metadata. Projects can still override those values. .targets is imported late, after the project, so it's for targets and logic that need final values. Only the nearest file is picked up. To layer them, the inner file imports the outer one with GetPathOfFileAbove. I debug them with dotnet msbuild -preprocess or a binary log.

🛠 Real-world problems: how you handle them

Two-day onboarding and local-versus-CI drift on a mixed-IDE team
Situation
A team of twelve building .NET services hosted in Azure Australia East used Visual Studio on Windows, Rider on macOS and VS Code on Linux. New starters took about two days to get a green build, and roughly one pull request a week failed CI after passing locally.
Signals
Onboarding support requests in the team channel, CI failures for analyser warnings that developers never saw, restore failures from different package versions, and style churn in reviews.
Investigation
Compared `dotnet --info` across machines and CI agents: four different SDK versions, including a preview. Found no `.editorconfig`, build properties copy-pasted inconsistently across 30 projects, `Version` attributes drifting between projects, and CI building Release with warnings as errors while developers built Debug without.
Resolution
Added `global.json`, `.editorconfig`, `Directory.Build.props`, Central Package Management and a trusted-source `nuget.config`. Committed `.vsconfig`, `.vscode/extensions.json` and cleaned launch profiles; moved local secrets to user secrets. Added a Dev Container for macOS and Linux users and a setup script for Visual Studio users. CI and developers both used a single `build.sh`.
Prevention
Tracked time to first green build for each new starter, added a monthly SDK bump task, and required changes to shared build files to go through review by the platform group.

How to tell it (STAR)

**Situation:** On [our team of twelve], onboarding took about two days and we had regular builds that passed locally but failed in CI because everyone's environment was slightly different. **Task:** I took ownership of making the environment reproducible without forcing anyone to change IDE. **Action:** I pinned the SDK with `global.json`, moved style into `.editorconfig`, centralised build settings and package versions, cleaned up shared IDE files and secrets, and added a Dev Container plus a single build script used by both developers and CI. **Result:** The next new starter reached a green test run in [about an hour], and local-versus-CI failures dropped to [your number].

↩ Usually asked after

More in IDEs and Editors

Last updated 1 Nov 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me