docker ps shows running containers and docker ps -a shows all of them, but the flags that actually save time are —filter, —format, and -q.
Everyone learns docker ps on day one and then never revisits it. That is a shame, because the default output is a wide table designed for a terminal nobody has, and the flags that make it useful in scripts are barely mentioned in most tutorials.
This is the version worth knowing: how to list precisely what you want, in the shape you want, so the output can feed the next command.
Table of contents
- The basic commands
- Filtering to the containers you care about
- Formatting output that fits your terminal
- Reading the status column properly
- Composing with other commands
- How this fits the rest of the stack
- FAQ
The basic commands
Two names for the same thing. docker ps is the historical form borrowed from Unix; docker container ls is the modern, more explicit spelling. They behave identically.
# Running containers only (the default)
docker ps
docker container ls
# Every container, including stopped and exited
docker ps -a
# Just the IDs, one per line -- the scripting form
docker ps -q
docker ps -aq
# The most recently created container, running or not
docker ps -l
# Include disk usage per container
docker ps -s
The default columns are container ID, image, command, created time, status, ports, and names. The command column is truncated, which is usually where the information you wanted went. --no-trunc restores it.
-q is the flag that matters most in practice, because it turns the command into something you can pipe. docker stop $(docker ps -q) stops everything running, and that composition is the whole point.
Filtering to the containers you care about
On a host running thirty containers, the full table is noise. --filter narrows it, and the filters compose.
# By status
docker ps -a --filter status=exited
docker ps -a --filter status=running --filter status=paused
# By name, which is a substring match, not exact
docker ps --filter name=api
# By image ancestry, including derived images
docker ps --filter ancestor=postgres:16
# By label -- the one worth adopting deliberately
docker ps --filter label=com.example.stack=payments
# Containers that exited non-zero: the failure list
docker ps -a --filter exited=1
# By health status, if the image defines a healthcheck
docker ps --filter health=unhealthy
Repeating the same filter key means OR — two status filters match either. Different keys mean AND. That asymmetry is not obvious and it is the source of most confusion with this flag.
The health=unhealthy filter is the single most useful one for operational work, and it only functions if your images declare a HEALTHCHECK. If they do not, add one; a container that is running but broken is otherwise indistinguishable from a healthy one.
Formatting output that fits your terminal
The default table is too wide to read. --format takes a Go template and lets you pick exactly the columns you want.
# A readable table of the fields that usually matter
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Name and image, no table header
docker ps --format "{{.Names}} -> {{.Image}}"
# JSON per line, for jq
docker ps --format json
# Every field as JSON, older syntax
docker ps --format "{{json .}}"
Available fields include .ID, .Image, .Command, .CreatedAt, .RunningFor, .Ports, .Status, .Names, .Labels, .Mounts, and .Networks.
If you type the same format daily, set it once and stop. Docker reads a config file for defaults.
# ~/.docker/config.json
{
"psFormat": "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
Reading the status column properly
The status string carries more information than people extract from it, and the distinctions matter when something is wrong.
Up 3 hours— running normallyUp 3 hours (healthy)— running and passing its healthcheckUp 2 minutes (health: starting)— inside the healthcheck start period, not yet judgedUp 5 minutes (unhealthy)— running, but failing its healthcheck; this is the one to alarm onExited (0) 2 hours ago— finished successfully, normal for a completed jobExited (137) 5 minutes ago— killed by SIGKILL, almost always the out-of-memory killerExited (1) 30 seconds ago— the application itself failedRestarting (1) 8 seconds ago— crash-looping under a restart policy
Exit code 137 is the one worth recognising instantly. It means 128 plus signal 9, and on a container host that nearly always means the kernel OOM killer terminated it. The fix is a memory limit or a memory leak, not a restart policy.
Restarting in a loop is the other pattern to catch early, because a crash-looping container looks superficially alive in monitoring that only checks whether the container exists.
Composing with other commands
Once output is filtered and formatted, the listing becomes the input to everything else.
# Stop everything currently running
docker stop $(docker ps -q)
# Remove all exited containers
docker rm $(docker ps -aq --filter status=exited)
# The maintained equivalent, safer and no subshell
docker container prune -f
# Tail logs from every container matching a label
for c in $(docker ps -q --filter label=stack=payments); do
echo "--- $c"
docker logs --tail 20 "$c"
done
# Live resource usage for running containers
docker stats --no-stream
A caution on the subshell pattern: docker stop $(docker ps -q) fails noisily when nothing is running, because the command receives no arguments. In a script, guard it or use the prune subcommands, which handle the empty case correctly.
docker stats is the natural companion here. docker ps tells you what exists; docker stats tells you what it is consuming. The --no-stream flag gives you a single snapshot instead of a live view, which is what you want in a script.
How this fits the rest of the stack
Listing containers is the first move in almost every debugging session on a container host, which is itself a signal about where the complexity lives. Running containers directly means you own the health checks, the restart policies, and the memory limits. RunxBuild runs container workloads with logs, health, and restart behaviour surfaced in the dashboard rather than discovered through docker ps on a box, and the RunxBuild hosting calculator shows what a service with that operational layer costs against what you are paying for the host today.
Useful related references:
- Deploy a Docker Container for Free
- Add a User to the Docker Group: Running Docker Without sudo (and the Risk)
- docker exec: How to Get a Shell Inside a Running Container Without Restarting It
- Docker services on RunxBuild
FAQ
What is the difference between docker ps and docker container ls?
Nothing functional. docker ps is the original Unix-inspired name; docker container ls is the newer explicit form. Both accept the same flags and produce the same output.
How do I list stopped containers?
docker ps -a shows all containers regardless of state. To see only stopped ones, use docker ps -a --filter status=exited, and add --filter exited=1 to narrow to failures.
What does exit code 137 mean?
The container was killed by SIGKILL — 128 plus signal 9. In practice this is almost always the kernel out-of-memory killer, meaning the container exceeded its memory limit. Raise the limit or fix the leak.
How do I list only container IDs?
docker ps -q for running containers, docker ps -aq for all. This form is designed for command substitution, as in docker stop $(docker ps -q).
Can I change the default docker ps columns?
Yes. Set psFormat in ~/.docker/config.json to a Go template such as table {{.Names}}\t{{.Status}}\t{{.Ports}} and every subsequent docker ps uses it.