DEV Community

Aaron Powell for Microsoft Azure

Posted on • Originally published at aaron-powell.com on

Debugging your .NET Core in Docker applications with VS Code

One of the nicest things about building applications of .NET Core is that its cross-platform support means that we can deploy our application as a Docker container. If you’re using Visual Studio it has built in support for Docker but that’s not going to work if you’re on Mac or Linux, of if like me, you prefer to use VS Code as your editor.

So if you create your Dockerfile for .NET it looks something like this:

FROM mcr.microsoft.com/dotnet/core/sdk:2.2
WORKDIR /app
COPY ./bin/Debug/netcoreapp2.2/publish .
ENTRYPOINT ["dotnet", "MyApplication.dll"]

Enter fullscreen mode Exit fullscreen mode

Great! We can run our application now by building that image and starting the container, but what happens if we want to debug it?

Enabling remote debugging

If you think about it logically, when running an application in Docker it’s essentially being run remotely. Sure, it might be remotely on the same machine, but it’s still “remote”, and this is how we need to think about debugging!

To do this we’ll need to install MIEngine into our Docker image as it’s being built, and to do that we’ll add a new layer into our Dockerfile:

FROM mcr.microsoft.com/dotnet/core/sdk:2.2
RUN apt update && \
    apt install unzip && \
    curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l /vsdbg
WORKDIR /app
COPY ./bin/Debug/netcoreapp2.2/publish .
ENTRYPOINT ["dotnet", "MyApplication.dll"]

Enter fullscreen mode Exit fullscreen mode

The new RUN layer will first update apt to get all the latest package references, then install unzip and finally execute curl which pipes to /bin/sh. It might seem a bit confusing, but that’s because we’re chaining three commands together into a single layer to reduce the size of our Docker image. Really the most important part is this line:

curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l /vsdbg

Enter fullscreen mode Exit fullscreen mode

This downloads a sh script from https://aka.ms/getvsdbgsh and pipes it straight to /bin/sh for execution and provides a few arguments, most importantly the /vsdbg which is where the remote debugger will be extracted to.

Now our image has the debugger installed into it we need to setup VS Code to attach to it.

Attaching VS Code to a remote debugger

We’re going to add a new entry to our launch.json file that is of "type": "coreclr" and "request": "attach". This will cause VS Code to launch the process picker and allow us to pick our .NET Core process.

But wait, that’s running in a Docker container, how do I pick that process?

Well, thankfully the process picker dialogue is capable of executing a command to get the list of processes and can do it against a remote machine.

Under the hood it will execute docker exec -i <container name> /vsdbg/vsdbg to list the processes within the container, but we’ll do it a little bit nicer:

{
    "name": ".NET Core Docker Attach",
    "type": "coreclr",
    "request": "attach",
    "processId": "${command:pickRemoteProcess}",
    "pipeTransport": {
        "pipeProgram": "docker",
        "pipeArgs": ["exec", "-i", "sunshine-downloader"],
        "debuggerPath": "/vsdbg/vsdbg",
        "pipeCwd": "${workspaceRoot}",
        "quoteArgs": false
    }
}

Enter fullscreen mode Exit fullscreen mode

Now if you run your container and then launch the debugger in VS Code you’ll be able to pick the dotnet process within the container that your application is using.

Conclusion

And there you have it, you can now use VS Code as your editor of choice and also debug applications running in Docker containers. There are more advanced scenarios you can tackle with this including debugging via SSH, all of which are covered on OmniSharp’s wiki.

In fact, I’m using this to debug an F# application I’m building to run on .NET Core. 😉

Happy debugging! 😁

Bonus Idea: Removing the additional layer with volumes

When I shared this post internally my colleague Shayne Boyer brought up an idea on how to tackle this without adding a new layer to your Dockerfile, and in fact, making it possible to debug pre-built images (assuming they have the debugging symbols in them).

You can do this by downloading the vsdbg package for the distro your image is based off (Ubuntu, Alpine, ARM, etc.), which you can determine by reading the shell script (or download into a container 😉) onto your machine and then mounting the path as a volume when starting your container:

docker run --rm -v c:/path/to/vsdbg:/vsdbg --name my-dotnet-app my-dotnet-app
Enter fullscreen mode Exit fullscreen mode

Now you've inserted the debugger into the container when you start it rather than bundling it into the image.

Latest comments (5)

Collapse
 
dawidkala profile image
dawidkala

I followed the instructions listed in this post but I'm not getting my breakpoints to work.

This is the error message I'm getting:

Breakpoint warning: No executable code of the debugger’s target code type is associated with this line.
Possible causes include conditional compilation, compiler optimizations, or the target architecture of this line is not supported by the current debugger code type.

What could I be doing wrong? Is there a full working example I could follow?

Collapse
 
teonivalois profile image
Teoni Valois

Excellent post, congratulations!

One thing that you didn't mention on your text, but could help others is that we need to map the source directory for a proper debug session by adding the sourceFileMap property in the launch.json file.

{
    "name": ".NET Core Docker Attach",
    "type": "coreclr",
    "request": "attach",
    "processId": "${command:pickRemoteProcess}",
    "pipeTransport": {
        "pipeProgram": "docker",
        "pipeArgs": ["exec", "-i", "sunshine-downloader"],
        "debuggerPath": "/vsdbg/vsdbg",
        "pipeCwd": "${workspaceRoot}",
        "quoteArgs": false
    },
    "sourceFileMap": {
        "/src": "${workspaceRoot}"
    }
}

In the example above I used a multi stage dockerfile, and the application was build on the /src folder. This causes the PDB files to reference such folder. By doing the sourceFileMap, we can translate the /src folder to our real source code directory.

This is what the OmniSharp docs says about it:

sourceFileMap: To debug programs built on computers other than the Visual Studio code computer, Visual Studio code needs to be hold how to map file paths. So, for example, if you are debugging 'ExampleProject' which was built in your home directory on the Linux server, and now you have the same code open in Visual Studio code, this rule tells the debugger to change any file paths that it sees in '/home/ExampleAccount/ExampleProject' and replace it with the open directory.

I just submitted a change to include it in the Wiki Page.

Collapse
 
aaronpowell profile image
Aaron Powell

Great tip!

Collapse
 
glenmccallumcan profile image
Glen McCallum

This is a useful docker file. I've had hit and miss experience debugging .NET Core applications in docker from visual studio. But I hadn't taken the time to figure out the manual process.

Collapse
 
aaronpowell profile image
Aaron Powell

That was part of my motivation, the Visual Studio tooling can seem like a bunch of "magic" so I want to know what it's actually doing so that I can do it without Visual Studio if so desired.