How to Fix: Debugging within Docker container

5 min read

When Next.js is started inside a Docker container with NODE_OPTIONS='–inspect=0.0.0.0', debugging can fail in a confusing way: the dev server starts, but the Node inspector either does not bind correctly, becomes unreachable from the host, or conflicts with how next dev launches its process tree. The fix is not just “enable inspect” — it is making sure the debugger port, host binding, container port publishing, and process startup mode all agree.

Understanding the Root Cause

This issue happens because debugging inside containers depends on multiple layers working together:

  • The Node.js inspector must listen on a network-accessible interface such as 0.0.0.0, not only 127.0.0.1.
  • The container must publish the inspector port, usually 9229, to the host.
  • next dev must not swallow, override, or duplicate the inspect flag when spawning internal processes.
  • If another process already uses port 9229, Node may silently increment ports or fail to expose the debugger where you expect it.

In practice, the bug described in the Next.js issue is triggered when NODE_OPTIONS injects –inspect=0.0.0.0 into the runtime inside Docker. That sounds correct, but it can still break because the inspector needs a complete, explicit binding model:

  • Host binding: 0.0.0.0 allows external access from outside the container namespace.
  • Port binding: if you omit the port, Node defaults to 9229, but your Docker run or Compose file must also expose 9229.
  • Process topology: development servers often restart, fork, or wrap Node processes. If the inspect option is attached to the wrong process, your IDE connects to nothing useful.

So the root cause is usually a mismatch between Node inspector configuration and Docker networking, sometimes amplified by how Next.js development mode manages subprocesses.

Step-by-Step Solution

The most reliable fix is to make the debugger configuration fully explicit.

1. Start Next.js with an explicit inspect host and port

Instead of using only –inspect=0.0.0.0, include the port too:

NODE_OPTIONS='--inspect=0.0.0.0:9229' next dev

This removes ambiguity and ensures the inspector binds to the expected port.

2. Publish both the app port and debugger port from Docker

If you run the container manually, expose both ports:

docker run --rm -it \
  -p 3000:3000 \
  -p 9229:9229 \
  -e NODE_OPTIONS='--inspect=0.0.0.0:9229' \
  your-image-name \
  next dev

If your image already starts the app via its default command, move the environment variable into the container config:

docker run --rm -it \
  -p 3000:3000 \
  -p 9229:9229 \
  -e NODE_OPTIONS='--inspect=0.0.0.0:9229' \
  your-image-name

3. Use Docker Compose if you want a repeatable local debugging setup

services:
  web:
    build: .
    command: next dev
    environment:
      NODE_OPTIONS: --inspect=0.0.0.0:9229
    ports:
      - "3000:3000"
      - "9229:9229"
    volumes:
      - .:/app

This keeps the inspector available every time the container starts.

4. Confirm that Node is actually listening inside the container

Open a shell in the running container and inspect the process:

ps aux | grep node
netstat -tulpn | grep 9229

If 9229 is not listening, the inspect option is not reaching the process that runs Next.js.

5. Attach from your IDE using the mapped host port

In VS Code, use an attach configuration like this:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to Next.js in Docker",
      "address": "localhost",
      "port": 9229,
      "localRoot": "${workspaceFolder}",
      "remoteRoot": "/app",
      "protocol": "inspector",
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

The critical parts are address, port, and the localRoot/remoteRoot path mapping. If path mapping is wrong, breakpoints appear unbound even when the debugger is connected.

6. Prefer direct Node startup if wrapper behavior interferes

If next dev is being launched through another script that strips environment variables, run it from a shell script or package script explicitly:

{
  "scripts": {
    "dev:debug": "NODE_OPTIONS='--inspect=0.0.0.0:9229' next dev"
  }
}

Then start that command inside the container:

npm run dev:debug

7. Validate the debugger endpoint from the host

After the container starts, verify the inspector is reachable by checking the endpoint exposed by Node:

curl http://localhost:9229/json/list

If this returns inspector metadata, the debugger is correctly exposed and your remaining issue is likely in the IDE configuration rather than Docker or Next.js.

docker run --rm -it \
  -v "$PWD":/app \
  -w /app \
  -p 3000:3000 \
  -p 9229:9229 \
  node:current \
  sh -c "npm install && NODE_OPTIONS='--inspect=0.0.0.0:9229' npx next dev"

This pattern makes the debug host, debug port, and working directory explicit, which is the safest route for containerized development.

Common Edge Cases

Port 9229 is already in use

If another local Node process is already using 9229, your container may fail to publish the port or the inspector may bind to an unexpected fallback port. Change it explicitly:

NODE_OPTIONS='--inspect=0.0.0.0:9230' next dev

Then publish that same port from Docker.

Breakpoints never bind

This is usually a source mapping or path mapping issue, not a Docker networking issue. Verify that remoteRoot matches the app directory inside the container, such as /app.

Debugger attaches to the wrong process

In development mode, file watching and restarts can create multiple Node processes. Your IDE may attach to a bootstrap process rather than the one executing application code. Use the inspector endpoint and process list to confirm which process owns the active debug session.

Container starts, but inspector is reachable only from inside the container

This usually means Node is listening on 127.0.0.1 instead of 0.0.0.0, or Docker did not publish the port. Both conditions must be fixed together.

Hot reload restarts the debug session

This is expected in some dev workflows. Configure your IDE with restart support so it reattaches automatically after recompilation or process restart.

Using Alpine or minimal images without debug tools

Some minimal images do not include utilities like netstat. Use alternatives such as ss, or temporarily install diagnostic tools during troubleshooting.

FAQ

Why is –inspect=0.0.0.0 not enough by itself?

Because it sets the listen address but still depends on correct port exposure, process inheritance, and Docker publishing rules. Adding :9229 makes the setup deterministic.

Should I use –inspect or –inspect-brk?

Use –inspect if you want the app to start normally and attach when ready. Use –inspect-brk if you need to pause immediately on startup and debug initialization code.

Can this happen only with Next.js?

No. The same class of bug appears in many Node.js apps running in Docker. Next.js makes it more noticeable because dev mode often involves watchers, restarts, and layered process execution.

For teams working on containerized Next.js development, the safest fix is to treat debugging as a full networked setup: bind the Node inspector to 0.0.0.0, specify the port explicitly, publish that port from Docker, and attach your IDE using correct path mappings. That resolves the underlying mismatch exposed by this issue and makes debugging inside the container predictable again.

Leave a Reply

Your email address will not be published. Required fields are marked *