Debugging Distroless Images: A Production Troubleshooting Guide
Learn how to debug distroless images when you lack a shell, package manager, or OS utilities. Master production troubleshooting techniques with Docker.

Previously in this course, we examined how to monitor our services using Container Health Monitoring to automate container restarts. While health checks tell you when a service fails, figuring out why a production service fails inside an ultra-minimal environment is a completely different challenge. This lesson adds practical techniques for debugging distroless images when you have no shell, no package manager, and no standard debugging utilities.
Distroless images—pioneered heavily by Google—contain only your application and its direct runtime dependencies. They omit package managers like apt or apk, standard shells like sh or bash, and core utilities like curl, ps, or netstat. While this drastically shrinks your attack surface and image size, it breaks the classic developer reflex of running docker exec -it <container> bash to poke around.
Let's break down how to diagnose and troubleshoot applications running inside distroless containers without breaking production security guarantees.
Understanding the Distroless Limitation
When a container crashes with a cryptic exit code or fails to connect to a database, developers typically jump inside the container to inspect environment variables, test network ports, or check file permissions. With a distroless base image (such as gcr.io/distroless/nodejs or gcr.io/distroless/java), executing docker exec fails immediately because /bin/sh simply does not exist.
Bash$ docker exec -it secure-app sh OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "sh": executable file not found in $PATH
This is an intended security feature, not a bug. Attackers who achieve remote code execution inside your container cannot escalate privileges or download malicious binaries because the underlying filesystem lacks the tools to do so. However, it forces us to adopt alternative debugging strategies.
| Debugging Method | Traditional Image | Distroless Image |
|---|---|---|
Interactive Shell (docker exec) | Available (bash/sh) | Not Available |
Package Management (apt/apk) | Available | Not Available |
| Build-Time Inspection | Supported | Supported via Multi-Stage |
| Debug Sidecars / Tags | Optional | Required (:debug tags) |
Strategy 1: Using Distroless Debug Tags

Google and other distroless maintainers provide companion -debug image variants for almost every standard distroless image. For instance, if your production Dockerfile uses gcr.io/distroless/nodejs18-debian11, its debugging counterpart is gcr.io/distroless/nodejs18-debian11:debug.
The :debug variant includes a busybox shell located at /busybox/sh (or accessible directly via sh in some versions), along with fundamental debugging utilities like busybox, cat, and ls, while still maintaining the core security posture of the distroless runtime.
To use this for troubleshooting, temporarily modify your image tag or spin up a local debugging container with the exact same entrypoint overridden:
Bashdocker run --rm -it --entrypoint=/busybox/sh gcr.io/distroless/nodejs18-debian11:debug
Once inside this restricted shell, you can inspect the file system layout, verify that configuration files were copied correctly to their expected paths, and check user permissions.
Strategy 2: Multi-Stage Build Inspection
Because most distroless images are built using multi-stage Dockerfiles (as covered in Optimizing Image Size), you can easily inspect what files land in the final image by copying them back to your build stage or a local directory.
Consider a multi-stage Dockerfile where the final stage is distroless:
Dockerfile# Build stage FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM gcr.io/distroless/nodejs18-debian11 WORKDIR /app COPY --from=builder /app/dist /app/dist COPY --from=builder /app/node_modules /app/node_modules CMD ["/app/dist/index.js"]
If your application throws a MODULE_NOT_FOUND error upon startup, the issue is almost certainly a missing file or incorrect path during the copy step. Instead of guessing, add a temporary build stage to output directory contents or run a local node container to verify the compiled output:
Bashdocker run --rm -it -v $(pwd):/workspace node:18-alpine node -e "console.log(require('fs').readdirSync('/workspace/dist'))"
Strategy 3: Ephemeral Debug Containers (Sidecars)
If your application is already running in a containerized environment and you cannot attach a shell directly to the distroless container, you can attach a debug container that shares the target container's network and process namespaces (when using Docker or Kubernetes).
In Docker, you can inspect container volumes and inspect logs extensively using Working with Container Logs. If you need deep process inspection, you can run a diagnostic container with --pid=container:<target_id>:
Bashdocker run -it --rm --pid=container:my-distroless-app nicolaka/netshoot
The netshoot image (or a standard alpine container) provides utilities like ss, nslookup, tcpdump, and strace. Because it shares the process namespace of the target container, you can see what system calls your distroless application is attempting to make and where network requests are timing out.
Hands-on Exercise: Debugging a Failing Distroless App
Let's put this into practice by building a tiny Node.js app that fails due to a missing asset path, then debugging it without a shell.
-
Create a simple application entry point
index.js:JAVASCRIPTconst fs = require(CE9178">'fs'); const path = require(CE9178">'/app/config/settings.json'); // Intentionally strict path console.log("Starting application..."); -
Build a distroless Dockerfile that forgets to copy the
configdirectory:DockerfileFROM gcr.io/distroless/nodejs18-debian11 COPY index.js /app/index.js WORKDIR /app CMD ["index.js"] -
Build and run the container:
Bashdocker build -t broken-distroless . docker run --rm broken-distrolessResult: You'll see a stack trace indicating
MODULE_NOT_FOUNDor file not found errors. -
The Fix & Debug Verification: Switch your base image temporarily to the
:debugtag to verify the filesystem structure:DockerfileFROM gcr.io/distroless/nodejs18-debian11:debug COPY index.js /app/index.js WORKDIR /app CMD ["index.js"]Run it interactively with an alternate entrypoint to list files:
Bashdocker run --rm -it --entrypoint=/busybox/sh broken-distroless /app # ls -laYou'll immediately notice the missing
configfolder, guiding you to fix yourCOPYdirective in the Dockerfile.
Common Pitfalls
- Leaving Debug Tags in Production: Never deploy
:debugdistroless images to production environments. They reintroduce busybox shells and package binaries, defeating the primary security benefit of going distroless. - Assuming Environment Variables Match Host: Distroless images do not ship with common shell configuration files (
.bashrc,/etc/profile). Ensure all required environment variables are injected explicitly via DockerfileENVor runtime flags. - Troubleshooting Glitches with
straceBlindly: While tools likestraceinside a debug sidecar are powerful, remember that compiled languages or heavily optimized runtimes can produce dense system call logs that are difficult to parse without context.
Frequently Asked Questions
Can I install curl into a distroless image at runtime?
No. Distroless images lack package managers like apt, apk, or yum, and they do not contain shell interpreters. If you need health-check requests or diagnostic utilities, build them into your application binary or use multi-stage builds to include static binaries.
How do I inspect environment variables in a distroless container?
If you cannot execute env via a shell, you can inspect environment variables using Docker's inspection command: docker inspect <container_name> --format='{{json .Config.Env}}'.
Are distroless images only for Java and Node.js?
No. Google provides distroless base images for Python, Go, Java, Node.js, and even static C/C++ binaries, as well as a generic base image containing just glibc and tzdata.
Recap

Debugging distroless images requires shifting your workflow from interactive container exploration to multi-stage build validation, debug image tags, and ephemeral sidecar containers. By leveraging these techniques, you retain the elite security and minimal footprint of distroless containers without losing your ability to troubleshoot production failures. For guidance on handling system-level anomalies outside containers, check out Handling System Errors: A Linux Debugging Guide for Developers.
Up next, we will explore advanced multi-container scaling strategies and orchestration best practices.
Work with me

CI/CD Pipeline & Docker Containerization
Ship with confidence: automated CI/CD pipelines and Docker setups so every push is tested and deployed — no more manual, error-prone releases.

VPS Server Setup, Deployment & Hardening
Get your app live on a fast, secure server — properly configured, hardened, and deployment-ready. No more wrestling with the command line.
