Ravindra BagaleCourses & study guides मराठी

Chapter 49: Docker Containers: Lifecycle, docker run Flags, exec, logs, Restart Policies, Limits and Every Container Command

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.

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.

container startsruns the main process (CMD)main process endsor crashescontainer exitswith that exit codefixkeep a real foreground process
Picture 1: why a container exits

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.

container's localhostthe container itselfhost.docker.internalthe host machine172.17.0.1the docker0 bridge on Linux--network hostshare the host network (Linux only)
Picture 2: who is localhost?

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.

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.

Lab: try at home

  1. Run ubuntu with and without -it and compare.
  2. Run nginx as a daemon and in the foreground.
  3. Make exit codes 126 and 127 and fix them.
  4. Start a host service on 0.0.0.0 and reach it with host-gateway.
  5. 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

  1. Create, start, pause, stop, kill and remove one container; note each status.
  2. Run the pay-api style command with limits and a health check.
  3. Make a container OOM and confirm OOMKilled.
  4. Set log rotation in daemon.json.
  5. 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.

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.

Ravi Sir's Cards for this lesson

मराठी cards