Why Does My Docker Container Keep Crashing? Debug Guide
Learn: Why Does My Docker Container Keep Crashing? Debug Guide
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
Why Does My Docker Container Keep Crashing? Debug Guide
Introduction: The 3 AM Wake-Up Call
I'll never forget the night my production Docker container decided to crash every 47 seconds. Like clockwork. I was three coffees deep, staring at my terminal at 3 AM, watching the same container restart loop play out like a broken record. My phone wouldn't stop buzzing with alerts, and I had a product demo scheduled for 9 AM.
Sound familiar?
If you've worked with Docker for more than a week, you've probably experienced that sinking feeling when you run docker ps and your container is nowhere to be found. Or worse—it's there, but the STATUS column mockingly shows "Restarting (1) 3 seconds ago."
Docker containers crash. It's not a matter of if, but when. The good news? Most container crashes follow predictable patterns, and once you know what to look for, debugging becomes significantly less painful. In this guide, I'll walk you through the exact troubleshooting process I use to diagnose and fix crashing containers—the same process that saved my demo that fateful night.
The Problem: When Containers Won't Stay Running
Picture this: You've just finished writing your Dockerfile. You're feeling confident. You run docker build, everything compiles beautifully. You execute docker run, and... the container exits immediately. Or maybe it runs for a few seconds, then dies. Or perhaps it works perfectly on your laptop but crashes instantly in production.
Here's what makes Docker container crashes particularly frustrating:
- Silent failures: Containers can exit without obvious error messages
- Timing issues: Crashes that happen only after specific conditions are met
- Environment differences: Works locally but fails in production
- Resource constraints: Subtle memory or CPU limits causing unexpected behavior
- Dependency problems: Missing files, network issues, or configuration errors
The challenge isn't just fixing the crash—it's figuring out why it crashed in the first place. Unlike traditional applications where you might have extensive logs and debugging tools readily available, containers can disappear along with their runtime state, taking valuable debugging information with them.
Understanding Why Docker Containers Crash
Before we dive into debugging, let's understand the fundamental reasons containers crash. Docker containers aren't like virtual machines—they're processes. When the main process inside your container exits (for any reason), the container stops. Period.
The Main Process Rule
Every Docker container runs a single main process defined by your CMD or ENTRYPOINT. When that process exits, your container exits. This is by design, not a bug.
# This container will exit immediately
FROM ubuntu
CMD echo "Hello World"
The container above will start, print "Hello World," and immediately exit because the echo command completes instantly.
Common Crash Culprits
Application Errors: Your application throws an unhandled exception or encounters a fatal error. This is the most common cause—your code has a bug, and it's crashing the process.
Missing Dependencies: Required files, environment variables, or external services aren't available when the container starts.
Resource Exhaustion: The container runs out of memory, hits CPU limits, or fills up disk space.
Configuration Issues: Incorrect environment variables, missing configuration files, or wrong file permissions.
Health Check Failures: Docker or orchestration tools kill the container because it fails health checks.
Signal Handling: The application doesn't properly handle SIGTERM or other signals, leading to forced kills.
Step 1: Check the Container Status and Logs
The first rule of debugging Docker containers: always check the logs. I can't tell you how many times I've watched developers spend hours troubleshooting when the logs would have given them the answer in 30 seconds.
Viewing Container Logs
# See logs from a running container
docker logs container_name
# Follow logs in real-time
docker logs -f container_name
# See the last 100 lines
docker logs --tail 100 container_name
# Include timestamps
docker logs -t container_name
# For stopped containers, find the container ID first
docker ps -a
docker logs container_id
What to Look For in Logs
When examining logs, I look for these telltale signs:
- Stack traces: Unhandled exceptions or errors
- "Permission denied" messages: File permission issues
- "Connection refused": Network or dependency problems
- "Out of memory" or "Killed": Resource constraints
- Missing file errors: Configuration or dependency issues
Pro Tip: Logs Disappear with Containers
Here's something that caught me off guard early on: if you remove a container with docker rm, you lose its logs. Always check logs before removing crashed containers.
# Keep the container around for debugging
docker run --name my-app my-image
# Instead of docker rm, use docker start to restart it
docker start my-app
Step 2: Inspect the Container Exit Code
Exit codes are your container's last words before it dies. They tell you how the process ended, which often points directly to the cause.
# Check the exit code
docker inspect container_name --format='{{.State.ExitCode}}'
# Or see it in docker ps
docker ps -a
Decoding Exit Codes
| Exit Code | Meaning | Common Causes |
| 0 | Success | Process completed normally (might be intentional) |
| 1 | Application Error | Unhandled exception, application bug |
| 125 | Docker Daemon Error | Problem with Docker itself |
| 126 | Command Not Executable | Permission issues, wrong file format |
| 127 | Command Not Found | Typo in CMD/ENTRYPOINT, missing binary |
| 137 | SIGKILL (Out of Memory) | Container ran out of memory |
| 139 | SIGSEGV (Segmentation Fault) | Application crashed (often C/C++ apps) |
| 143 | SIGTERM | Graceful termination requested |
| 255 | Exit Status Out of Range | Application returned invalid exit code |
Real-World Example
I once spent two hours debugging a Node.js container that kept crashing with exit code 137. I checked the application logs, reviewed the code, tested locally—everything worked fine. Finally, I checked the memory usage and discovered the container was limited to 512MB, but the application needed at least 1GB during startup. One line in my docker-compose.yml fixed it:
services:
app:
image: my-node-app
mem_limit: 2g # Increased from default
Step 3: Run the Container Interactively
Sometimes you need to get inside the container and poke around. Running interactively lets you see exactly what's happening in the container's environment.
Interactive Debugging Techniques
# Override the entrypoint to get a shell
docker run -it --entrypoint /bin/bash my-image
# Or use /bin/sh if bash isn't available
docker run -it --entrypoint /bin/sh my-image
# For Alpine-based images
docker run -it --entrypoint /bin/sh alpine-image
# Execute commands in a running container
docker exec -it container_name /bin/bash
What to Check Inside the Container
Once you're inside, here's my debugging checklist:
# 1. Check if your application binary exists and is executable
ls -la /app
which node # or python, java, etc.
# 2. Try running your application manually
/app/start.sh # or whatever your CMD is
# 3. Check environment variables
env | sort
# 4. Verify file permissions
ls -la /app
ls -la /var/log
# 5. Test network connectivity
ping google.com
curl http://api.example.com
# 6. Check disk space
df -h
# 7. Verify dependencies
npm list # for Node.js
pip list # for Python
The "Works in Interactive Mode" Problem
Here's a gotcha that's bitten me multiple times: your container works perfectly when you run it interactively but crashes when run normally. This usually means:
- Your application expects a TTY: Add
-tflag to your docker run command - Environment variables are missing: Interactive mode might load different profiles
- Timing issues: Interactive mode is slower, hiding race conditions
Step 4: Check Resource Constraints
Docker containers can be limited in CPU, memory, and disk I/O. When containers hit these limits, they crash—often without clear error messages.
Monitoring Resource Usage
# Real-time resource usage
docker stats container_name
# Check container resource limits
docker inspect container_name | grep -A 10 "Memory"
# See if the container was killed due to OOM
docker inspect container_name --format='{{.State.OOMKilled}}'
Setting Appropriate Resource Limits
# Run with memory limits
docker run -m 512m --memory-swap 1g my-image
# CPU limits (1.5 CPUs)
docker run --cpus="1.5" my-image
# In docker-compose.yml
services:
app:
image: my-image
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
Memory Leak Detection
If your container crashes after running for a while, you might have a memory leak:
# Monitor memory over time
watch -n 1 'docker stats --no-stream container_name'
# Check memory usage history (if using monitoring tools)
docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}"
Step 5: Examine the Dockerfile
Sometimes the problem isn't your application—it's how you've containerized it. Let's look at common Dockerfile mistakes that cause crashes.
The Missing Dependency Problem
# BAD: Missing runtime dependencies
FROM node:alpine
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]
# GOOD: Install all dependencies
FROM node:alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
The Wrong Base Image Problem
# BAD: Using a builder image for runtime
FROM golang:1.21
COPY . .
RUN go build -o app
CMD ["./app"]
# GOOD: Multi-stage build with minimal runtime image
FROM golang:1.21 AS builder
COPY . .
RUN go build -o app
FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
CMD ["/app"]
The Permission Problem
# BAD: Running as root, files owned by root
FROM node:alpine
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]
# GOOD: Non-root user with proper permissions
FROM node:alpine
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
WORKDIR /app
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "server.js"]
Step 6: Debug Networking Issues
Network problems are sneaky. Your container might crash because it can't reach a database, API, or other service it depends on.
Testing Network Connectivity
# Check if the container can reach other services
docker exec container_name ping database_host
# Test specific ports
docker exec container_name nc -zv database_host 5432
# Check DNS resolution
docker exec container_name nslookup database_host
# Inspect network configuration
docker network inspect bridge
Common Network Issues
Wrong Network: Containers on different networks can't communicate:
# Create a custom network
docker network create my-network
# Run containers on the same network
docker run --network my-network --name db postgres
docker run --network my-network --name app my-image
Hostname Resolution: Using localhost inside a container refers to the container itself, not your host machine:
# BAD: Won't work in container
DATABASE_URL=localhost:5432
# GOOD: Use service name or host.docker.internal
DATABASE_URL=database:5432 # for docker-compose
DATABASE_URL=host.docker.internal:5432 # to reach host
Port Conflicts: Make sure you're exposing and mapping ports correctly:
# Expose port in Dockerfile
EXPOSE 8080
# Map port when running
docker run -p 8080:8080 my-image
Step 7: Handle Startup Dependencies
One of the most common reasons containers crash in production: they start before their dependencies are ready. Your application tries to connect to a database that's still initializing, fails, and exits.
The Wait-for-it Pattern
# Install wait-for-it script
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh
# Use it in your entrypoint
CMD ["/wait-for-it.sh", "database:5432", "--", "node", "server.js"]
Docker Compose Health Checks
services:
database:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
app:
image: my-app
depends_on:
database:
condition: service_healthy
Application-Level Retry Logic
The most robust solution is building retry logic into your application:
// Node.js example with retry logic
const connectWithRetry = async (maxRetries = 5) => {
for (let i = 0; i < maxRetries; i++) {
try {
await database.connect();
console.log('Database connected');
return;
} catch (err) {
console.log(`Connection attempt ${i + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
throw new Error('Could not connect to database');
};
Step 8: Use Health Checks Effectively
Health checks tell Docker whether your container is actually working, not just running. A container can be "up" but completely non-functional.
Implementing Health Checks
# In Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# In docker-compose.yml
services:
app:
image: my-app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Checking Health Status
# View health status
docker ps
# Detailed health check logs
docker inspect --format='{{json .State.Health}}' container_name | jq
Health Check Best Practices
- Keep it lightweight: Health checks run frequently; don't make them expensive
- Check dependencies: Verify database connections, not just that the process is running
- Set appropriate start periods: Give your app time to initialize
- Return proper exit codes: 0 for healthy, 1 for unhealthy
Advanced Debugging Techniques
When basic troubleshooting doesn't cut it, here are advanced techniques I use for stubborn problems.
Enable Debug Logging
# Run with debug output
docker run -e DEBUG=* my-image
# For Docker daemon debugging
dockerd --debug
# Check Docker daemon logs
journalctl -u docker.service
Use strace to See System Calls
# Install strace in your container
RUN apt-get update && apt-get install -y strace
# Run with strace
docker run --cap-add=SYS_PTRACE my-image strace -f node server.js
Attach a Debugger
# For Node.js
docker run -p 9229:9229 my-image node --inspect=0.0.0.0:9229 server.js
# For Python
docker run -p 5678:5678 my-image python -m debugpy --listen 0.0.0.0:5678 app.py
Check Kernel Messages
# View kernel messages (useful for OOM kills)
dmesg | grep -i docker
# Or from inside container
docker run --privileged my-image dmesg
Comparison Table: Debugging Tools and When to Use Them
| Tool/Command | Best For | When to Use | Limitations |
docker logs | Quick diagnosis | First step, always | Only shows stdout/stderr |
docker inspect | Configuration issues | Checking settings, exit codes | Verbose output |
docker exec | Interactive debugging | Need to explore container | Container must be running |
docker stats | Resource problems | Suspected memory/CPU issues | Real-time only |
docker events | Monitoring lifecycle | Understanding container behavior | Requires active monitoring |
docker run -it | Startup problems | Container exits immediately | Changes runtime environment |
| Health checks | Production monitoring | Ongoing reliability | Adds overhead |
strace | System-level issues | Deep debugging | Performance impact |
Preventing Container Crashes: Best Practices
After debugging hundreds of container crashes, here are the practices that prevent most problems:
1. Implement Proper Error Handling
// Catch unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Don't exit immediately - log and handle gracefully
});
// Catch uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Perform cleanup, then exit
process.exit(1);
});
2. Use Multi-Stage Builds
# Separate build and runtime environments
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
3. Set Resource Limits Proactively
services:
app:
image: my-app
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
restart: unless-stopped
4. Implement Graceful Shutdown
// Handle SIGTERM gracefully
process.on('SIGTERM', async () => {
console.log('SIGTERM received, shutting down gracefully');
// Stop accepting new requests
server.close(() => {
console.log('HTTP server closed');
});
// Close database connections
await database.close();
// Exit cleanly
process.exit(0);
});
5. Use Restart Policies Wisely
# Restart on failure, but not if manually stopped
docker run --restart unless-stopped my-image
# In docker-compose.yml
services:
app:
restart: unless-stopped
FAQ Section
Why does my Docker container exit immediately after starting?
The most common reason is that your main process completes instantly or fails immediately. Check your logs with docker logs container_name and verify your CMD or ENTRYPOINT is running a long-lived process. If you're running a script, make sure it doesn't exit immediately and that it has proper error handling. Also verify that all dependencies are installed and the command you're trying to run actually exists in the container.
How do I debug a Docker container that crashes before I can exec into it?
Override the entrypoint to keep the container running: docker run -it --entrypoint /bin/bash my-image. This gives you a shell instead of running your application, letting you manually test commands and explore the environment. Alternatively, add `tail -f /