← Back to GuidesGUIDESelf-Hosting & Homelab

Run Your First Containers: docker run, ps, logs, exec, stop, rm

about 17 hours ago
❤️ 0 likes
💬 0 comments
dockerself-hostinglinux
Run Your First Containers: docker run, ps, logs, exec, stop, rm

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 run creates and starts a container. -d runs it in the background, -p publishes a port, --name gives it a friendly name.docker ps lists running containers, docker ps -a includes stopped ones.docker logs, docker exec, docker stop, docker start, and docker rm are the rest of the daily toolkit.--rm auto-deletes a container when it exits, which keeps throwaway runs from piling up.

Prerequisites

  • Docker installed and working. If docker run hello-world prints "Hello from Docker!", you are set. If not, start with Install Docker on macOS, Windows (WSL2), and Linux.
  • On Linux, either be in the docker group or prefix these commands with sudo. 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:

Bash
1docker 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:

Plain Text
1Status: Downloaded newer image for nginx:latest222bc7448b0bc864f0fbe6fce4966fa99a07ca7e017a11d6bcdadf5d738f7cb51

See it running

Bash
1docker ps

docker ps lists running containers. Here is the row for the one we just started (columns trimmed to fit):

Plain Text
1CONTAINER ID   IMAGE     STATUS         PORTS                    NAMES222bc7448b0bc   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:

Bash
1curl -s localhost:8080 | grep -i title
Plain Text
1<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:

Bash
1docker logs web
Plain Text
1172.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:

Bash
1docker 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:

Bash
1docker exec web whoami2docker exec web nginx -v
Plain Text
1root2nginx version: nginx/1.31.5

Info

`exec` runs 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 between docker run (create a new container) and docker 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:

Bash
1docker stop web
Plain Text
1web

A stopped container does not vanish. It still exists, just not running. docker ps alone will not show it, but docker ps -a will:

Bash
1docker ps -a
Plain Text
1NAMES     STATUS2web       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:

Bash
1docker start web2docker ps
Plain Text
1NAMES     STATUS         PORTS2web       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:

Bash
1docker rm -f web
Plain Text
1web

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 run without --rm leaves a container behind after it exits, and they accumulate quietly. Run docker ps -a now 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:

Bash
1docker run --rm alpine echo "hello from a throwaway container"
Plain Text
1hello 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 run creates and starts a container (add -d, -p, --name, --rm as needed).
  • docker ps / docker ps -a list running / all containers.
  • docker logs reads its output.
  • docker exec -it <name> sh opens a shell inside it.
  • docker stop / docker start pause and resume it.
  • docker rm -f removes 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.

Join the discussion on Run Your First Containers: docker run, ps, logs, exec, stop, rm

Likes, comments, and replies are available for authenticated readers with verified email addresses.

Comments (0)

Loading discussion...

Related guides