Dev Environment as a Code (DEaaC) with DevContainers, Dotfiles, and GitHub Codespaces devcontainers dotfiles deaac
Development environment based on Windows Terminal + WSL + ZSH + dotfiles git dotfiles wsl deaac

TL;DR

In this blog post, I provide an example of how to organize/dockerize your personal development setup. Also, you may want to use one of my devcontainers to try .NET 6. Everything is baked inside the devcontainer, so you don’t have to install SDK and other tooling on your host machine.


Introduction

As you may know, .NET 6 Release Candidate 2 has been announced and it is very close to the final build of .NET 6 that will be shipped in November this year in time for .NET Conf 2021. It is the perfect time to start playing with .NET 6 and C# 10 and all of its goodness.

This is why I’ve created sandbox https://github.com/NikiforovAll/devcontainer-for-dotnet6-demo. This is dotnet new webapi -minimal packed inside devcontainer. So, all you need to do is to get the source code and compile the devcontainer. You can do it by downloading via VSCode URL launcher or clone via git. I suggest you go straight to documentation for more details. My goal is to share my experience using devcontainers.

Here is how I do it:

git clone https://github.com/NikiforovAll/devcontainer-for-dotnet6-demo

devcontainer open ./devcontainer-for-dotnet6-demo

💡 Tip: You can open repository in VSCode code ./devcontainer-for-dotnet6-demo and after that, you will be prompted with a suggestion to reopen workspace in devcontainer (https://code.visualstudio.com/docs/remote/devcontainer-cli#_opening-a-folder-directly-within-a-dev-container).

🚀 Run dotnet run --project ./src/App/ and enjoy your coding.

Anatomy of devcontainers

When you generate configuration from VSCode, by default, it generates something like this:

$ tree -a 
.
└── .devcontainer
    ├── devcontainer.json
    ├── Dockerfile
    └── library-scripts
        └── azcli-debian.sh
# Dockerfile

ARG VARIANT="5.0"
FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT}
// .devcontainer/devcontainer.json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/dotnet
{
    "name": "",
    "build": {
        "dockerfile": "Dockerfile",
        "args": { 
            // Update 'VARIANT' to pick a .NET Core version: 2.1, 3.1, 5.0
            "VARIANT": "5.0"
        }
    },
    // Set *default* container specific settings.json values on container create.
    "settings": {},
    // Add the IDs of extensions you want installed when the container is created.
    "extensions": [
    ],
    "remoteUser": "vscode"
}

A devcontainer.json file in your project tells VS Code how to access (or create) a development container with a well-defined tool and runtime stack. This container can be used to run an application or to separate tools, libraries, or runtimes needed for working with a codebase.

Everything you need is already added in the base docker image, but it is quite easy to extend it. You may want to pre-install some dependencies and tools in a custom Dockerfile.

💡Tip: You are not limited to using devcontainers for .NET 6 project, actually, you can use them pretty much for everything. For example, I’m writing this blog post from devcontainer 🙂.

Distribute devcontainers

Personally, I think it should be possible to easily explore the content of the devcontainer in order to change it on demand. This is why my go-to option is to create custom-tailored containers for each project based on some lightweight base images with shared tooling installed in them. For example, you might want to install something like: https://github.com/rothgar/awesome-tuis, https://github.com/unixorn/git-extra-commands, https://github.com/junegunn/fzf, https://github.com/sharkdp/fd, https://github.com/ogham/exa, etc. 🤓

Here you can find my devcontainer for .NET:

After that, you can install it directly from docker container registry by specifying “image” field inside devcontainer.json.

Minimum devcontainer looks like this:

{
    "name": ".NET 6 devcontainer",
    "image": "nikiforovall/devcontainers-dotnet:latest",
    "settings": {},
    "extensions": []
}

Dotfiles

You can also include your dotfiles repository to replicate your terminal experience (configurations, aliases, customizations, tools, etc.). See user settings for the “Remote - Containers” extension.

Basically, you want to configure the remote git repository as the source of dotfiles and tell vscode what to do upon installation. Here is my dotfile for devcontainers: https://github.com/NikiforovAll/dotfiles/blob/master/src/dev-container/boot/install.sh.

"settings": {
    "dotfiles.installCommand": "",
    "remote.containers.dotfiles.repository": "https://github.com/NikiforovAll/dotfiles.git",
    "remote.containers.dotfiles.installCommand": "~/dotfiles/src/dev-container/boot/install.sh",
    "remote.containers.dotfiles.targetPath": "~/dotfiles",
},

Anatomy - Summary

Name Description Responsibility
.devconatiner/devcontainer.json Workspace definition. Specify how to assemble devcontainer. Customize IDE (vscode) behavior.
.devconatiner/Dockerfile Image for docker container. Install dependencies and tools. Configure defaults.
dotfiles External versioned source of configuration for developer environment. Customize the way your terminal looks and feels and developer experience in general.

Summary

I’ve explained to you the main building blocks of devcontainers and shared some ideas regarding how you may want to organize your development setup. Hope you find it useful.


Reference


Oleksii Nikiforov

Jibber-jabbering about programming and IT.