VSCode Live Debugging in CloudFoundry Environment - Node.js version

January 21, 2023 ☼ NestJSCloudfoundryNode.js

Sometime being able to debug and step through your code as your application is running in production, is essential to figure out what’s wrong with your code.

There are plenty of articles how to do so with Node.js and VSCode but there’s a small gotcha when working in CloudFondry.

Tech Stack

I’ve personally applied the following techniques to a Node.js Microservice written in NestJS.

Running the service

When running a Node.js server in CloudFoundry the recommended configuration for your application is similar to the below manifest file:

applications:
  - name: myapp-api
    buildpacks:
      - https://github.com/cloudfoundry/nodejs-buildpack.git#v1.8.4 # lock buildpack with correct node version
    command: node dist/main
    memory: 1024M
    # The below line sets max_old_space_size based on the available memory in the instance. This avoids that instances are occasionally restarted due to memory constraints
    env:
      OPTIMIZE_MEMORY: true
    disk_quota: 512M
    health-check-type: http
    health-check-http-endpoint: /health

You can dig deeper in some of the above configuration parameters in the docs: Tips for Node.js Developers.

How to Live Debug

The Node.js debugger included in VS Code already supports remote debugging.

Here’s a couple of links that will help you with your setup:

Step 1

If you don’t have it already create a new launch.json file in .vscode folder and add the following configuration:

{
  // 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": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach PROD App",
      "address": "localhost",
      "sourceMaps": true,
      "port": 9229,
      "localRoot": "${workspaceFolder}/dist/src/",
      "remoteRoot": "/home/vcap/app/dist/src/",
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

You should adapt localRoot and remoteRoot based on your folder structure. In CF your file will be found in /home/vcap/app/.

Step 2

In order to be able to attach the debugger to a live instance in CF you will need to ssh into the Cell and forward the port to your local environment.

  1. Log into your app container with SSH and forward the node debug port ssh -L 9229:localhost:9229 myapp-api

  2. Send USR1 signal to running node process to enable the debugger.

vcap@c72ef456-f66b-1234-462d-faa3:~$ ps ax | grep node
    PID TTY      STAT   TIME COMMAND
    6 ?        Ssl    0:10 node dist/src/main
vcap@c72ef456-f66b-1234-462d-faa3:~$ kill -usr1 6

Make sure that you have build the same version locally that is deployed and run the new debug target Attach PROD App”

Step 3

Once you are done, just exit the debugging session and stop the ssh port forward from Step 2.

What’s the Gotcha”?

If you try to run the debugger you will notice that after few seconds your container will crash and CF will restart your server.

This is happening because of these 2 lines in your manifest file:

health-check-type: http
health-check-http-endpoint: /health

From the docs Understand Health Checks”

When an app instance becomes healthy, its route is advertised, if applicable. Subsequent health checks are run every 30 seconds once the app becomes healthy.

Your container is crashing because enabling the debugging mode later on shuts down among others the health check thread. Thus, the health check requests by the cf runtime will not be responded to anymore.

If this state remains, the CF runtime assumes the container is unhealthy. It will shut it down and restarts it, thereby also shutting down the debugging session.

The health-check-type: http is the recommended health check type whenever possible.

How do I fix it?

In order to be able to successfully open a debugging session, you have to first set the health check type to process (see the reference documentation):

cf set-health-check myapp-api process

Restart the app as advised.

If you struggle to understand CF log entries have a look at this guide: How to interpret the log entries when an app crashes

Happy debugging! 🚀

Bonus: Health Check Controller with NestJS

The health check controller (currently testing the memory part), will figure out if the server, DB (PostgreSQL) are running and if the memory consumption falls below the set limits.

@Controller("health")
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private postgresHealthIndicator: PostgresHealthIndicator,
    private memory: MemoryHealthIndicator,
    private readonly configService: ConfigService<AppConfig, true>
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      async () => this.postgresHealthIndicator.isHealthy(),
      async () => ({
        api: {
          status: "up",
          version: this.configService.get("apiVersion", { infer: true }),
        },
      }),
      // The process should not use more than 768MB memory
      // This number is calculated from this rule of thumb: https://nodejs.org/api/cli.html#cli_max_old_space_size_size_in_megabytes
      // 75% of 1024M (from manifest file)
      // To be reviewed when stress testing is done
      () => this.memory.checkHeap("memory_heap", 768 * 1024 * 1024),
      // memoryUsage() returns bytes
      () => ({
        stats: {
          status: "up",
          heapTotal: `${process.memoryUsage().heapTotal / (1024 * 1024)} MB`,
          heapUsed: `${process.memoryUsage().heapUsed / (1024 * 1024)} MB`,
          rss: `${process.memoryUsage().rss / (1024 * 1024)} MB`,
        },
      }),
    ]);
  }
}

That’s all folks! :)


If you have any suggestions, questions, corrections or if you want to add anything please DM or tweet me: @zanonnicola