Anatomy of a .NET devcontainer
| Comments- | Posted in
- dotnet
- codespaces
- github
Unbelievable Instant .NET Development Setup. It is available when you visit https://github.com/codespaces and you can start using it immediately.
Codespaces are built off of the devcontainer mechanism, which allows you to define the environment in a bunch of different ways using container images or just a devcontainer image. I won’t go through all the options you can do with devcontainers, but will share the anatomy of this and what I like about it.
NOTE: If you don’t know what Development Containers are, you can read about them here https://containers.dev/
Throughout this post I’ll be referring to snippets of the definition but you can find the FULL definition here: github/dotnet-codespaces.
Base Image
Let’s start with the base image. This is the starting point of the devcontainer, the OS, and pre-configurations built-in, etc. You can use a Dockerfile definition or a pre-defined container image. I think if you have everything bundled nicely in an existing container image in a registry, start there. Just so happens, .NET does this and has nice images with the SDK already in them, so let’s use that!
{ "name": ".NET in Codespaces", "image": "mcr.microsoft.com/dotnet/sdk:8.0", ... }
This uses the definition from our own container images defined here: https://hub.docker.com/_/microsoft-dotnet-sdk/. Again this allows us a great/simple starting point.
Features
In the devcontainer world you can define ‘features’ which are like little extensions someone else has done to make it easy to add/inject into the base image. One aspect of adding things can be done through pre/post scripts, but if someone has created a ‘feature’ in the devcontainer world, this makes it super easy as you delegate that setup to this feature owner. For this image we’ve added a few features:
{ "name": ".NET in Codespaces", "image": "mcr.microsoft.com/dotnet/sdk:8.0", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/github-cli:1": { "version": "2" }, "ghcr.io/devcontainers/features/powershell:1": { "version": "latest" }, "ghcr.io/azure/azure-dev/azd:0": { "version": "latest" }, "ghcr.io/devcontainers/features/common-utils:2": {}, "ghcr.io/devcontainers/features/dotnet:2": { "version": "none", "dotnetRuntimeVersions": "7.0", "aspNetCoreRuntimeVersions": "7.0" } }, ... }
So here we see that the following are added:
- Docker in docker – helps us use other docker-based features
- GitHub CLI – why not, you’re using GitHub so this adds some quick CLI-based commands
- PowerShell – an alternate shell that .NET developers love
- AZD – the Azure Developer CLI which helps with quick configuration and deployment to Azure
- Common Utilities – check out the definition for more info here
- .NET features – even though we are using a base image, in this case .NET 8, there may be additional runtimes we need so we can use this to bring in more. In this case this is needed for one of our extensions customizations that need the .NET 7 runtime.
This enables the base image to append additional functionality when this devcontainer is used.
Extras
You can configure more extras through a few more properties like customizations (for environments) and pre/post commands.
Customizations
The most common used configuration of this section is to bring in extensions for VS Code. Since Codespaces default uses VS Code, this is helpful and also carries forward if you use VS Code locally with devcontainers (which you can do!).
{ "name": ".NET in Codespaces", ... "customizations": { "vscode": { "extensions": [ "ms-vscode.vscode-node-azure-pack", "github.vscode-github-actions", "GitHub.copilot", "GitHub.vscode-github-actions", "ms-dotnettools.vscode-dotnet-runtime", "ms-dotnettools.csdevkit", "ms-dotnetools.csharp" ] } }, ... }
In this snippet we see that some VS Code definitions will be installed for us to get started quickly:
- Azure Extensions – a set of Azure extensions to help you quickly work with Azure when ready
- GitHub Actions – view your repo’s CI/CD activity
- Copilot – AI-assisted code development
- .NET Runtime – this helps with any runtime acquisitions needed by activity or other extensions
- C#/C# Dev Kit – extensions for C# development to make you more productive in the editor
It’s a great way to configure your dev environment to be ready to start when you use devcontainers without spending time hunting down extensions again.
Commands
Additionally you can do some post-create commands that may be used to warm-up environments, etc. An example here:
{ "name": ".NET in Codespaces", ... "forwardPorts": [ 8080, 8081 ], "postCreateCommand": "cd ./SampleApp && dotnet restore", ... }
This is used to get the sample source ready to use immediately by restoring dependencies or other commands, in this case running the restore command on the sample app.
Summary
I am loving devcontainers. Every time I work on a new repository or anything I’m now looking first for a devcontainer to help me quickly get started. For example, I recently explored a Go app/repo and don’t have any of the Go dev tools on my local machine and it didn’t matter. The presence of a devcontainer allowed me to immediately get started with the repo with the dependencies and tools and let me get comfortable. And portable as I can navigate from machine-to-machine with Codespaces and have the same setup needed by using devcontainers!
Hope this little insight helps. Check out devcontainers and if you are a repo owner, please add one to your Open Source project if possible!
Please enjoy some of these other recent posts...
Comments