How to debug Node.js in a Docker container

More and more teams are moving their development environments into Docker containers. It has a lot of advantages: a unified environment for every developer, faster onboarding for new people, and predictable deployments. For example, in the article “Microservices vs. monolithic architecture” we explained that with microservices, Docker is practically mandatory, because otherwise you'd have to run several microservices on your local machine and development becomes a nightmare. With just 5–10 microservices you already have to start them one by one from the terminal and make sure you have all the dependencies installed, the database, elasticsearch, etc. The alternative is to spin everything up with a single command using docker-compose, a much better approach.
But that approach requires understanding Docker and not losing the functionality or the experience you had without it. One of the things you need to be clear on is how to debug inside the Docker container. In this article we'll look at several use cases for debugging a Node.js app in a docker container.
Prerequisites
- VS Code
- Docker
- Docker extension for VS Code
Cases
- Node.js, Docker, without Nodemon
- Node.js, Docker, with Nodemon
- Node.js, Docker with docker-compose
Node.js, Docker, without Nodemon
If you already have the Dockerfile for your Node.js app, it probably looks like this:
FROM node:10-alpineEXPOSE 3000WORKDIR /usr/src/appCOPY package*.json ./RUN npm installCOPY . .CMD [ "npm", "start" ]
To continue, we need to build the Dockerfile. We recommend using the Docker extension for VS Code and running the build as shown below:
To enable the debugger in Node.js you need to use — inspect or — inspect-brk, but since the app will run inside Docker, we also need to allow debugger access from external networks by passing 0.0.0.0.
"scripts": { "start": "node --inspect=0.0.0.0 index.js" },
Now, when you run npm start, the Node debugger will launch on a separate port (9229 by default) that your debugging tool can connect to. To access the debugger, you also need to expose port 9229 to your host machine. You can do this with this 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 this command we expose ports 3000 and 9229 of the dockerized app to localhost, mount the current app folder to /usr/src/app, and use a small trick so Docker doesn't overwrite the node modules with the ones from the local machine.
Now we can configure the debug task with VS Code's assistant. Press CMD(Ctrl)+Shift+P (command palette) and search for “Debug: Open launch.json”:
Then choose Docker: Node.js:
A launch.json file will be generated with this 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 consists of manually connecting to the debugger port and mapping the local root folder to the remote one so breakpoints keep working.
Go to VS Code's debug view, press the “Play” button, and start debugging in Docker.
Node.js, Docker, with Nodemon
There's a small difference when we want to use the debugger with nodemon. To start, the package.json script will look like this:
"start": "nodemon --inspect=0.0.0.0 src/index.js",
Since nodemon restarts the app with every change, the debugger will lose the connection. For these cases there's the “restart: true” option, which makes it try to reconnect to the debugger after every 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 VS Code's debug view, press the “Play” button, and, as before, start debugging in Docker.
Node.js, Docker, with docker-compose
A third option is to run your Docker images with docker-compose, which is very handy if your service also needs a database or other dependencies you can run with Docker.
Create a docker-compose.yaml in your app's folder with this content:
version: '3'services: example-service: build: . volumes: - .:/usr/src/app - /usr/src/app/node_modules ports: - 3000:3000 - 9229:9229 command: npm start
Basically we've used the same instructions as in the solution without docker-compose, but converted to yaml format. Now you can proceed with the launch.json file, with either the nodemon or the node.js option, and use the debugger as explained in the previous sections.