Docker and Compose
For running a few containers rather than a cluster. Weighted toward the commands you want when something is already broken.
What this page covers
Running and stopping
run, exec, ps and the flags that matter day to day.
Compose
up, down, logs, and the difference between restart and recreate.
Debugging
Getting inside a container, reading logs, and inspecting what it really has.
Images and layers
Building, tagging, and why the image is bigger than you expected.
Volumes and data
Named volumes against bind mounts, and where the data actually lives.
Reclaiming disk
prune, and what each variant deletes - this is the one that catches people.
Running and inspecting
docker ps # running containers
docker ps -a # including stopped ones
docker run --rm -it alpine sh # throwaway shell, deleted on exit
docker run -d -p 8080:80 --name web nginx
docker exec -it web sh # a shell INSIDE a running container
docker logs -f --tail 100 web
docker inspect web # everything, as JSON
docker stats # live CPU and memory per container
docker stop web && docker rm web
--rm on a throwaway container is the difference between a clean machine
and forty stopped containers you have to clear out later.
Compose
docker compose up -d # start, detached
docker compose up -d --build # rebuild images first
docker compose down # stop and remove containers + network
docker compose down -v # ALSO delete named volumes - your data
docker compose ps
docker compose logs -f web
docker compose exec web sh
docker compose restart web # restart the process, same container
docker compose up -d --force-recreate web # new container, new config
restart and --force-recreate are not the same thing. A restart
reuses the existing container, so a changed environment variable or image
is not picked up. If a config change "did nothing", this is usually why.
down -v deletes named volumes. That is your database. down on its
own does not.
When something is wrong
# It exited immediately - the logs are still there
docker logs <container>
# Why did it stop? Exit code and reason.
docker inspect <container> --format '{{.State.ExitCode}} {{.State.Error}}'
# It has no shell (distroless, scratch). Attach a toolbox to its namespace:
docker run -it --rm --pid container:<name> --network container:<name> \
nicolaka/netshoot
# What ports is it actually publishing?
docker port <container>
# What does it think its environment is?
docker exec <container> env
A container that exits instantly has usually failed at its entrypoint, and
docker logs still has the output even though docker ps shows nothing.
Images, builds and layers
docker images
docker build -t myapp:local .
docker build --no-cache -t myapp:local . # ignore the layer cache
docker history myapp:local # which layer added the weight
docker tag myapp:local registry.example.com/myapp:1.4
docker push registry.example.com/myapp:1.4
Order your Dockerfile from least to most frequently changed. Copy the dependency manifest and install BEFORE copying the source, or every source edit invalidates the dependency layer and reinstalls everything:
COPY package*.json ./
RUN npm ci
COPY . . # this line changes constantly - keep it last
Volumes, and where data actually is
docker volume ls
docker volume inspect mydata
docker run -v mydata:/var/lib/data myapp # named volume - managed
docker run -v "$(pwd)":/app myapp # bind mount - your folder
A named volume is managed by Docker and survives down. A bind
mount is a directory on your machine and changes both ways. Use bind
mounts in development so edits appear immediately, and named volumes for
anything you would be upset to lose.
Reclaiming disk, carefully
This is the section people come back for. Docker does not clean up after itself and the numbers get large quietly.
docker system df # what is using the space, by category
docker container prune # stopped containers
docker image prune # dangling (untagged) images only
docker image prune -a # every image not used by a container
docker builder prune # the build cache, often the biggest
docker volume prune # UNUSED volumes - can delete real data
docker system prune # containers + networks + dangling images
docker system prune -a --volumes # nearly everything. Read that twice.
Run docker system df first, every time. It tells you which category
is actually large, and usually it is the build cache rather than the
images - in which case builder prune alone reclaims the space with no
risk to anything you are running.
volume prune is the one to be careful with: "unused" means no running
container references it, and a stopped database is unused by that
definition.