49.11 Container exits immediately, and reaching the host (host.docker.internal on Linux)
Friends, the biggest beginner question: why does my container stop as soon as it starts? And second: how do I reach the database on my laptop from a container? Today we solve both, one by one.
मित्रांनो, beginners चा सगळ्यात मोठा प्रश्न: माझा container चालू होताच बंद का होतो? आणि दुसरा: container मधून laptop वरचा database कसा गाठायचा? आज दोन्ही प्रश्न एकेक करून सोडवू.
दोस्तों, beginners का सबसे बड़ा सवाल: मेरा container शुरू होते ही बंद क्यों हो जाता है? और दूसरा: container से laptop का database कैसे पहुँचें? आज दोनों एक-एक करके सुलझाएँगे.
1. Start from a story
A student in Pune runs docker run -d ubuntu and then docker ps. Nothing is there. She runs it five times. Still nothing. She thinks Docker is broken.
Think of a taxi driver. A taxi runs only while there is a trip. When the trip ends, the driver goes home. A container is the same: it runs only while its main process runs. When the main process ends, the container exits. ubuntu's main process is bash, and bash with no terminal ends at once.
The second puzzle: her app in a container must talk to MySQL running on her laptop. localhost inside the container is the container itself, not the laptop. We need the special name host.docker.internal.
2. Why containers exit
Step 1: No foreground process
Why: The most common case: the image's command finishes at once.
What: A container lives as long as PID 1 lives. bash with no terminal, echo, or a script that ends will stop the container.
Example 1: ubuntu exits at once
Type this:
docker run -d --name u1 ubuntu:24.04
docker ps -a --filter name=u1 --format '{{.Status}}'
You will see:
...
Exited (0) 1 second ago
What it means: bash had no input, so it ended with code 0.
Example 2: What was the command
Type this:
docker inspect u1 --format '{{.Config.Cmd}}'
You will see:
[/bin/bash]
What it means: The default command is bash.
Example 3: Give it a terminal
Type this:
docker run -dit --name u2 ubuntu:24.04
docker ps --filter name=u2 --format '{{.Status}}'
You will see:
...
Up 3 seconds
What it means: With -it, bash waits for input and stays alive. Good for practice, not for apps.
Step 2: Services that go to the background
Why: Old habits from servers: the app starts itself as a daemon.
What: If the main process starts the real app in the background and then ends, the container exits. Run the app in the foreground.
Example 4: nginx as a daemon
Type this:
docker run -d --name n1 nginx:1.27 nginx
docker ps -a --filter name=n1 --format '{{.Status}}'
You will see:
...
Exited (0) 1 second ago
What it means: nginx went to the background, and the foreground process ended.
Example 5: nginx in the foreground
Type this:
docker run -d --name n2 nginx:1.27 nginx -g 'daemon off;'
docker ps --filter name=n2 --format '{{.Status}}'
You will see:
...
Up 2 seconds
What it means: daemon off keeps it in the foreground. This is the image's default CMD.
Example 6: Same for others
Type this:
docker run --rm redis:7 redis-server --help | grep -m1 daemonize || echo 'redis runs in the foreground by default in Docker'
You will see:
redis runs in the foreground by default in Docker
What it means: Official images already run in the foreground.
Step 3: The app crashed
Why: Sometimes the app starts and then fails.
What: A non-zero exit code means the app failed. The answer is almost always in docker logs.
Example 7: A crashing app
Type this:
docker run -d --name c1 -e DB_HOST=nowhere python:3.12-slim python -c 'import socket,os; socket.create_connection((os.environ["DB_HOST"],5432),2)'
You will see:
...
What it means: It needs a database.
Example 8: Read the exit code
Type this:
docker inspect c1 --format '{{.State.ExitCode}}'
You will see:
1
What it means: App error.
Example 9: Read the logs
Type this:
docker logs c1 2>&1 | tail -1
You will see:
socket.gaierror: [Errno -2] Name or service not known
What it means: The DB host is wrong. Fix the setting, not Docker.
Step 4: Wrong command or missing file
Why: The container exits with 126 or 127.
What: 127 means the command was not found. 126 means it was found but cannot run, often because the script is not executable or has Windows line endings.
Example 10: Not found
Type this:
docker run --name c2 alpine:3.20 bash; docker inspect c2 --format '{{.State.ExitCode}}'
You will see:
docker: Error response from daemon: ... exec: "bash": executable file not found in $PATH
127
What it means: Alpine has sh, not bash.
Example 11: Not executable
Type this:
printf '#!/bin/sh\necho hi\n' > run.sh; chmod 0644 run.sh
docker run --name c3 -v $PWD/run.sh:/run.sh alpine:3.20 /run.sh; docker inspect c3 --format '{{.State.ExitCode}}'
You will see:
... permission denied
126
What it means: Fix with chmod 0755 run.sh, or RUN chmod 0755 in the Dockerfile.
Example 12: Windows line endings
Type this:
printf '#!/bin/sh\r\necho hi\r\n' > win.sh; chmod 0755 win.sh
docker run --rm -v $PWD/win.sh:/win.sh alpine:3.20 /win.sh
You will see:
exec /win.sh: no such file or directory
What it means: The hidden \r breaks the first line. Fix with sed -i 's/\r$//' win.sh.
Step 5: Debug an exiting container
Why: You need to look inside, but it exits before you can exec.
What: Override the entrypoint with a shell, or with a long sleep, then look around.
Example 13: Open a shell instead
Type this:
docker run -it --rm --entrypoint sh python:3.12-slim
You will see:
#
What it means: You are inside the same image.
Example 14: Keep it alive for exec
Type this:
docker run -d --name dbg --entrypoint sleep python:3.12-slim infinity
docker exec -it dbg sh
You will see:
#
What it means: Now you can exec as long as you like.
Example 15: Run the real command by hand
Type this:
python -c 'print(1)'
You will see:
1
What it means: Run each step yourself and watch for the error.
3. Keep it running the right way
Step 6: Good and bad ways to stay alive
Why: Online answers say: add tail -f /dev/null.
What: tail -f /dev/null keeps a container alive, but it is a hack for debug only. A real container runs one real service in the foreground.
Example 16: Debug hack
Type this:
docker run -d --name hack alpine:3.20 tail -f /dev/null
You will see:
...
What it means: Alive, but does nothing useful.
Example 17: Real service
Type this:
docker run -d --name real -p 8090:80 nginx:1.27
You will see:
...
What it means: Alive because nginx is serving.
Example 18: Remove the hack
Type this:
docker rm -f hack u1 u2 n1 n2 c1 c2 c3 dbg
You will see:
...
What it means: Never ship tail -f /dev/null.
4. Reaching the host from a container
Now the second puzzle. The student fixed her container, and the app stays up. But it cannot reach MySQL on her laptop. This is not a Docker bug. It is about who localhost is.
Step 7: localhost is the container
Why: The app connects to localhost:3306 and fails.
What: Each container has its own network. localhost inside it means the container, not your laptop or server.
Example 19: A service on the host
Type this:
python3 -m http.server 7000 --bind 0.0.0.0 &
You will see:
Serving HTTP on 0.0.0.0 port 7000
What it means: A test service on the host.
Example 20: Try localhost from a container
Type this:
docker run --rm alpine:3.20 wget -qO- -T 2 localhost:7000
You will see:
wget: can't connect to remote host (127.0.0.1): Connection refused
What it means: Nobody listens on port 7000 inside the container.
Example 21: It works on the host
Type this:
curl -s localhost:7000 | head -c 15
You will see:
<!DOCTYPE HTML>
What it means: The service is fine; the address was wrong.
Step 8: host.docker.internal on Linux
Why: Docker Desktop on Mac and Windows has this name built in. Linux does not, by default.
What: On Linux, add --add-host=host.docker.internal:host-gateway. Docker maps the name to the host's bridge IP.
Example 22: Without the flag on Linux
Type this:
docker run --rm alpine:3.20 wget -qO- -T 2 host.docker.internal:7000
You will see:
wget: bad address 'host.docker.internal'
What it means: The name is unknown.
Example 23: With host-gateway
Type this:
docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.20 wget -qO- -T 2 host.docker.internal:7000 | head -c 15
You will see:
<!DOCTYPE HTML>
What it means: It reached the host.
Example 24: See the mapping
Type this:
docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.20 grep host.docker /etc/hosts
You will see:
172.17.0.1 host.docker.internal
What it means: host-gateway became 172.17.0.1.
Step 9: The host must listen on the right address
Why: It still fails, even with host.docker.internal.
What: If the host service listens only on 127.0.0.1, containers cannot reach it. It must listen on 0.0.0.0 or the docker0 IP. A firewall may also block it.
Example 25: Service on 127.0.0.1 only
Type this:
python3 -m http.server 7001 --bind 127.0.0.1 &
You will see:
Serving HTTP on 127.0.0.1 port 7001
What it means: Only the host itself can reach it.
Example 26: Container fails
Type this:
docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.20 wget -qO- -T 2 host.docker.internal:7001
You will see:
wget: can't connect to remote host (172.17.0.1): Connection refused
What it means: Wrong bind address on the host.
Example 27: For MySQL
Type this:
sudo grep bind-address /etc/mysql/mysql.conf.d/mysqld.cnf
You will see:
bind-address = 127.0.0.1
What it means: Change it to 0.0.0.0 and allow only the Docker subnet in the firewall.
Step 10: Better: put the database in a container too
Why: Reaching the host is fine for practice, but teams at Zoho run the database as a second container on the same network.
What: On a user-made network, containers reach each other by name. No host IP, no bind-address change, no firewall rule.
Example 28: Network and MySQL
Type this:
docker network create devnet
docker run -d --name mysql --network devnet -e MYSQL_ROOT_PASSWORD=example mysql:8.4
You will see:
...
What it means: The example password is for practice only.
Example 29: Connect by name
Type this:
sleep 20; docker run --rm --network devnet mysql:8.4 mysqladmin -h mysql -uroot -pexample ping
You will see:
mysqld is alive
What it means: The name mysql works like a hostname.
Example 30: Clean up
Type this:
docker rm -f mysql; docker network rm devnet
You will see:
mysql
devnet
What it means: Chapter 51 covers networks in depth.
Step 11: Compose and --network host
Why: Teams want the same setup for everyone.
What: In Compose, use extra_hosts with host-gateway. On Linux, --network host shares the host network, so localhost is the host, but you lose isolation and -p.
Example 31: Compose extra_hosts
Type this:
printf 'services:\n app:\n image: alpine:3.20\n command: wget -qO- host.docker.internal:7000\n extra_hosts:\n - "host.docker.internal:host-gateway"\n' > compose.yaml
docker compose run --rm app | head -c 15
You will see:
<!DOCTYPE HTML>
What it means: Works for every teammate.
Example 32: Host network
Type this:
docker run --rm --network host alpine:3.20 wget -qO- localhost:7001 | head -c 15
You will see:
<!DOCTYPE HTML>
What it means: localhost is the host now, even for the 127.0.0.1 service.
Example 33: Clean up
Type this:
kill %1 %2 2>/dev/null; docker rm -f real; rm -f run.sh win.sh compose.yaml
You will see:
...
What it means: All clean.
Check yourself
Q1. Why does docker run -d ubuntu exit at once?
bash has no input, so the main process ends.
Q2. What does exit code 126 often mean?
The script is not executable or has Windows line endings.
Q3. How do you debug a container that exits at once?
Run it with --entrypoint sh or sleep and look inside.
Q4. What is localhost inside a container?
The container itself.
Q5. How do you get host.docker.internal on Linux?
--add-host=host.docker.internal:host-gateway.
Friends, a container is like a taxi: when the trip (the main process) ends, it goes home. And localhost inside a container is the container itself; for the host use host.docker.internal and host-gateway. Did you understand? Let us try it once more. Run ubuntu like the Pune student and fix it yourself.
मित्रांनो, container taxi सारखा: trip (main process) संपली की घरी जातो. आणि container मधला localhost म्हणजे container स्वतः. Host साठी host.docker.internal आणि host-gateway. समजलं का? चला, पुन्हा एकदा करून पाहू. Pune च्या student सारखं ubuntu चालवून स्वतः fix करा.
दोस्तों, container taxi जैसा है: trip (main process) खत्म, तो घर चला जाता है. और container का localhost मतलब container खुद. Host के लिए host.docker.internal और host-gateway. समझ आया? चलिए, एक बार फिर करके देखते हैं। Pune के student की तरह ubuntu चलाकर खुद fix कीजिए.
Ravindra Bagale's Tip
When a container stops, first check the exit code and docker logs. Five codes are enough: 0, 1, 126, 127, 137.
Container बंद झाला की आधी exit code आणि docker logs बघा. 0, 1, 126, 127, 137 हे पाच codes पुरेसे आहेत.
Container बंद हो तो पहले exit code और docker logs देखिए. 0, 1, 126, 127, 137 ये पाँच codes काफ़ी हैं.
Lab: try at home
- Run ubuntu with and without -it and compare.
- Run nginx as a daemon and in the foreground.
- Make exit codes 126 and 127 and fix them.
- Start a host service on 0.0.0.0 and reach it with host-gateway.
- Try a 127.0.0.1-only service and see why it fails.
Recap
- A container lives as long as its main process.
- Run services in the foreground.
- Read exit codes and logs to find why it stopped.
- localhost inside is the container.
- On Linux, add host.docker.internal with host-gateway.
Lab
- Create, start, pause, stop, kill and remove one container; note each status.
- Run the pay-api style command with limits and a health check.
- Make a container OOM and confirm OOMKilled.
- Set log rotation in daemon.json.
- Use docker events while restarting a container.
Practice task
Run Redis with --memory 100m, --restart unless-stopped, a health check and log rotation. Reboot the host and prove it came back healthy.
Ravindra Bagale's Tip
Remember the exit codes: 0 is good, 1 is an app error, 137 is a kill or OOM, 143 is SIGTERM. Interviewers ask this.
Exit code लक्षात ठेवा: 0 चांगला, 1 app error, 137 kill किंवा OOM, 143 SIGTERM. Interview मध्ये विचारतात.
Exit code याद रखिए: 0 अच्छा, 1 app error, 137 kill या OOM, 143 SIGTERM. Interview में पूछते हैं.
In short
- Containers move through created, running, paused, exited and removed.
- docker run flags set ports, env, volumes, network, user and limits.
- logs first, exec second.
- Restart policies restart crashes, not bugs.
- Limits protect neighbours.
- Rotate logs or the disk fills.
Revision card
Interview card