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.

Steps in this part
  1. docker run --rm alpine echo hello, then the same without --rm, and docker ps -a
  2. docker run -d --name dw-p2-db -e POSTGRES_PASSWORD=... -p 15820:5432 postgres:18, then docker logs with --tail, --since and -f
  3. docker exec dw-p2-db psql -U postgres -c "select version();" from PowerShell, then with -it from Git Bash
  4. docker inspect with a format string, docker stats --no-stream, docker top
  5. docker stop, start, restart and kill, each followed by docker logs --tail 5
  6. Start a second container with the same name, read the conflict, docker rename, then docker rm -f -v
  7. docker run --rm -d --name dw-p2-sleep alpine sleep 300 and time docker stop on 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.