Debugging Dotnet Watch processes inside of a Docker container

Debugging Dotnet Watch processes inside of a Docker container

Hello, full example can be found here.

Sorry for the huge wall of text, this is mostly just so you can copy paste stuff in for it to work, it’s not actually THAT much work 🙂

This will allow you to do two things. Using Visual Studio Code, you can enable dotnet watch run to work in a docker container, and then be able to debug the instance being ran through your watch. This is incredibly useful for microservices, you can docker-compose orchestrate your entire microservice application, and then individually debug your containers, even ones not publically exposed. This has a huge advantage in docker, as if you’ve used it before, you reference it by the orchestrated name, not by an ip:port (as it’s in the orchestrations network, so it doesn’t even have an external ip:port nor a way of you getting it).

So, on to the code.

To get dotnet watch run to work, (taken from, all you have to do is add a Directory.Build.props file in your application with this content:

<Project> <PropertyGroup> <DefaultItemExcludes>$(DefaultItemExcludes);$(MSBuildProjectDirectory)/obj/**/*</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);$(MSBuildProjectDirectory)/bin/**/*</DefaultItemExcludes> </PropertyGroup> <PropertyGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)' == 'true'"> <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/container/</BaseIntermediateOutputPath> <BaseOutputPath>$(MSBuildProjectDirectory)/bin/container/</BaseOutputPath> </PropertyGroup> <PropertyGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)' != 'true'"> <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/local/</BaseIntermediateOutputPath> <BaseOutputPath>$(MSBuildProjectDirectory)/bin/local/</BaseOutputPath> </PropertyGroup> </Project> 

This is to separate your vscode/vs and docker fragments and nuget packages, so there aren’t conflicts when we will use volumes later. An example dockerfile at this point would be:

FROM microsoft/dotnet:sdk ENV DOTNET_USE_POLLING_FILE_WATCHER 1 WORKDIR /app COPY . . ENTRYPOINT dotnet watch run --urls=http://+:5000 

The docker file is pretty simple, but to explain, it will download the dotnet sdk, set the container to use polling file watcher instead of the .net system watcher (this runs much better on linux containers), set the container to point to root/app, copy the contents of your project to root/app, and then when you execute the container, call dotnet watch run, and use the –urls flag to set the url to not be localhost, as you can’t access the localhost within a container, you want it to be assigned to, so you can go to localhost on your computer to see the port.

To run it, just use

docker build -t dotnet-watch-docker-example . docker run -it --rm -v ${PWD}:/app -p 5000:5000 dotnet-watch-docker-example 

Now, when you make changes to any file in your directory, dotnet watch run should automatically see those changes and restart incredibly fast, allowing for rapid proto typing. Pretty cool!

Now for the tricky part, debugging. In vscode, to remote debug we have vsdbg, which can start an instance remotely and connect to to debug. So, to make this work with docker, all we have to do is make our dockerfile download and place vsdbg, and then set our launch.json to point to that instance and tell it what process to run and then we can actually debug our docker container remotely!

To get started, we can adjust our above dockerfile to:

FROM microsoft/dotnet:sdk WORKDIR /vsdbg RUN apt-get update  && apt-get install -y --no-install-recommends  unzip  && rm -rf /var/lib/apt/lists/*  && curl -sSL  | bash /dev/stdin -v latest -l /vsdbg ENV DOTNET_USE_POLLING_FILE_WATCHER 1 WORKDIR /app COPY . . ENTRYPOINT dotnet watch run --urls=http://+:5000 

The only difference as you can see is that we download vsdbg into our root/vsdbg directory, and we have the entire line as one install so it can be cached across instances. So if you have 50 microservices, it can just reference the same cached install instead of redownloading it for each instance.

With vsdbg now in our docker-container, we now have to modify our launch.json in our .vscode folder. Add a configuration such as below:

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

What we’re doing is saying we want to attach, allow the processId to show up from the dropdown in visual studio code (we will look at this in a second), use the source map from our working directory as the debug information, and then pipe to the vsdbg instance with the processId that we selected before using docker, into the container directory of whatever name is after the “-i” (in my case, dotnet_watch_docker_example, which you will set to be whatever your container_name is).

With all of that setup, you can now use

docker build -t dotnet-watch-docker-example . docker run -it --rm -v ${PWD}:/app --name dotnet_watch_docker_example -p 5000:5000 dotnet-watch-docker-example 

The difference from before is the –name. This will set the container_name that docker will use that we can reference. This –name parameter needs to be the same as the line after “-i” in the launch.json above, as it tells what container you want to connect to.

to run, now when you hit F5, just choose the instance that says running your dll, such as:

You just click the one that says it’s using your dll.

Tada! You can now breakpoint and debug your application like a normal application.

However, that run line is pretty long, and it’s pretty annoying to type every time. If you wanted to make it simple, we can create a simple docker-compose.yml file, and add:

version: '3.3' services: dotnet-watch-docker-example: container_name: dotnet_watch_docker_example image: dispersia/dotnet-watch-docker-example build: context: . ports: - 5000:5000 volumes: - './:/app' 

and now just call

docker-compose up 

Much simpler. You run this command once at the beginning of your day, and you just code and debug away for the rest without ever worrying about starting/restarting/running in debug/etc.

After you do it once and understand it, it’s super fast to do, and makes debugging your microservice apps not a complete pita. Hopefully someone finds this useful! The biggest current limitation is you can not scale. This is a limit of visual studio code, and I hope to work on this soon. Basically, you couldn’t tell docker to scale your one container to match 5 containers, so if you have a load balancer or something, you can only remote connect to a single container instance, you couldn’t connect to all 5 (which usually isn’t a huge problem in development – you usually only want a single instance running and not developing against a load balancer as that’s a dev ops things anyway, but hey, it would be useful).

If you have any questions, feel free to ask!


submitted by /u/Dispersia
[link] [comments]

Leave a Reply