The core commands
run, ps, stop, rm, images.
There is a box of frozen lasagna in your freezer. The box never changes. Heat one up, slide it onto a plate, and that hot plate of food is a copy you can eat, bin, and make again tomorrow from the same box. Docker splits the world the same way. The box is an image: a frozen template holding your app and everything it needs to run. Docker calls an image read-only, which means you can start copies from it but you can never change the thing itself. The hot plate is a container: one running copy started from that image. A single image can feed many containers at once, each one running separately from the others. Five commands cover nearly everything you will ever do with the two of them, and they are worth wearing into reflexes. So follow one container through its whole life, from the second it starts to the moment you sweep it away.
Start one up
The command that starts a container is docker run. Hand it an image name and Docker takes it from there. The first time you ask for an image your machine has never seen, Docker fetches it from a registry, which is an online store for images. The default store is Docker Hub. The output labels that step 'Pulling', because pull is Docker's word for downloading an image. In the command below, nginx:1.27 is an image name plus a tag. nginx is a small and very popular web server. The tag is the version label, so 1.27 pins that exact version. The -d flag stands for detached: run in the background and give your prompt back straight away so you can keep typing. The --name web part pins a name you will actually remember, instead of the random one Docker would otherwise invent. All of that work happens inside the Docker daemon, the background service (also called the Docker Engine) that does the pulling, creating and running. The command you type only carries the orders to it.
$ docker run -d --name web nginx:1.27Unable to find image 'nginx:1.27' locally1.27: Pulling from library/nginxa2abf6c4d29d: Pull completee5c7b8f1a4c9: Pull completeb0a2c3d4e5f6: Pull complete1f2e3d4c5b6a: Pull completeDigest: sha256:2ef4c8b1a09d7f6e5c4b3a2918f7e6d5c4b3a2918f7e6d5c4b3a2918f7e6d5Status: Downloaded newer image for nginx:1.277f8e9d0a1b2c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f70819a2b3$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES7f8e9d0a1b2c nginx:1.27 "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 80/tcp web
Alive, stopped, gone
docker ps is your window onto what is alive right now. By default it lists running containers only, so your nginx shows up as Up. Two things catch people out here. First, a container that crashed or finished its work does not appear in plain docker ps at all. Add -a (short for all), and docker ps -a shows the stopped ones too, which is exactly how you track down a container that died half a second after it started. Second, stopping a container is a different act from deleting it. docker stop asks the app to shut down politely. It sends a signal, meaning a short built-in message from the operating system to a program, called SIGTERM (terminate). Then it waits about ten seconds for the app to finish what it was doing, and only then forces the matter with a harder signal called SIGKILL. So a stop that seems to hang for ten seconds is not broken. It is being patient. Once it returns, the container is still sitting there in the Exited state, holding on to its logs and its name, until you remove it with docker rm.
$ docker stop webweb$ docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES7f8e9d0a1b2c nginx:1.27 "/docker-entrypoint.…" 2 minutes ago Exited (0) 8 seconds ago web$ docker rm webweb
Skip the stop and you will meet one of the most common beginner errors. Docker blocks the removal of a running container on purpose, so nothing ever yanks a live app out from under you. The message tells you the fix in plain words. Stop first, remove second.
$ docker run -d --name api nginx:1.27c4d5e6f7a8b90c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d$ docker rm apiError response from daemon: cannot remove container "/api": container is running: stop the container before removing or force remove$ docker stop api && docker rm apiapiapi
What the STATUS column is telling you
The STATUS column repays a second look, because that is where you diagnose almost everything. Up 3 seconds means the container is running. Exited (0) means the program finished on its own with nothing wrong, the way a script ends once it has done its job. Exited (1), or any other non-zero number, means it crashed or hit a problem, and that number is your first clue about what went wrong. When somebody says a container 'won't start', nine times out of ten it started perfectly well and then exited within a second. Begin with docker ps -a and that exit code. Then run docker logs web, which prints whatever the container wrote out before it quit, and the rest of the story is usually right there.
The image outlives the container
Removing a container clears the running copy. The image it came from is still on disk, ready to start the next one. That is usually what you want, and it is also why disk space creeps up on you. Containers come and go while images quietly stack up. docker images lists every image you have pulled or built, with its size beside it. docker rmi removes one by name (rmi is short for remove image). docker system df prints how much disk Docker is eating in total, and the RECLAIMABLE column tells you how much of that you could win back. When you want it back, the prune family, such as docker system prune, sweeps out everything that is not currently in use. A laptop that has been running Docker for a month can be carrying tens of gigabytes of forgotten images without ever mentioning it.
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx 1.27 3b6f2a1c8d90 3 weeks ago 192MB$ docker rmi nginx:1.27Untagged: nginx:1.27Untagged: nginx@sha256:2ef4c8b1a09d...Deleted: sha256:3b6f2a1c8d90...Deleted: sha256:9a0b1c2d3e4f...$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 2 0 384MB 384MB (100%)Containers 0 0 0B 0BLocal Volumes 1 0 41MB 41MB (100%)Build Cache 12 0 120MB 120MB
The daily rhythm is short. Run it, list it, stop it, remove it, and glance at your images now and then. The flags come with practice; the lifecycle is the part you want in your hands. What makes it worth drilling is that the same loop fits every image you will ever touch, whether that is nginx, a database like Postgres, or the image you build yourself later in this course. The commands do not change shape.
Names are worth the extra typing. Leave off --name and Docker invents something like eager_lovelace, an adjective and a surname glued together, which is charming until you have to type it twice without a mistake. Name a container whenever you plan to stop it by hand or open a shell inside it. For a one-shot experiment, add --rm instead and the container deletes itself the moment it exits, so there is nothing left to tidy. Keep the named ones around while you are learning logs and inspect, because every command you try needs something to point at.
Images stay behind once their containers are gone, which is good for speed and bad for disk if you keep pulling. Two habits pay for themselves. Learn docker images early so you can see what is actually sitting there. And know that docker rmi refuses to delete an image while any container still references it, including a stopped one you forgot about. If a removal is refused, the fix is the order: take the container out first, then the image.
Read before you delete. Exited (1) means the process failed, and the only account of why lives in that container's logs. Remove the container and the evidence goes with it. Restarting blindly gets you nowhere either, because the second attempt fails in exactly the same way as the first. Check the status, read the logs, then clean up.
Try this
Start a container that does one tiny thing and exits. Watch it vanish from docker ps, find it again with -a, remove it, then confirm the image is still sitting on disk.
docker run --name brief alpine:3.20 echo "hi from alpine"docker psdocker ps -a --filter name=briefdocker rm briefdocker images alpine
hi from alpineCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaaaaaaaaaaaa alpine:3.20 "echo ..." 10 seconds ago Exited (0) 9 seconds ago briefbriefREPOSITORY TAG IMAGE ID CREATED SIZEalpine 3.20 ........ weeks ago 7.33MB
Takeaway
Carry one idea out of here: a container that stopped has not disappeared, and an image whose container you deleted has not disappeared either. docker ps -a finds the first, docker images finds the second, and rm and rmi are the only two commands that make either of them truly go away.