The 'Now You See It, Now You Don't' Container
You hit docker run, the terminal returns a container ID, and everything seems fine. But a second later, your service is unreachable. You run docker ps and see an empty list. Finally, docker ps -a reveals the truth: your container died almost the moment it was born.
# What you see in the terminal
CONTAINER ID IMAGE COMMAND STATUS NAMES
7a1b2c3d4e5f my-app:latest "docker-entrypoint.sā¦" Exited (0) 5 seconds ago my-app-container
Docker containers stay alive only as long as their primary process (PID 1) is running. If that process finishes its task or hits a snag and crashes, the container stops. It is not a bug; it is how Docker is designed to work. Here is how to diagnose and fix the most common causes.
Step 1: Get the Story from the Logs
Don't start changing code blindly. Even if a container is in the 'Exited' state, Docker usually preserves the STDOUT and STDERR streams. Your first move should be checking the last 20 to 50 lines of output.
docker logs --tail 50 <container_id_or_name>
Look for specific clues like stack traces, "Permission denied," or missing configuration files. If the logs are completely empty and you see Exited (0), the container likely performed a quick task (like ls or a short script) and exited because it had nothing left to do.
Step 2: Keep Interactive Containers Alive
Base images like ubuntu, alpine, or debian are built to provide a shell. If you run them without an interactive terminal, the shell sees no input and exits immediately to save resources.
The Quick Fix for One-Off Runs
Use the -it flags. This tells Docker to keep the standard input open and allocate a pseudo-TTY:
docker run -it ubuntu /bin/bash
The Docker Compose Solution
If you are using Compose to build a development environment, add these two lines to your service definition. This mimics the -it behavior and prevents the container from closing the session.
services:
app:
image: ubuntu
tty: true
stdin_open: true
The "Infinite Loop" Trick
Sometimes you need a container to stay up in the background just so you can exec into it later. In these cases, override the default command with a process that never ends:
services:
app:
image: alpine
command: tail -f /dev/null
Step 3: Troubleshooting Exit Code 1 (App Crashes)
An Exited (1) status means the application tried to run but encountered a fatal error. In roughly 90% of cases, the culprit is one of three things:
- Missing Environment Variables: Your app expects a
DATABASE_URLbut it wasn't defined in your environment block. - Volume Permissions: You mounted a local folder to
/var/log/app, but the container user doesn't have permission to write to your host machine. - Incorrect WORKDIR: The
CMDis looking forindex.jsin the root, but yourWORKDIRis set to/usr/src/app.
Pro Tip: Always verify your paths. If you use an entrypoint script, ensure it has the correct execution permissions before building the image:
# Inside your Dockerfile
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Step 4: The Windows-to-Linux Line Ending Trap
If you develop on Windows and run containers on Linux, your scripts might be saved with CRLF line endings. Linux doesn't recognize these. This often results in a confusing "file not found" error, even when the file is clearly visible.
You can fix this by forcing your IDE to use LF endings or by adding dos2unix to your build process:
# A robust way to handle line endings in a Dockerfile
RUN apt-get update && apt-get install -y dos2unix
RUN dos2unix /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Step 5: Use the JSON 'Exec' Form
How you write your CMD matters. Avoid the shell form (CMD node index.js) because it wraps your command in /bin/sh -c. This prevents your app from receiving Unix signals like SIGTERM.
# Preferred: Exec form
CMD ["node", "index.js"]
The Exec form ensures your application is PID 1. This allows for graceful shutdowns and prevents "zombie" processes from hanging around after an error.
How to Verify the Fix
After applying your changes, don't just assume it works because the command didn't error out. Check the uptime after 30 seconds. Use docker ps to ensure the status shows Up 30 seconds (or longer). If you have a health check defined, verify it by running docker inspect --format='{{json .State.Health.Status}}' <container_id>. A status of "healthy" is the ultimate confirmation that your process is actually doing its job.

