Try out .NET 6 inside your own development environment built with devcontainers, docker, and vscode dotfiles deaac devcontainers

Table of Contents

Motivation

Crafting a perfect dev environment takes time and effort. It is a tedious process that you have to do every time you change a working environment. For example, you buy a new laptop, switch to another operating system, or switch to a new project that requires extra tools and configuration. The latter case is interesting to me because I like to try out new things. Varying from different tools, and frameworks to new programming languages.

There are lots of benefits to making a reproducible dev environment. For me the two main ones:

  1. You open a project and it just works. Image some open-source project that interests you but you don’t know how to even run it. With DEaaC the installation problems go away.
  2. You can share it with your peers. A codified developer setup is a form of knowledge that could be evaluated and evolved. It opens up tons of new opportunities.

Anatomy of a dev environment

Basically, you can divide aspects of a dev environment into next categories:

graph LR dev[DevEnv] dev-->personal[Personal] personal --> pconfig[Configuration] pconfig --> shellconfig[Shell: aliases, prompts,...] pconfig --> editorconfig[Editor: extensions, customizations] pconfig --> gitconfig[Git] personal-->ptools[Tools] ptools -->pcli[command-line tools] ptools -->pgui[Graphical User Interface] dev-->project[Project] project-->pjconfig[Configuration] project-->projecttools[Tools] pjconfig -.-> pjshellconfig[Shell: aliases, prompts,...] pjconfig --> pjeditorconfig[Editor: extensions, customizations] pjconfig -.-> pjgitconfig[Git] projecttools --> pjcli[command-line tools] projecttools-->pjgui[Graphical User Interface] style pjshellconfig stroke:grey,stroke-width:2px,stroke-dasharray: 5 5 style pjgitconfig stroke:grey,stroke-width:2px,stroke-dasharray: 5 5

It might seem like a lot but luckily we have good tools to automate the scaffolding process. Namely dotfiles and devcontainers.

Dotfiles

Dotfiles are focused on personal configuration aspects and are usually maintained by a person. Dotfiles are tailored to make up a unique developer experience. Previously, I wrote about it here: Development environment based on Windows Terminal + WSL + ZSH + dotfiles

Usually, dotfiles are comprised of common configurations and utilities that could be used on various different platforms and platform-specific configurations. For example, you use a specific package manager innate into the system to install dependencies.

Here is an example of how I do it: https://github.com/NikiforovAll/dotfiles. As you can see, there are three different setups for windows, wsl, and dev-containers.

As result, we can create a dev environment of choice from a scratch. All you need to do is to run one-liner:

bash -c "$(wget -qO - https://raw.github.com/nikiforovall/dotfiles/master/src/wsl/os/install.sh)"

An approach like this does work. But it is annoying and impractical. We can do better about it. I will show you in a moment.

DevContainers

From the official documentation:

The Visual Studio Code Remote - Containers extension lets you use a Docker container as a full-featured development environment. It allows you to open any folder or repository inside a container and take advantage of Visual Studio Code’s full feature set. 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.

A devcontainer.json file in your project tells Visual Studio Code (and other services and tools that support the format) how to access (or create) a development container with a well-defined tool and runtime stack. It’s currently supported by the Remote - Containers extension and GitHub Codespaces.

remote-arch

DevContainers are meant to be reusable and extendable. From my experience, they have improved in this regard. Here is how you can add dependencies via .devconatiner.json

{
    "name": "Azure Dev CLI",
    "build": {
        "dockerfile": "Dockerfile", // base docker image
        "args": {
            "VARIANT": "bullseye" // parametrization
        }
    },
    "features": {
        "github-cli": "2",
        "azure-cli": "2.38",
        "dotnet": "6.0",
        "docker-from-docker": "20.10",
    },
    "extensions": [
        "ms-azuretools.azure-dev",
        "ms-azuretools.vscode-bicep",
        "ms-azuretools.vscode-docker",
        "ms-azuretools.vscode-azurefunctions",
        "ms-dotnettools.csharp",
        "ms-dotnettools.vscode-dotnet-runtime",
    ],
}

💡DevContainers define project-specific configurations and dotfiles define user-specific configurations.

A better way to do dotfiles - Dotbot

💭 My initial version of dotfiles was intended for wsl & early devcontainers version. Since then, I’ve learned a couple of lessons. Mainly, it is a good idea to keep dotfiles simple and rely on essentials. You don’t need to bring tons of dependencies and convoluted configuration files. The simpler - the easier to maintain and synchronize.

So, I’ve created another dotfiles repo: https://github.com/NikiforovAll/dotbot. This one is based on https://github.com/anishathalye/dotbot. Dotbot is a tool that bootstraps your dotfiles. It is straightforward and easy to use. I suggest you to give a try on your own.

As discussed, since I deliberately choose to not have heavy dependencies it takes a couple a seconds to install dotfiles. Exactly as I want it to be. My general suggestion is to move the heavy lifting of dependencies installation to DevContainers.

One-liner: cd ~ && git clone https://github.com/NikiforovAll/dotbot && cd ./dotbot && cd ./install

Here is how the project structure looks like:

# oleksii_nikiforov in ~/dotbot
> tree -L 2
.
├── bash
│   ├── plugins.bash
│   └── prompt.bash
├── bash_profile
├── bashrc
├── gitconfig
├── gitignore_global
├── install
├── install.conf.yaml
├── pre-install.sh
├── shell
│   ├── aliases.sh
│   ├── bootstrap.sh
│   ├── dircolors.extra
│   ├── external.sh
│   ├── functions.sh
│   └── plugins
├── zsh
│   ├── plugins
│   ├── plugins_after.zsh
│   ├── plugins_before.zsh
│   ├── prompt.zsh
│   └── settings.zsh
└── zshrc

Combining everything together

The resulting dev environment is combined from diverse sources. Roughly speaking a host (developer machine/codespace) is composed of the next pieces:

flowchart subgraph host[ ] direction LR subgraph dotfiles[Dotfiles] direction LR shellconfig[Shell] shellconfig-->aliases shellconfig-->prompts shellconfig-->scripts shellconfig-->tools editorconfig[VSCode] editorconfig-->extensions editorconfig-->customizations[user settings] gitconfig[Git]-->preferences gitconfig[Git]-->galiases[aliases] end subgraph devconataners[DevContainers] direction LR projecttools[Tools] projecttools-->dockerimage[dockerfile] projecttools-->devcontainerfeatures[devcontainer.json features] projecttools-->postcreatecommand[postCreateCommand] pjeditorconfig[VSCode] pjeditorconfig-->pjextensions[extensions] pjeditorconfig-->pjcustomization[workspaces settings] end tools-.->projecttools extensions-.->pjextensions customizations-.->pjcustomization vscodesettingssync(VSCode settings Sync)-->editorconfig gitcredentials(Git Credentials)-->gitconfig end style host fill:#e6eced,stroke:grey,stroke-width:2px,stroke-dasharray: 5 5 style dotfiles fill:#def4ea style devconataners fill:#dee9f4

Pick a host. GitHub Codespaces

The suggested approach abstracts away the host. It doesn’t necessarily has to be your own machine. This is what “GitHub Codespaces” product is about.

A codespace is a development environment that’s hosted in the cloud. You can customize your project for GitHub Codespaces by committing configuration files to your repository (often known as Configuration-as-Code), which creates a repeatable codespace configuration for all users of your project.

GitHub Codespaces run on a variety of VM-based compute options hosted by GitHub.com, which you can configure from 2 core machines up to 32 core machines. You can connect to your codespaces from the browser or locally using Visual Studio Code.

codespaces-diagram

Example

We will examine Azure Developer CLI (azd) template - todo-csharp-cosmos-sql. Here is a repo that will be used as an example: https://github.com/NikiforovAll/todo-csharp-cosmos-sql.

The end goal is to setup a full-featured dev environment and measure how much time it takes.

This application utilizes the following Azure resources:

  • Azure App Services to host the Web frontend and API backend
  • Azure Cosmos DB SQL API for storage
  • Azure Monitor for monitoring and logging
  • Azure Key Vault for securing secrets

The demo combines techniquest from both Infrastructure as a Code (IaaC) and Dev Environment as a Code (DEaaC). It takes only 15 stagering minutes from zero to deployed azure resources on the first launch.

1️⃣ Configure dotfiles in “Settings>Codespaces”:

configure-dotfiles

2️⃣ Go to repository and launch a codespace:

launch-codespace

3️⃣ Open the codespace in the browser or locally (I will go with browser option for the demo)

codespace-welcome

4️⃣ Login to Azure via CLI or VSCode extension:

az login --use-device-code

5️⃣ Run azd up

azd-up

6️⃣ Setup the breakpoint

debug-in-codespace

7️⃣ Inspect forwarded ports. Note, codespaces allocates some random URL

port-forwarding-panel

8️⃣ Generate load

forwarded-port

9️⃣ Inspect logs by running azd monitor --logs

🔟 Tear down the dev environment in Azure azd down

azd-down

As result we have:

  • Deployed whole developer setup in 15 minutes
  • Terminal/Shell is configured
  • Git is configured
  • All project tooling and dependencies are installed: dotnet, az, etc.
  • VSCode contains user extensions as well as suggested by devcontainer
  • A one happy developer 😉

Summary

DEaaS has lots of advantages. Most of the time it just works. But it definitely required investing some time to learn about the moving parts to know how to maintain and troubleshoot the setup. I have not shown you the dark side of using DevContainers but I can assure you that it could be hard to understand when something goes wrong. Despite all challenges, it opens new horizons and it is a future of large-scale and open-source development.

Reference


Oleksii Nikiforov

Jibber-jabbering about programming and IT.