Posted in:

Have you ever been burned by installing a beta or preview version of some developer tools that destabilised your development environment? It's certainly happened to me a few too many times over the years.

But what if you want to try out some of the new cool stuff in the pipeline such as C# 8 and .NET Core 3 (which is still in preview at the time of writing)? Is there any way of trying them out without installing the tools?

(Sidenote: in recent years, preview versions of .NET Core and Visual Studio have been very well behaved and not interfered with the non-preview versions which can be installed side by side. But still I tend to err on the side of caution and avoid installing preview tooling unless I absolutely need it)

Well, thanks to the awesome "Visual Studio Code Remote - Containers" extension, you have a very risk-free way of trying out the latest tooling for your language of preference, without needing anything more than Docker and VS Code installed.

In this post, we'll see how easy it is to set up a .NET Core 3 development environment in a container, and then develop in it using Visual Studio Code.

1. Pre-requisites

To follow along, you need Docker installed (I'm running Docker Desktop on Windows 10), and Visual Studio Code, with the "Visual Studio Code Remote - Containers" extension enabled. I also needed to go into my Docker Desktop settings dialog and enable "Folder Sharing" for my C drive. This is needed as your source code will be mounted as a volume in your container.

2. Create a project folder

Next create an empty folder (I called it core3container) and open Visual Studio Code in that folder (e.g. with code .).

3. Setup container configuration

Next we need to run the "Remote Containers: Add development container configuration files" command in VS Code. You can find this either by pressing F1 and searching for it, or by clicking the green Remote Window icon in the bottom left of VS Code.

This gives us a whole host of pre-defined container images in a variety of languages. I didn't see one for the .NET Core 3 preview in the list, so I just picked the "C# (.NET Core Latest)" option.

4. Open the folder in a container

This prompted me to reopen Visual Studio Code in a container, which I did. You can also do this on demand with the "Remote-Containers: Reopen folder in container" VS Code command.

The first time we do this, it builds the container image for us, which might take a little while as it could need to download base images you don't have.

We can use regular Docker commands such as docker image ls and docker ps to see what's going on behind the scenes. On my machine I can see that there is a new container with the name vsc-core3container-dfa84ec1259930dde9355646f1b8c6d2 running.

5. Examine the .devcontainer folder

Enabling remote container support for VS Code essentially means that a new folder called .devcontainer is created for you. This contains two files - devcontainer.json and Dockerfile.

devcontainer.json holds various configuration settings, such as the location of the Dockerfile, but also any VS Code extensions we want to be enabled when we're working in this container. This is an awesome feature. When you are connected to a container, you can have additional VS Code extensions enabled that just apply to development in that container. In our example, the C# VS Code extension is listed.

{
  "name": "C# (.NET Core Latest)",
  "dockerFile": "Dockerfile",
  "extensions": [
    "ms-vscode.csharp"
  ]
}

This configuration file can also be used for things like publishing ports which is useful if you're doing web development in a container. You can find the full reference documentation for devcontainer.json here.

The Dockerfile that got generated for us began with FROM mcr.microsoft.com/dotnet/core/sdk:latest but then also had some apt-get commands to install a few additional bits of software into the container, such as Git. Here's the Dockerfile that got created:

FROM mcr.microsoft.com/dotnet/core/sdk:3.0

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

# Or your actual UID, GID on Linux if not the default 1000
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Configure apt and install packages
RUN apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    #
    # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed
    && apt-get -y install git procps lsb-release \
    #
    # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user.
    && groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    # [Optional] Uncomment the next three lines to add sudo support
    # && apt-get install -y sudo \
    # && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    # && chmod 0440 /etc/sudoers.d/$USERNAME \
    #
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=

If you want you can update this Dockerfile to install any additional tooling that your development process needs. We'll see how to update the Dockerfile shortly.

The great thing about the .devcontainer folder is that it can be checked into source control so that everyone who clones your repository can use VS Code to develop against it in a dontainer.

6. Run some commands in the container

With VS Code connected to the container, we can run commands directly against the container in the terminal window (accessible with CTRL + '). Let's see what version of .NET Core we have installed, with dotnet --version:

@68cec1b9578c:/workspaces/core3container# dotnet --version
2.2.401

That's not actually what I wanted. I want the preview of .NET Core 3, so I need to point at the correctly tagged version of the .NET SDK.

I can fix this quite easily though. I first run the "Remote-Containers: Reopen Folder Locally" command. Then I edit the Dockerfile to point to the 3.0 SDK base image:

FROM mcr.microsoft.com/dotnet/core/sdk:3.0

Then I run the "Remote-Containers: Rebuild Container" command. This rebuilds the container, and VS Code relaunches inside the new container. If we run dotnet --version again we can see that we now are running the preview version of .NET Core we needed.

root@0c125132ceb3:/workspaces/core3container# dotnet  --version
3.0.100-preview8-013656

7. Try out C# 8 IAsyncEnumerable

Now in the VS Code terminal, we can run dotnet new console to create a new console app, which will generate a .csproj and Program.cs file for us.

And we'll edit Program.cs to make use of the cool new IAsyncEnumerable capabilities of C# 8. You can read more about it in this article by Christan Nagel.

I've updated my Program.cs with a very simple example of how we can use await and yield return to generate an IAsyncEnumerable<string>, and iterate through it with the new await foreach construct.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace core3container
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Starting");
            await foreach(var message in GetMessagesAsync())
            {
                Console.WriteLine(message);
            }
            Console.WriteLine("Finished");
        }

        static async IAsyncEnumerable<string> GetMessagesAsync()
        {
            for (int n = 0; n < 10; n++)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                yield return $"Async message #{n+1}";
            }
        }
    }
}

We can easily check this is working with dotnet run in the terminal in VS Code, which will show a new message appearing every second.

root@0c125132ceb3:/workspaces/core3container# dotnet run
Starting
Async message #1
Async message #2
Async message #3
...
Async message #10
Finished

8. Cleaning up

When you exit Visual Studio Code, it will automatically stop the running container, but it does not delete the container (you can still see it with docker ps -a) or any Docker images. This means that it will be faster to start up next time you do some remote container development on this project.

But you will need to manually remove the containers and images when you're done - there doesn't seem to be any built-in support for cleaning up.

9. Try other stuff

Of course, this feature isn't only for .NET Core development, or just for when you want to use preview SDKs. You can use it to develop in any language and get an extremely consistent development experience across all members of your team without having to tell everyone to install specific versions of development tools.

There's also a really nice collection of quick starts which are GitHub repos you can clone which already have the .devcontainer set up for various languages, including Node, Python, Rust, Go, Java and more. I used it to create my first ever Rust app, which I had up and running in just a couple of minutes and without needing to install any new tools on my development PC, thanks to VS Code remote containers.

Comments

Comment by Alex Dresko

I used remote container development in my latest blog post. It's so crazy cool!

Alex Dresko