Skaffold’s debug mode doesn’t just start your container with a debugger attached; it actually attaches a debugger to a running container, and the magic is how it handles the ephemeral nature of Kubernetes.

Let’s see this in action. Imagine you have a simple Node.js app you want to debug.

main.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  console.log('Received a request!');
  res.send('Hello from a debuggable container!');
});

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

And a Dockerfile:

FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY main.js ./
EXPOSE 3000
CMD ["node", "main.js"]

Your skaffold.yaml might look like this:

apiVersion: skaffold/v2beta28
kind: Config
metadata:
  name: debug-example
build:
  artifacts:
    - image: my-debug-app
      docker: {}
deploy:
  kubectl:
    manifests:
      - k8s/deployment.yaml
profiles:
  - name: debug
    activation:
      - kubeContext: docker-desktop # Or your K8s context
    patches:
      - op: add
        path: /spec/template/spec/containers/0/ports
        value:
          - containerPort: 9229 # Node.js default debugger port
            protocol: TCP
      - op: replace
        path: /spec/template/spec/containers/0/command
        value: ["node", "--inspect=0.0.0.0:9229", "main.js"]
      - op: replace
        path: /spec/template/spec/containers/0/command
        value: ["node", "--inspect=0.0.0.0:9229", "main.js"]

And your Kubernetes deployment k8s/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: debug-app-deployment
spec:
  selector:
    matchLabels:
      app: debug-app
  replicas: 1
  template:
    metadata:
      labels:
        app: debug-app
    spec:
      containers:
        - name: debug-app
          image: my-debug-app # This will be replaced by Skaffold
          ports:
            - containerPort: 3000

Now, to start debugging, you’d run:

skaffold dev -p debug

Skaffold will build your image, deploy it, and then, crucially, it will find the running container and attach a debugger. It automatically forwards the debugger port (9229 in this case) to your local machine. Your IDE (like VS Code) will then connect to localhost:9229.

The core problem Skaffold solves here is twofold:

  1. Ephemeral Containers: Kubernetes containers are often short-lived. If you just manually kubectl exec into a pod and try to start a debugger, the pod might restart, and your debugging session is lost. Skaffold manages the deployment lifecycle.
  2. Port Forwarding: Debugger ports aren’t usually exposed externally. Skaffold automates the kubectl port-forward process for you, making the debugger accessible locally.

When you run skaffold dev -p debug, here’s what’s happening under the hood:

  • Build: Skaffold builds your Docker image (my-debug-app).
  • Deploy: It applies your Kubernetes manifests. The debug profile modifies the deployment to:
    • Add a containerPort: 9229 to expose the debugger.
    • Override the CMD to node --inspect=0.0.0.0:9229 main.js. This starts the Node.js process with the inspector enabled, listening on all interfaces (0.0.0.0) on port 9229.
  • Attach: Skaffold then watches for your application’s pod to become ready. Once it’s running, Skaffold automatically initiates a port-forward from the remote debugger port (9229 on the pod) to a local port on your machine (usually also 9229 by default).
  • IDE Connect: You configure your IDE to connect to localhost:9229. When you hit a breakpoint, execution pauses, and you can inspect variables, step through code, etc.

The activation block in the profile tells Skaffold when to apply these debug-specific changes. Here, it’s active when your kubeContext is docker-desktop. This is useful for having separate configurations for local development versus CI/CD.

The patches are where the real transformation happens. Skaffold uses strategic merge patches to modify your existing Kubernetes manifest just for this profile. It adds the necessary port and changes the command to enable the Node.js inspector.

What most people don’t realize is that Skaffold doesn’t just modify the initial deployment. If your pod crashes and Kubernetes restarts it, Skaffold detects the new pod and re-establishes the port-forwarding. It’s a continuous debugging experience that survives pod restarts.

Once you’ve got debugging working, the next logical step is to explore how Skaffold handles hot-reloading your code while the debugger is attached.

Want structured learning?

Take the full Skaffold course →