loading...
Cover image for How to debug Node.js in a Docker container

How to debug Node.js in a Docker container

alex_barashkov profile image Alex Barashkov ・4 min read

More and more teams are moving their development environments to Docker containers. It brings a lot of advantages, such as a unified environment shared between all devs, a faster onboarding process for new members, and predictable deployments. For example, in my previous article “Microservices vs Monolith architecture", I made the point that with microservices you have to use Docker, because otherwise you’re launching multiple microservices on a local machine and development becomes a huge pain. When you have even 5-10 microservices, you run them through your terminal one by one and have to make sure that you have all dependencies, db, elasticsearch, etc., installed. Alternatively, you can get it running with one command using docker-compose, a much better approach.

But that approach requires you to understand Docker and to not miss the functionality or experience you had without it. One of the things to understand is how to debug within the Docker container. In this article, we will go through a few use cases related to debugging a
Node.js app in a docker container.

Prerequirements

Cases

  • Node.js, Docker, without Nodemon
  • Node.js, Docker, Nodemon
  • Node.js, Docker with docker-compose

Node.js, Docker, without Nodemon

If you already have the Node.js app your Dockerfile, it probably looks like this:

FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", "start" ]

In order to continue, we need to build our Dockerfile. I recommend using the VS Code Docker extension and begin building as shown below:

To enable the debugger in Node.js, we need to use --inspect or --inspect-brk, but because our app will be launched inside Docker, we also need to allow access from external networks to our debugger by passing 0.0.0.0.

 "scripts": {
    "start": "node --inspect=0.0.0.0 index.js"
  },

Now, when you execute npm start it will run the node debugger on a separate port (by default 9229) you can then connect your debugger tool to. To access debugger, you have to also expose the 9229 port to your host machine. You could do it with the following command:

docker run --rm -d -p 3000:3000 -p 9229:9229 -v ${PWD}:/usr/src/app -v /usr/src/app/node_modules example:latest

With that command, we expose 3000 and 9229 ports of the Dockerized app to localhost, then we mount the current folder with the app to /usr/src/app and use a hack to prevent overriding of node modules from the local machine through Docker.

Now we could configure with the VS Code wizard debug launch task. Press CMD(Ctrl)+Shift+P(Command Palette) and find “Debug: Open launch.json”:


Then choose Docker: Node.js:


This will generate a launch.json file with the following content:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Docker: Attach to Node",
            "type": "node",
            "request": "attach",
            "port": 9229,
            "address": "localhost",
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/usr/src/app",
            "protocol": "inspector"
        }
    ]
}

The configuration for Docker is to manually attach to the debugger port and map the local root folder to remote in order to keep breakpoints definitions working.

Go to the Debug Page at VS code, press the “Play” button and enjoy debugging in Docker.

Node.js, Docker, with Nodemon

The small difference comes when we want to use the debugger with nodemon. To start with, your script in package.json will look like this:

"start": "nodemon --inspect=0.0.0.0 src/index.js",

Then, because nodemon will restart your app on each change, your debugger will lose a connection. When this happens, there is an option “restart: true”, which will cause you to simply attempt to reconnect to the debugger after each restart.

So your launch.json should look like this:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Docker: Attach to Node",
            "type": "node",
            "request": "attach",
            "port": 9229,
            "address": "localhost",
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/usr/src/app",
            "protocol": "inspector",
            "restart": true
        }
    ]
}

Go to Debug Page at VS code, press the “Play” button and, just as before, enjoy debugging in Docker.

Node.js, Docker, with docker-compose

A third option is to run your docker images with docker-compose, which is good if your service also requires a database or other dependencies you can run with Docker.

Create a docker-compose.yaml in your app folder with the following content:

version: '3'

services:
  example-service:
    build: .
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - 3000:3000
      - 9229:9229
    command: npm start

We used basically the same instructions as we used for non docker-compose solution, just converted them in yaml format. Now you can continue with the launch.json file using nodemon or the node.js running option and use Debugger as described in previous sections.

Discussion

pic
Editor guide
Collapse
sirhennihau profile image
Sir hennihau

"start": "nodemon --inspect=0.0.0.0 source/index.ts",

throws

[nodemon] starting ts-node --inspect=0.0.0.0 source/index.ts
/app/node_modules/arg/index.js:90
throw err;
^

Error: Unknown or unexpected option: --inspect

any idea how to fix it?

Collapse
ariehkovler profile image
Arieh Kovler

Thanks for this helpful guide. I get that you're covering Open-source options here, but another option for getting code-level debug data out of a containerized Node.js app is Rookout.com. It has some interesting advantages over a traditional debugger - it's always on, even in production, and it doesn't stop the app so you can create 'breakpoints' that don't actually break, all without restarting or redeploying. It also works at scale - you don't have to attach it to a single instance in a single container, it can debug across a whole Swarm or Cluster all at once.

On the other hand, it doesn't allow variable forcing or logic forcing, which means it's more limited than a true debugger. Of course, this has some advantages too (safe use on live systems etc).

Have you encountered Rookout before?

Collapse
nimatrazmjo profile image
Nimatullah Razmjo

Dear Alx,

Your tutorial is great. Why did you add "/usr/src/app/node_modules" in volumes. What is the purpose of it? because "/usr/src/app" contain every thing that we need.

Collapse
alex_barashkov profile image
Alex Barashkov Author

Thank you. In you local machine in app folder you don't have node modules, so when you use mount your folder will override everything in /usr/src/app. The line you mentioned, sort of preventing overriding of node modules installed during the image build process.

Collapse
lfre profile image
Alfredo Lopez

You can do the same by adding node_modules to a .dockerignore file. 👍

Thread Thread
alex_barashkov profile image
Alex Barashkov Author

No. dockerignore works only for the process of copying file during the build of the image and has nothing to do with mount.

Collapse
aghost7 profile image
Jonathan Boudreau

You can also start the inspector if the process is already running with:

kill -SIGUSR1 $pid

Where $pid is the process identifier of the program you want to debug.

Collapse
gannochenko profile image
Sergei Gannochenko

Impressive, always wanted to "grow up" and stop using console.dir() while developing nodejs apps :)

Collapse
alex_barashkov profile image
Alex Barashkov Author

It's also much faster to debug functionality with debugger rather than using console.log or console.dir

Collapse
shoks profile image
Boubacar Ndoye

Hi done it all and it dont wanna work

Collapse
ramadanko profile image
Mohammed Ramadan

amazing, for the first part before configuring for VS code, will continue reading later :P