Docker Container Exits Immediately? Here's the Fix
You've just run docker run and—nothing. The container starts, runs for a split second, and exits with code 0. What happened?
This is one of the most common Docker frustrations, especially for developers new to containerization. The good news? It's usually easy to fix once you understand what's happening.
Why Your Container Keeps Stopping
Here's the fundamental rule: A Docker container lives only as long as its primary process (PID 1).
When that process completes or crashes, the container stops. This is by design—containers aren't virtual machines. They're isolated processes with their own filesystem.
Common causes:
- Short-lived commands — Running a script that finishes immediately
-
Background processes — Starting a daemon that daemonizes itself (like
nginxorredis-server) - Crashes — The application encounters an error and exits
- Missing arguments — The container expects runtime arguments you didn't provide
Let's walk through diagnosing and fixing each scenario.
Step 1: Diagnose with Docker Logs
Before fixing anything, find out why the container stopped:
# Get the container ID (even stopped ones)
docker ps -a
# View the logs
docker logs <container_id>
The output usually tells the story:
Hello from Node.js!
Server started on port 3000
Connection established
Process completed
If you see "Process completed" or similar, your app ran and finished normally. If you see error messages, it crashed. If you see nothing, the process might have daemonized itself.
Pro tip: Use docker logs -f <container_id> to follow logs in real-time while the container is running.
Step 2: Fix Based on Your Scenario
Scenario A: Your Script Finishes Too Quickly
You're running a Node.js script, Python file, or shell script that does its job and exits.
Wrong approach:
docker run my-image node script.js
# Container exits immediately
Solution 1: Keep it running with an interactive shell
docker run -it my-image sh
# Now you're inside, can run commands manually
Solution 2: Add a long-running process
FROM node:20-alpine
COPY script.js .
CMD ["sh", "-c", "node script.js && tail -f /dev/null"]
The tail -f /dev/null trick keeps the container alive after your script finishes—useful for debugging or when you need the container to persist.
Solution 3: Run as a service
FROM node:20-alpine
COPY server.js .
CMD ["node", "server.js"]
Where server.js is a long-running server (Express, Fastify, etc.) that doesn't exit on its own.
Scenario B: Background Process Daemonizes
Some applications start as daemons by default—they fork into the background and the main process exits.
Wrong approach:
# nginx daemonizes by default
CMD ["nginx"]
Solution: Run in foreground mode
Most daemons have a flag to stay in the foreground:
# Nginx
CMD ["nginx", "-g", "daemon off;"]
# Redis
CMD ["redis-server", "--daemonize", "no"]
# Apache
CMD ["apache2ctl", "-D", "FOREGROUND"]
This is the recommended pattern—let Docker manage the process lifecycle, not the application.
Scenario C: Application Crashes
If your logs show errors, fix the underlying issue:
# Check logs for stack traces
docker logs <container_id>
# Run interactively to debug
docker run -it --rm my-image sh
# Now you can run commands, check files, etc.
Common crash causes:
- Missing environment variables
- Database connection failures
- Permission issues (try not running as root)
- Missing dependencies (did you install them in the Dockerfile?)
Step 3: Use Restart Policies
Even with correct configuration, things can go wrong. Docker restart policies automatically restart containers when they stop.
# Always restart
docker run --restart always my-image
# Restart unless manually stopped
docker run --restart unless-stopped my-image
# Restart only on failure (max 5 retries, delay 10s)
docker run --restart on-failure:5 my-image
Restart policy comparison:
| Policy | Starts on daemon start? | Restarts on failure? | Use case |
|---|---|---|---|
no |
No | No | Development, testing |
on-failure |
No | Yes (with limit) | Services that might crash |
always |
Yes | Yes | Production services |
unless-stopped |
Yes (if running before) | Yes | Production services (preferred) |
For most production workloads, unless-stopped is the best choice—it restarts on failure and on Docker daemon restart, but respects manual stops.
Real-World Scenarios
Development: Keep a Database Running
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: development
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
The restart: unless-stopped ensures your database comes back up after crashes or system reboots.
CI/CD: Run Tests and Exit
For CI pipelines, you want the container to exit:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "test"]
The test runs, results are collected, and the container exits—exactly what CI needs.
Production: Graceful Shutdown
For long-running services, handle shutdown signals properly:
// Node.js example
const server = app.listen(3000);
process.on('SIGTERM', () => {
console.log('Shutting down gracefully...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
Docker sends SIGTERM on stop, waits (default 10s), then sends SIGKILL. Handling SIGTERM lets your app finish in-flight requests.
FAQ
Why does my container exit with code 0?
Code 0 means "success"—your process completed normally. This usually means you're running a one-off command instead of a long-running service. Either change your entrypoint to a server/daemon, or add && tail -f /dev/null to keep it alive.
Why does my container exit with code 1?
Code 1 indicates an error. Run docker logs <container_id> to see what went wrong. Common causes: missing dependencies, invalid configuration, database connection failures.
How do I keep a container running for debugging?
Use an interactive shell:
docker run -it --rm my-image sh
# or
docker run -it --rm --entrypoint sh my-image
The -t flag allocates a pseudo-TTY (required for shell interaction), and -i keeps STDIN open.
What's the difference between CMD and ENTRYPOINT?
-
CMD: Default arguments, easily overridden withdocker run my-image arg1 arg2 -
ENTRYPOINT: The executable, harder to override (needs--entrypointflag)
Best practice: Use ENTRYPOINT for the executable and CMD for default arguments:
ENTRYPOINT ["node"]
CMD ["server.js"]
Now docker run my-image runs node server.js, but docker run my-image other.js runs node other.js.
My container works locally but exits in production. Why?
Common differences:
- Environment variables: Are all required env vars set in production?
- Networking: Can the container reach required services?
- Volumes: Are volume paths correct and accessible?
- Permissions: Is the container running as a user that can access required files?
Always test with production-like configuration locally:
docker run --env-file .env.production -v ./data:/app/data my-image
Conclusion
When your Docker container exits immediately, remember the golden rule: PID 1 determines container lifetime.
Your fix depends on the scenario:
-
Script finishes too fast? Add a server or use
tail -f /dev/null - Daemon daemonizes? Use foreground mode flags
- App crashes? Check logs and fix the root cause
- Want resilience? Add restart policies
The docker logs command is your best friend—it almost always reveals what went wrong. Start there, apply the appropriate fix, and you'll have containers that stay running when they should (and exit cleanly when they shouldn't).
Next time your container disappears, don't panic. Check the logs, fix PID 1, and get back to shipping.
Top comments (0)