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:

npx express-generator

You will see the output as this:

Express Generator Output

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: code .

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.

VS Code Extensions list

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.

VS Code Generate Docker workflow

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 npm install

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: From docker-compose.debug.yml

VSCode Generate DevContainer Config

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"

Cleaning up

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 Dockerfile and name it Dockerfile.debug
  • Edit the Dockerfile.debug and do the following: -- Remove the -alpine in 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 docker-compose.debug.yml file.

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 Dockerfile.debug and 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 .devcontainer/devcontainer.json file
  • Find workspaceFolder and change the value from /workspace to /usr/src/app

And, we also need to update the location that the local sources will be mounted on-to:

  • Open the .devcontainer/docker-compose.yml file
  • Look for the volumes section
  • The comment there clearly calls out: Update this to wherever you want VS Code to mount the folder of your project
  • So, change /workspace to /usr/src/app

Here are the file changes:

vscode-change-dev-files.gif

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".

Niceties

If there are extensions you like to use, in VS Code, those can be added to the "extensions" property in the .devcontainer/devcontainer.json file.

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.

No Comments Yet