Debugging ExpressJS app with Docker and Visual Studio Code DevContainers
When I started my journey with NodeJS and ExpressJS, one of the things I saw in the trainings and blogs were instructions on installing NodeJS runtime. Although its trivial, but coming from the world of Enterprise application development where deployment to customer and being able to recreate the exact environment is critical, I wondered if there is an application of Docker in this workflow.
As I explored Docker, and Docker Compose, I saw that the starting point would have been easier if the instructions for using those with NodeJS and Express were available. But then, as I continued to explore, I did not find anything that could walk me through setting up VS Code, my editor of choice, for developing my application while still using breakpoints, as I would, with applications developed locally on my machine.
This is where the hunt to make something work, and make it easy for others to use, started. After exploring various options, I zoomed in on a workflow, that would get me started quickly. Like everything in life, there is some work required. So, let's begin.
Here are things we will do in this blog:
- Create an Express application
- Create a Docker and Docker-Compose environment
- Run and Debug in VS Code
The prerequisites for reading further are:
- Docker For Desktop installed ( docker.com/products/docker-desktop )
- Visual Studio Code installed
- NodeJS installed ( Yeah! I know. Only to generate the app, never again )
- An Internet connection
I am going to assume no understanding of Docker, and an going to skip all attempt at explaining how the steps work, in this post. The details on that will come as follow up, because it drive focus away from the intent of this article.
At the end of the steps, you will have a generated Express template, Dockerised, and a VS Code environment for debugging the application.
I am not getting into details of using Docker in production for this app, just development.
Supporting code, with follow along commit history, is available on Github
Generate ExpressJS app
The benefits of using Docker are to not need local installation of tools. Ironically, in the process I am about to demonstrate, the first step requires that NodeJS be installed. But this is only the first time. You wont need it after this, ever.
To generate the Express app, run the command:
You will see the output as this:
You don't need to do anything to run
npm install yet. This is why it was an irony that you need
Create local Git Repo
As a matter of best practice, I suggest initialising a local git repository, just in case you need to roll back to this point. So, here are the steps:
git init . git add . git commit -m "new express app (generated)"
VS Code configuration
Start VS Code in the selected directory by running:
Switch to the Extensions tab in VS Code, and ensure you have the following extensions installed (both published by Microsoft):
- Docker ( ms-azuretools.vscode-docker )
- Remote Containers ( ms-vscode-remote.remote-containers )
You must have a minimum of these in the VS Code environment, and really, after what we do today, you won't need any installed here either. You will discover why, as we progress.
Let the VS Code extensions do their magic
- Start up the "Command Palette" in VS Code using Cmd+Shift+P (Ctrl+Shift+P on Windows) or from the "View" menu
- Invoke the action named "Docker: Add Docker Files to Workspace"
- Use the default values for the next few prompts: -- Application Platform: Node JS -- Port that the application listens on: 3000 -- Include optional Docker Compose files: Yes
The git tab on VSCode will tell you that 6 files have been created, ready to be added to git.
Again, commit the files, just in case you need to roll back here. So, use VS Code, add the files, and commit using the comment "Add generated Docker files"
Welcome to Remote Containers
As you notice, we have not yet run the magical command
But hang on a little longer.
Let us generate the last bit of files, before we edit them. So, back to the "Command Palette", this time we want to run the command: "Remote-Containers: Add Development Container Configuration Files".
For the next prompt
How would you like to create your container configuration? use the following value:
You will see a prompt to reopen to develop in container, but not yet. Need some manual steps.
But before we go there, add the files to git, with the comment, "add generated devcontainer config"
First step is to delete the
.vscode directory and its contents. These files were generated by the Docker extension. We don't need them, and we want to keep focus on our task. If you need them, they are in the git history for you.
Delete, and commit.
Updating the Dev Dockerfile
Follow these steps:
- Create a copy of the
Dockerfileand name it
- Edit the
Dockerfile.debugand do the following: -- Remove the
-alpinein the first line. We need a full environment for debugging -- Change the line
RUN npm install --production --silent && mv node_modules ../to
RUN npm install
The file will look something like this:
FROM node:12.18 ENV NODE_ENV production WORKDIR /usr/src/app COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"] RUN npm install COPY . . EXPOSE 3000 CMD ["npm", "start"]
You may change the version of Node in the
FROM command, and make sure you do it in the
Dockerfile too, so that you are consistent.
Again, not getting into the production usage of this app in this post.
Do not overwrite node_modules
The idea behind using
npm install inside the container is to ensure the right module binaries are available for that environment. We do not want to use the node_modules for your local machine there. So, we need to update the
I am posting the full line here. I have changes the
build section, and added
volumes. git will show you the lines that changed. YAML files are very sensitive to the indentation, so don't want you to spend hours figuring out why it doesn't work.
version: '3.4' services: sample: image: sample build: context: . dockerfile: Dockerfile.debug environment: NODE_ENV: development ports: - 3000:3000 - 9229:9229 volumes: - /usr/src/app/node_modules command: ["node", "--inspect=0.0.0.0:9229", "./bin/www"]
Ensuring VS Code is looking at the right place
As you notice in the files we have worked with yet, specifically the
docker-compose.debug.yml, there are specific references to the location of the source in the container. (
/usr/src/app ). So we need to make sure that VS Code will recognise that as the location to work with. Let's do that.
- Open the
workspaceFolderand change the value from
And, we also need to update the location that the local sources will be mounted on-to:
- Open the
- Look for the
- The comment there clearly calls out:
Update this to wherever you want VS Code to mount the folder of your project
- So, change
Here are the file changes:
As always, commit to git.
One more thing ...
No, I am not going to say those magic words. We are done. You can, at this time, click on the green icon at the bottom left of the VS Code window, or use the 'Command Palette' and "Reopen in Container".
VS Code is helpful to show the logs, if you are interested, when you click the appropriate link in the message shown during the deployment.
You are now ready to go.
Ok, there is a one more thing. If you change the
package.json, and hence need to update the
node_modules, and, for some reason, it does't help, you can open the
Command Palette and run "Rebuild and Reopen in Container".
If there are extensions you like to use, in VS Code, those can be added to the "extensions" property in the
As you will know, you can open the package.json, and see the "Debug" prompt there to be able to debug your application, in-place.
Have fun !!!
About the Author: Navneet Karnani is a Full Stack Ployglot veteran, and explores technology to build great products, always striving to extract more productivity from the tools he uses.