Part 1 showed where containers live. This part is the daily vocabulary: run, name, look inside,
read the logs, stop, start, kill and remove, with a PostgreSQL 18 container and a tiny Alpine one on
Docker Desktop 4.95.0. It includes the four things Windows developers hit in the first week: the
-it error from Git Bash, head not existing in PowerShell, Git Bash rewriting a
Linux path inside docker exec, and a database that needed crash recovery because it was
killed instead of stopped. Every output is quoted from the run.
docker run --rm alpine echo hello, then the same without--rm, anddocker ps -adocker run -d --name dw-p2-db -e POSTGRES_PASSWORD=... -p 15820:5432 postgres:18, thendocker logswith--tail,--sinceand-fdocker exec dw-p2-db psql -U postgres -c "select version();"from PowerShell, then with-itfrom Git Bashdocker inspectwith a format string,docker stats --no-stream,docker topdocker stop,start,restartandkill, each followed bydocker logs --tail 5- Start a second container with the same name, read the conflict,
docker rename, thendocker rm -f -v docker run --rm -d --name dw-p2-sleep alpine sleep 300and timedocker stopon it
run, with and without --rm
docker run --rm alpine echo hello from a container printed its line and left nothing
behind. Without --rm the finished container stays:
76ab81362eb1 alpine "echo hello" 1 second ago Exited (0) Less than a second ago frosty_buck
A random name, an exit code, a writable layer and a log kept on disk until docker rm frosty_buck.
For one-off commands, --rm is the habit; for anything you will start again,
--name is.
A named database and its logs
docker run -d --name dw-p2-db -e POSTGRES_PASSWORD=dw-p2-pass -p 15820:5432 postgres:18
returned the container id in under a second, and the table form of docker ps is the
view worth memorising:
dw-p2-db Up 11 seconds 0.0.0.0:15820->5432/tcp, [::]:15820->5432/tcp
The first start takes a few seconds inside the container: initdb, a temporary server, a shutdown and
the real server, ending in database system is ready to accept connections. Two details
about docker logs that scripts get wrong. It keeps the container's two streams apart, and
the main server writes to standard error, so docker logs dw-p2-db 2>$null in PowerShell
shows only the init lines and stops at "init process complete". And --since 1m is
measured against the Docker VM's clock, which ran about five seconds ahead of Windows during this
run, so a window that just misses the last line prints nothing. docker logs -f follows
the log and never exits by itself; a failing query sent two seconds later appeared live:
ERROR: relation "no_such_table" does not exist at character 15
STATEMENT: select * from no_such_table;
exec: inside the container, and the -it error
docker exec dw-p2-db psql -U postgres -c "select version();" from PowerShell printed the
version line, no quoting trouble. Add -it and it depends on where you type it:
cannot attach stdin to a TTY-enabled container because stdin is not a terminal
That message appeared from a Git Bash started by a tool, from mintty with pseudo-console support
disabled, which is what this PC's Git Bash configuration sets, and from any script or pipe. It did not
appear from a PowerShell window, from Git Bash in a plain console window, or from mintty with the
pseudo console enabled. The rule: -t asks for a terminal, so give it one or leave it out.
docker exec -i without -t worked everywhere, and winpty docker exec -it
fixed the mintty case. The Fixes
post on this message is the most read page on this site, which says something about how often
the first week includes it.
Two more things the exec step taught. docker exec dw-p2-db cat /etc/os-release | head -2
fails in PowerShell because head is not a PowerShell command; use
Select-Object -First 2, or run head inside the container. And from Git Bash the
same command failed with cat: 'C:/Program Files/Git/etc/os-release': No such file or directory,
because Git Bash rewrote the Linux path before Docker saw it; Part 3 is about that, and
MSYS_NO_PATHCONV=1 is the fix. Finally, never pipe or redirect the output of
docker exec -it ... psql: without a console size, psql started its pager, the pager
waited for a key that never came, and the command hung.
inspect, stats, top
docker inspect prints two hundred lines of JSON; a format string picks the field. One
the older tutorials use no longer exists on engine 29.8:
template parsing error: template: :1:57: executing "" at <.NetworkSettings.IPAddress>: map has no entry for key "IPAddress"
The working form walks the networks:
{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}, which printed
172.17.0.5. docker stats --no-stream gives one line per container:
2a0d6e999557 dw-p2-db 0.33% 38.75MiB / 15.5GiB 0.24% 97.7kB / 1.18kB 713kB / 43.3MB 9
The 15.5 GiB is the VM's memory from Part 1, not a limit on this container. docker top
lists the processes with the VM's process ids; inside the container the same server is PID 1.
stop, start, restart, kill
docker stop dw-p2-db took half a second and the log ended properly:
LOG: received fast shutdown request
LOG: database system is shut down
PostgreSQL's image declares SIGINT as its stop signal, and the server treats it as a fast
shutdown. docker start brought it back in a third of a second with "Database directory
appears to contain a database; Skipping initialization". docker kill took the same half
second and left no shutdown lines at all; the status was Exited (137), and the next start
had to repair:
LOG: database system was not properly shut down; automatic recovery in progress
Recovery took a second and a half here, with nothing to redo. On a busy database it is longer, and
anything not yet committed is gone. docker rm -f is a kill too, so stop a database
container before removing it. One more way to force a recovery, found by accident: killing stray
processes inside the container with docker exec ... kill. Their parent became PID 1,
which is the postgres server, and it treated the death of an unknown child as a crash and restarted
every backend. Stop the container; do not kill inside it.
Names are unique
docker: Error response from daemon: Conflict. The container name "/dw-p2-db" is already in use by container "2a0d6e9995570e054290436ef618af28962e5f153b04efe56d34440c9702fcf9". You have to remove (or rename) that container to be able to reuse that name.
A stopped container still owns its name. docker rename dw-p2-db dw-p2-db-old freed it,
and the same run command then succeeded. Removing the old one with docker rm -f left its
anonymous data volume behind; docker rm -f -v on the other took the volume with it, the
habit Part 3 explains. The Fixes post
shows the docker ps -a search that finds the owner.
How long docker stop waits
An Alpine container running sleep 300 as PID 1 ignores the stop signal, so
docker stop has to wait and then kill. On this PC it took 3.7 seconds three times in a
row; the daemon's events show SIGTERM, then SIGKILL 3.0 seconds later, and with
docker stop -t 10 the gap was 10.8 seconds. Whatever the default is on your machine, the
lesson is the same: a process that does not handle the signal is killed, and a database that does
handle it shuts down in half a second. --rm on that container removed it the moment it died.
About prune: docker container prune --help says "Remove all stopped
containers". The preview on this PC, docker ps -a --filter status=exited, listed this
part's two leftovers next to three other projects' stopped containers. The test removed its own by
name; Part 8 is where prune gets its rules.
Frequently asked
- Why does docker exec -it fail with cannot attach stdin to a TTY-enabled container?
- Because the shell you typed it in has no Windows console attached: Git Bash under mintty with pseudo-console support off, a script, a pipe, a CI step or an IDE task. Run the same command from a PowerShell window or a console-hosted Git Bash, prefix it with winpty in mintty, or drop -t and use docker exec -i, which works everywhere.
- What is the difference between docker stop and docker kill?
- docker stop sends the container's stop signal, SIGINT for the postgres image, waits for the process to exit, and only then kills it; PostgreSQL logs a fast shutdown and is shut down cleanly in about half a second. docker kill sends SIGKILL at once: no shutdown lines, exit code 137, and the next start runs crash recovery. docker rm -f is a kill as well, so stop a database before removing it.
- Why does Docker say the container name is already in use?
- A container with that name exists, running or stopped, and names are unique. Find it with docker ps -a, then docker rm it, or docker rename it, or start the existing one with docker start. Use docker rm -v so that the anonymous volume an image such as postgres declares is removed with the container.
Next: Part 3, files in and out: bind mounts from PowerShell and Git Bash, named volumes and where their bytes really are, and why the source on drive C: builds slower.