DEV Community

Cover image for How to use VSCode debugger with multiple Docker services
Loïc Poullain
Loïc Poullain

Posted on

How to use VSCode debugger with multiple Docker services

In my company (https://indy.fr), we use Docker and Docker Compose to run our Node.js services locally. Recently, I needed to configure and run the VSCode debugger on some of these services to debug a feature. There are a few things to know to achieve this, which I will share in this article with some basic examples.

Before we start, here are the points that will serve as a guideline in this tutorial:

  • We want to keep using Docker and Docker compose to run our services, so that we have the proper environment for each of these services (environment variables, etc).
  • We do not want to touch the current docker-compose.yml which could, potentially, be used in the future to deploy our services in production.

The Sample Application

Let's start by creating a first service. It is a simple web server that concatenates two strings, a first name and a last name, and returns the result. This service will live in a webapp/ directory at the root of the project.

The Node.JS code

webapp/package.json

{
    "name": "webapp",
    "scripts": {
        "start": "node src/server.js"
    },
    "dependencies": {
        "express": "^4.16.1"
    }
}
Enter fullscreen mode Exit fullscreen mode

webapp/src/server.js

const express = require('express');
const app = express();

app.get('/fullname', (req, res) => {
    const firstName = req.query.firstNme;
    const lastName = req.query.lastName;
    res.send(`${firstName} ${lastName}`);
});

app.listen(8080, () => console.log('Listening on port 8080...'));
Enter fullscreen mode Exit fullscreen mode

webapp/Dockerfile

FROM node:16

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install
COPY . .

EXPOSE 8080
CMD [ "node", "src/server.js" ]
Enter fullscreen mode Exit fullscreen mode

webapp/.dockerignore

node_modules
npm-debug.log
Enter fullscreen mode Exit fullscreen mode

The Docker configuration

Now that the application code is written and the Dockerfile created, we can add a docker-compose.yml file at the root of the project.

docker-compose.yml

services:
    webapp:
        build: ./webapp
        ports:
            - "127.0.0.1:8080:8080"
Enter fullscreen mode Exit fullscreen mode

Let's start the service.

docker-compose build
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

If you go to http://localhost:8080/fullname?firstName=Foo&lastName=Bar, you should see the string undefined Bar, which is the unexpected behavior we will debug.

Debugging the Application in Docker with VSCode

The debugger command

To allow the future VSCode debugger to attach to the Node service, we need to specify it when we start the process by adding the --inpect flag.

Simply using --inspect or --inspect=127.0.0.1:9229 is not sufficient here because we need the 9229 port to be accessible from outside the service, which is allowed by the 0.0.0.0 address. So this command should only be used when you run the debugger in a Docker service. Otherwise, you would expose the port and the debugger to anyone on the Internet.

webapp/package.json

{
    "name": "webapp",
    "scripts": {
        "start": "node src/server.js",
        "start:docker:debug": "node --inspect=0.0.0.0:9229 src/server.js"
    },
    "dependencies": {
        "express": "^4.16.1"
    }
}
Enter fullscreen mode Exit fullscreen mode

The Docker configuration

Following our guideline, we do not modify the initial docker-compose.yml but create a second one that extends the first one. We will use the -f flag of the docker-compose CLI to use them both.

docker-compose.debug.yml

services:
    webapp:
        command: [ 'npm', 'run', 'start:docker:debug' ]
        ports:
            - "127.0.0.1:8080:8080"
            - "127.0.0.1:9229:9229"
Enter fullscreen mode Exit fullscreen mode

Then, to restart the service with debug mode enabled, you can use this command:

docker-compose build
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
Enter fullscreen mode Exit fullscreen mode

The service is now ready to be attached to the VSCode debugger.

Running the debugger with VSCode

At the root of your project, create a new directory .vscode and add the following configuration file.

.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Debug webapp",
            "remoteRoot": "/app/src",
            "localRoot": "${workspaceFolder}/webapp/src"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

When adding a breakpoint, the remoteRoot and localRoot properties will match the file's position in the VSCode environment and its location in the Docker service file system.

You can now start the debugger on the webapp service. Open the debugging panel and select the Debug webapp option. Then click on the play button.

start-debugger.png

The debugger is started.

add-breakpoint.png

Add a breakpoint on line 6 and then go to http://localhost:8080/fullname?firstName=Foo&lastName=Bar.

run-debugger.png

The debugger stops on line 6 and we can see that the variable firstName is undefined. The problem comes from line 5 where this is a typo on the firstName parameter name.

To close the debugger, click on the button with a red square.

Debugging Multiple Docker Services

The Node.JS micro-service

To take this a step further, we will add another service, named micro-service, which will be called by webapp.

First, copy and paste the contents of the webapp directory into another directory named micro-service.

Then, in the webapp directory, install axios and update the code as follows.

npm install axios
Enter fullscreen mode Exit fullscreen mode

webapp/src/server.js

const express = require('express');
const axios = require('axios');

const app = express();

app.get('/fullname', async (req, res, next) => {
    try {
        const { data: fullName } = await axios.get('http://micro-service:8080/fullname', {
            params: req.query
        });
        res.send(fullName);
    } catch (err) {
        next(err);
    }
});

app.listen(8080, () => console.log('Listening on port 8080...'));
Enter fullscreen mode Exit fullscreen mode

The URL used line 8 is based on the name of the Docker service defined in the next section.

The Docker configuration

Add the new service to your docker-compose.yml. Note that it uses a different port so as not to conflict with the webapp service.

docker-compose.yml

services:
    webapp:
        build: ./webapp
        ports:
            - "127.0.0.1:8080:8080"
    micro-service:
        build: ./micro-service
        ports:
            - "127.0.0.1:3001:8080"
Enter fullscreen mode Exit fullscreen mode

Then, in your docker-compose.debug.yml, add the new service as well. Note that the debugger port is also different from the first one.

docker-compose.debug.yml

services:
    webapp:
        command: [ 'npm', 'run', 'start:docker:debug' ]
        ports:
            - "127.0.0.1:8080:8080"
            - "127.0.0.1:9229:9229"
    micro-service:
        command: [ 'npm', 'run', 'start:docker:debug' ]
        ports:
            - "127.0.0.1:3001:8080"
            - "127.0.0.1:9230:9229"
Enter fullscreen mode Exit fullscreen mode

Now build and start the two services.

docker-compose build
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
Enter fullscreen mode Exit fullscreen mode

Running multiple debuggers with VSCode

The last thing to do is to add the configuration of the second debugger in launch.json.

.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Debug webapp",
            "remoteRoot": "/app/src",
            "localRoot": "${workspaceFolder}/webapp/src"
        },
        {
            "type": "node",
            "request": "attach",
            "name": "Debug micro-service",
            "port": 9230,
            "remoteRoot": "/app/src",
            "localRoot": "${workspaceFolder}/micro-service/src"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Once the configuration is added, you can run the two debuggers for each service.

start-webapp-debugger.png

start-micro-service-debugger.png

Once both debuggers are started, add a breakpoint in each service and go to http://localhost:8080/fullname?firstName=Foo&lastName=Bar. The application will stop successively on each breakpoint.

Your VSCode debugger is now fully configured to work with your Docker services. Congratulations!

Article originally published here: https://tech.indy.fr/2022/06/10/how-to-use-vscode-debugger-with-multiple-docker-services/

Top comments (0)