You have Docker installed and hello-world printed its greeting. That container did its one job and exited immediately, which is not very interesting. Real containers stay up, serve traffic, and need starting, inspecting, and cleaning up. This post walks the everyday container lifecycle on a single command you will use constantly: a web server you can actually open.
By the end you will be able to start a container, see it running, talk to it, look inside it, stop and restart it, and remove it, all with a handful of docker commands. Every command and its output below was captured on a real Ubuntu 24.04 machine running Docker Engine 29.8.0.
Tip
Key takeaways
- An image is the read-only template. A container is a running instance of it. One image, many containers.
docker runcreates and starts a container.-druns it in the background,-ppublishes a port,--namegives it a friendly name.docker pslists running containers,docker ps -aincludes stopped ones.docker logs,docker exec,docker stop,docker start, anddocker rmare the rest of the daily toolkit.--rmauto-deletes a container when it exits, which keeps throwaway runs from piling up.
Prerequisites
- Docker installed and working. If
docker run hello-worldprints "Hello from Docker!", you are set. If not, start with Install Docker on macOS, Windows (WSL2), and Linux. - On Linux, either be in the
dockergroup or prefix these commands withsudo. The install guide covers the group step.
Images versus containers, in one minute
This is the one idea that makes everything else click. An image is a packaged, read-only snapshot: an application plus everything it needs to run. A container is a live, running copy created from that image, with its own writable layer on top. You can start ten containers from the same image, and each one is independent.
When you run docker run nginx, Docker looks for the nginx image locally, pulls it from Docker Hub (the default public registry, home to thousands of ready to run images) if it is missing, then creates and starts a container from it. Let us do exactly that, but with a real web server you can open in a browser.
Start a container
Run the official nginx web server, in the background, with its port published to your machine:
docker run -d -p 8080:80 --name web nginx
Three flags are doing the work here. -d (detached) runs the container in the background and hands you back your prompt. -p 8080:80 maps port 8080 on your machine to port 80 inside the container, where nginx listens. --name web gives the container a name so you do not have to use its ID for every later command.
On the first run, Docker pulls the image, then prints the new container's full ID:
Status: Downloaded newer image for nginx:latest
22bc7448b0bc864f0fbe6fce4966fa99a07ca7e017a11d6bcdadf5d738f7cb51
See it running
docker ps
docker ps lists running containers. Here is the row for the one we just started (columns trimmed to fit):
CONTAINER ID IMAGE STATUS PORTS NAMES
22bc7448b0bc nginx Up Less than a second 0.0.0.0:8080->80/tcp web
That 0.0.0.0:8080->80/tcp is the port mapping in action. Note the short container ID (22bc7448b0bc); it is just the first 12 characters of the full ID from the run command.
Talk to it
The container is serving nginx on port 8080. Ask it for a page:
curl -s localhost:8080 | grep -i title
<title>Welcome to nginx!</title>
That is a real HTTP response from software running inside the container. Open http://localhost:8080 in a browser and you will see the nginx welcome page.
Look inside
Two commands cover most of what you need to inspect a running container.
Read its logs with docker logs. nginx writes each request to its access log, so the curl above shows up:
docker logs web
172.17.0.1 - - [10/Sep/2026:10:12:07 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.5.0" "-"
Get a shell inside with docker exec. The -it flags give you an interactive terminal:
docker exec -it web sh
Now you are inside the container. Poke around, then type exit to leave. For a quick one-off you can also run a single command without an interactive shell:
docker exec web whoami
docker exec web nginx -v
root
nginx version: nginx/1.31.5
Info
execruns inside a container that is already running. It does not start a new one. If the container is stopped, start it first. This is the difference betweendocker run(create a new container) anddocker exec(step into an existing one), which trips up a lot of beginners.
Stop, start, and see stopped containers
Stop the container. Docker asks nginx to shut down gracefully and returns the name once it has:
docker stop web
web
A stopped container does not vanish. It still exists, just not running. docker ps alone will not show it, but docker ps -a will:
docker ps -a
NAMES STATUS
web Exited (0)
Exited (0) means it stopped cleanly (exit code 0). Because the container still exists, you can start it again without recreating it, and it keeps its name and configuration:
docker start web
docker ps
NAMES STATUS PORTS
web Up Less than a second 0.0.0.0:8080->80/tcp
Clean up
When you are done, remove the container. A running container will not be removed unless you force it, so -f stops and removes in one step:
docker rm -f web
web
Now docker ps -a no longer lists web. The container is gone; the nginx image stays cached locally for next time.
Warning
Stopped containers pile up. Every
docker runwithout--rmleaves a container behind after it exits, and they accumulate quietly. Rundocker ps -anow and then to see them, and remove the ones you do not need.
For anything short-lived, skip the cleanup entirely with --rm, which deletes the container the moment it exits. This runs a tiny Alpine Linux container, prints one line, and removes itself:
docker run --rm alpine echo "hello from a throwaway container"
hello from a throwaway container
After that, docker ps -a shows nothing new. The container ran and cleaned up after itself. This is the pattern for one-off commands and scripts.
The lifecycle at a glance
That is the whole loop you will use every day:
-
docker runcreates and starts a container (add-d,-p,--name,--rmas needed). -
docker ps/docker ps -alist running / all containers. -
docker logsreads its output. -
docker exec -it <name> shopens a shell inside it. -
docker stop/docker startpause and resume it. -
docker rm -fremoves it.
Common gotchas
"port is already allocated"
Something else is using host port 8080. Either stop that process or publish a different port, for example -p 8081:80, then browse to 8081.
docker exec says the container is not running
exec only works on a running container. Check docker ps -a; if it shows Exited, run docker start <name> first.
The name is already in use
Container names are unique. If docker run --name web fails because web exists (even stopped), remove the old one with docker rm web or pick a new name.
Changes inside a container disappear
Anything you write inside a container is lost when it is removed. That is by design. Persisting data needs volumes, which is a later topic in this series.
Where to go next
You can now drive a single container through its whole life. Real applications are rarely one container though; they are an app plus a database plus a cache, wired together. That is what Docker Compose is for.
-
Next in this series: Docker Compose, where we bring up a web app, Postgres, and Redis together with one
docker compose up.
Verified on 2026-09-10 on a real Ubuntu 24.04.5 LTS system running Docker Engine 29.8.0. Every command in this post was run in order against the official nginx image (container 22bc7448b0bc, nginx 1.31.5) and the alpine image: docker run -d -p 8080:80 --name web nginx, docker ps, curl localhost:8080 returning the nginx welcome page, docker logs showing the captured GET / 200, docker exec returning root and the nginx version, docker stop (Exited 0), docker start, docker rm -f, and a --rm throwaway run.
Top comments (0)