TechEarl

Docker Container Lifecycle: ps, stop, start, restart, rm

Listing, stopping, starting, restarting, renaming, and removing containers without breaking anything. The commands you reach for once docker run is done and the container is sitting there doing its job.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Manage Docker containers after they're running: list with ps, stop and start them, restart, rename, remove. Plus the --rm pattern and the SIGTERM-then-SIGKILL stop timing.

docker run creates and starts containers. Everything that happens after — listing them, stopping them, restarting them, getting rid of them — is the container lifecycle. This article covers the small set of commands that handle all of that: docker ps, docker stop, docker start, docker restart, docker rename, docker rm, plus kill, pause, and the auto-remove --rm pattern.

The lifecycle states

A container moves between these states:

code
created → running → paused → running → exited → removed
                           ↘ exited ↗
  • createddocker create ran but the container hasn't been started. Rare; usually you go straight to running with docker run.
  • running — the main process is alive. docker ps shows it.
  • paused — frozen via cgroups. docker pause and docker unpause.
  • exited — the main process has stopped (either gracefully via docker stop or because it crashed/finished). The container still exists; you can read logs, inspect, restart.
  • removed — the container's filesystem and metadata are gone. docker rm did this.

List running containers — docker ps

bash
# Running containers
docker ps

# All containers, including stopped ones
docker ps -a

# Just the IDs (useful in scripts)
docker ps -q

# All IDs (running + stopped)
docker ps -aq

# Filter by status, name, image, label
docker ps --filter status=exited
docker ps --filter name=web
docker ps --filter ancestor=nginx:alpine
docker ps --filter label=env=production

# Custom format with Go templates
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

# Show file sizes (writable layer + image)
docker ps -s

docker ps is the inventory command. By default it only shows running containers; add -a to see everything including exited ones — that's how you find the container that died ten minutes ago so you can docker logs it.

The default columns are container ID, image, command, age, status, ports, and name. --format lets you pull just the columns you want; useful in scripts.

Stop a container — docker stop

bash
docker stop CONTAINER_NAME

# Custom grace period (default is 10s)
docker stop -t 30 CONTAINER_NAME

# Stop all running containers
docker stop $(docker ps -q)

docker stop sends SIGTERM to the container's main process, waits 10 seconds for it to exit gracefully, then sends SIGKILL if it hasn't. The 10-second wait is the most common reason a docker stop "feels slow" — your app isn't reacting to SIGTERM, so Docker waits the full timeout before killing.

Two ways to fix that:

  • Use exec form for CMD/ENTRYPOINT in your Dockerfile. Shell form (CMD npm start) wraps your process in /bin/sh -c, which receives SIGTERM but doesn't pass it to your app. Exec form (CMD ["npm", "start"] or CMD ["node", "server.js"]) sends signals directly. See How to Write a Dockerfile.
  • Handle SIGTERM in your app. Express, Fastify, FastAPI etc. all have graceful-shutdown patterns. Without one, your app gets killed mid-request.

docker kill CONTAINER_NAME skips the SIGTERM and sends SIGKILL immediately. Useful when an app is wedged and stop is taking too long.

Start and restart a stopped container — docker start, docker restart

bash
# Start a stopped container (keeps the same name, same volumes, same env)
docker start CONTAINER_NAME

# Start AND attach my terminal to it
docker start -a CONTAINER_NAME

# Restart (stop + start)
docker restart CONTAINER_NAME

# Restart with custom grace period
docker restart -t 30 CONTAINER_NAME

A stopped container is not gone. docker start brings it back exactly as it was — same name, same network attachments, same volumes mounted, same env. This is the difference between docker start and docker run: run always creates a new container; start resurrects an existing one.

When to use which:

  • docker restart when a service is misbehaving and you want to reset it without losing its config. Database isn't responding to queries; bounce it.
  • docker start when you previously stopped a container and want it back. Common after host reboots if you don't have a --restart policy set.
  • docker run when you want a fresh container, possibly with different flags.

Restart policies — surviving crashes and reboots

If you want a container to come back automatically after a crash or a host reboot, set a restart policy:

bash
docker run -d --restart unless-stopped CONTAINER_NAME IMAGE_NAME
PolicyBehavior
no (default)Never restart.
on-failure[:max]Restart only on non-zero exit. Optional max-attempts.
alwaysRestart, including after host reboot. Restarts even if you manually stopped it.
unless-stoppedRestart, including after host reboot. Does NOT restart if you stopped it manually.

unless-stopped is the right default for almost every long-running service. Full breakdown plus health-check pairing in Docker Restart Policies and Health Checks.

Pause and unpause — docker pause

bash
docker pause CONTAINER_NAME
docker unpause CONTAINER_NAME

pause uses the cgroups freezer to suspend every process inside the container. It is not the same as stop — the processes are still there, they just aren't scheduled. Memory is preserved. Useful for snapshotting / quiescing state without a full restart.

Not commonly needed in day-to-day work. The two cases I've actually used it: pausing a CPU-hungry container to let another finish first, and pausing a database briefly while taking a filesystem snapshot of its volume.

Rename a container — docker rename

bash
docker rename OLD_NAME NEW_NAME

Just changes the name. The container ID, volumes, network attachments, everything else stays the same. Useful when you started with --name old_temp and want to give it a real name without recreating.

Remove a container — docker rm

bash
# Remove a stopped container
docker rm CONTAINER_NAME

# Force-remove (stops it first if running)
docker rm -f CONTAINER_NAME

# Remove a container AND its anonymous volumes
docker rm -v CONTAINER_NAME

# Remove all stopped containers in one go
docker container prune

# Remove ALL containers (running and stopped) — destructive
docker rm -f $(docker ps -aq)

docker rm deletes the container's filesystem (the writable layer) and metadata. The image is unaffected; you can docker run again from the same image and get a fresh container.

By default, named volumes survive docker rm — that's the whole point of using a named volume. Anonymous volumes (volumes you didn't name, declared by VOLUME in a Dockerfile or just -v /path) survive by default too, but -v (or --volumes) on docker rm removes them. To clean them up later, docker volume prune.

The --rm flag — auto-remove on exit

bash
# Container is deleted the instant its main process exits
docker run --rm IMAGE_NAME

# --rm + -it = the throwaway interactive shell pattern
docker run --rm -it IMAGE_NAME bash

--rm is for one-shot containers — try a tool, run a quick command, get an interactive shell to poke around. Without --rm, every quick test leaves a stopped container behind, and a week later docker ps -a is a graveyard.

Do NOT use --rm on services you might want to inspect later. Once the container exits, docker logs is no longer available for it.

Common patterns

Find and stop a container by name pattern:

bash
docker stop $(docker ps -q --filter name=web)

Stop everything cleanly:

bash
docker stop $(docker ps -q)

Clean up all stopped containers:

bash
docker container prune
# or, with no prompt:
docker container prune -f

Restart a container that's stuck:

bash
docker kill CONTAINER_NAME && docker start CONTAINER_NAME

Watch a container's lifecycle events live:

bash
docker events --filter type=container --filter container=CONTAINER_NAME

Useful when debugging a container that keeps restarting — the events show every start, die, restart with timestamps and exit codes.

Common pitfalls

  • docker stop taking 10 full seconds every time. Your app isn't handling SIGTERM. Either use exec form in your Dockerfile's CMD, or add a SIGTERM handler in the app. See How to Write a Dockerfile.
  • "Conflict. The container name '/web' is already in use." A container with that name still exists, even if stopped. Either docker rm web first, or docker run --rm to auto-clean, or use a different name.
  • docker ps doesn't show my container — but docker ps -a does. It's not running. Either it crashed (check docker logs) or it exited normally (single-shot command). Restart with docker start.
  • Containers piling up over time. Add --rm to one-shot runs, and periodically docker container prune. Or use a restart policy and let Docker manage state.
  • docker rm doesn't delete the volume. Correct — named and anonymous volumes survive container removal by default. Use docker rm -v to remove anonymous volumes, and docker volume prune to clean up unused ones.

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsDockerdocker psdocker stopdocker rmContainersDevOps

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Stop running Docker containers as root. Add a non-root USER to your Dockerfile, fix file ownership for bind mounts and volumes, bind privileged ports, and enforce it at runtime.

Running Docker Containers as a Non-Root User

By default, processes inside Docker containers run as root, which is risky. Switch to a non-root USER, fix permissions on volumes and ports, and configure Compose and Kubernetes to refuse to run root containers.