Orchestrating Visual Studio Code : Part 3 : Debugging Docker Containers
2nd May 2018When working with multiple microservices, it is important to be able to run a debugger inside the docker container. In this article, we will learn how to load vsdbg
- the Visual Studio Debugger - inside of a docker image and attach to it using VS Code's launchers.
The Files
The following files will be created in this section:
.
├── Dockerfile
├── docker-compose.yml
├── scripts
├── project-tasks.ps1
└── project-tasks.sh
The Dockerfile
We are using a multi-stage dockerfile to load a base image containing the Visual Studio Code debugger with the following steps:
- Create a base image layer from
aspnetcore2.0
. - Download the VSCode debugger
vsdbg
. - Create a build image layer from
aspnetcore-build
. - Copy the contents of the web project WebProject.
- Publish the web project into
/publish
folder. - Create a production layer from the base layer.
- Copy the
/publish
folder contents into the production layer.
The ENTRYPOINT
for the dockerfile serves dual purposes:
- Run the application normally.
- If the
ENABLE_DEBUGGING
environmental variable is set,sleep
the application and listen for debug commands
# #############################################################################
# Development docker image with support for Visual Studio debugging integration
#
# #############################################################################
# BASE IMAGE
FROM microsoft/aspnetcore:2.0 AS base
WORKDIR /app
EXPOSE 80
# vscode debugging support
WORKDIR /vsdbg
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
unzip \
&& rm -rf /var/lib/apt/lists/* \
&& curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg
# #############################################################################
# BUILDER IMAGE
FROM microsoft/aspnetcore-build:2.0 AS builder
ENV NUGET_XMLDOC_MODE skip
# publish
COPY . /app
WORKDIR /app/src
RUN dotnet publish -f netcoreapp2.0 -r debian.8-x64 -c Debug -o /publish -v quiet
# #############################################################################
# PRODUCTION IMAGE
FROM base AS production
WORKDIR /app
COPY --from=builder /publish .
# Kick off a container just to wait debugger to attach and run the app
ENTRYPOINT ["/bin/bash", "-c", "if [ \"$REMOTE_DEBUGGING\" = \"enabled\" ]; then sleep infinity; else dotnet WebProject.dll; fi"]
# #############################################################################
The docker-compose file
The docker-compose file contains a single service for creating the docker container.
The REMOTE_DEBUGGING variable is set via a VSCode pre-launch task.
version: '3'
services:
docker-debug-webapp:
container_name: docker-debug-webapp
image: docker-debug-webapp
build:
context: .
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=development
- REMOTE_DEBUGGING=${REMOTE_DEBUGGING}
ports:
- "5000:80"
networks:
- dev-network
tty: true
stdin_open: true
networks:
dev-network:
driver: bridge
The setup scripts
The setup scripts project-tasks.sh
and project-tasks.ps1
are used to build and compose the container before launching the debugger.
.
├── scripts
│ ├── project-tasks.ps1
│ └── project-tasks.sh
We will be using the
bash
project-tasks.sh
script for this exercise. If you are on windows, aPowerShell
project-tasks.ps
script is provided in the GitHub source.
The compose() helper method
The compose()
method is called by the task to stage your container. It supports multiple docker.{environment}.yml
files. The default is docker-compose.yml
.
compose () {
echo -e "${GREEN}"
echo -e "++++++++++++++++++++++++++++++++++++++++++++++++"
echo -e "+ Composing docker images "
echo -e "++++++++++++++++++++++++++++++++++++++++++++++++"
echo -e "${RESTORE}"
if [[ -z $ENVIRONMENT ]]; then
ENVIRONMENT="debug"
fi
composeFileName="docker-compose.yml"
if [[ $ENVIRONMENT != "debug" ]]; then
composeFileName="docker-compose.$ENVIRONMENT.yml"
fi
if [[ ! -f $composeFileName ]]; then
echo -e "${RED} $ENVIRONMENT is not a valid parameter. File '$composeFileName' does not exist. ${RESTORE}\n"
else
echo -e "${YELLOW} Building the image $imageName ($ENVIRONMENT). ${RESTORE}\n"
docker-compose -f "$composeFileName" build
echo -e "${YELLOW} Creating the container $imageName ${RESTORE}\n"
docker-compose -f $composeFileName kill
docker-compose -f $composeFileName up -d
fi
}
...
case "$1" in
"composeForDebug")
export REMOTE_DEBUGGING="enabled"
compose
;;
*)
showUsage
;;
esac
Note the
export REMOTE_DEBUGGING
environmental variable. It is being set so that docker-compose can pass it to the Dockerfile'sENTRYPOINT
. This will pause the container so that it can listen forvsdbg
debugging commands from the IDE. It also allows us to use thecompose()
method to run our containers without debugging enabled.
The VSCode Task
The Visual Studio Code tasks.json is used to link the setup scripts.
Links to the Windows PowerShell and OSX/Linux Bash scripts. It is used to create our docker image and container:
- Creates the image.
- Kills any running instances.
- Starts the container.
Add the following to your .vscode/tasks.json
:
{
"label": "compose-for-debug",
"type": "shell",
"osx": {
"command": "bash ./scripts/project-tasks.sh composeForDebug"
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "dedicated"
},
"problemMatcher": [],
"windows": {
"command": ".\\scripts\\project-tasks.ps1 -ComposeForDebug"
}
}
The VSCode Launcher
The Visual Studio Code launch.json orchestrates the application with the following settings:
- Configures apreLaunchTask called compose-for-debug to build and compose the docker container.
- Sets the REMOTE_DEBUGGING env variable to allow the container debugger vsdbg to listen for commands.
- Configures the pipeTransport with the path /vsdgb/vsdbgto the debugger within the container and the command docker and args exec -i
that should be executed to begin debugging. - Sets the sourceFileMap to allow breakpoints on the local filesystem to line-up with the compiled source in the container.
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker Launch",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "compose-for-debug",
"cwd": "/app",
"program": "/app/WebApp.dll",
"env": {
"ASPNETCORE_ENVIRONMENT": "development",
"REMOTE_DEBUGGING": "true"
},
"sourceFileMap": {
"/app": "${workspaceRoot}"
},
"launchBrowser": {
"enabled": true,
"args": "http://localhost:5000/api/values",
"windows": {
"command": "cmd.exe",
"args": "/C start http://localhost:5000/api/values"
},
"osx": {
"command": "open"
}
},
"pipeTransport": {
"debuggerPath": "/vsdbg/vsdbg",
"pipeProgram": "docker",
"pipeCwd": "${workspaceRoot}",
"pipeArgs": [
"exec -i docker-debug-webapp"
],
"quoteArgs": false
}
}
]
}
Debugging Your App
At this point, you should be able to run the debug task from within VSCode. Press F1, select Run Tasks
, and select compose-for-debug
. You application will start with a debugger attached.
Composing Without Debug
I've included an additional task compose
that allows you to run your docker container without the debugger attached. This is useful in microservice environments where you want to run several services to work against.
In a later post, I'll be showing how you can orchestrate a large number of microservices using VSCode workspaces and the compose
task.
The Source Code
You can find the source code for this article in the following repository under the part-2-tasks-and-launchers
branch:
https://github.com/christophla/blog-orchestrating-vscode/tree/part-3-debugging-docker
Next Post : Running Docker Containers
In the next post we will learn how to run stand-along docker container without the debugger attached. This is useful when orchestrating several dependent microservices in a large application.
Previous Post : Tasks and Launchers
https://christophertown.com/orchestrating-vscode-tasks-and-launchers
View Comments