What is it? #
A container is a running instance of an image. The image is the template; the container is the process.
Containers are meant to be disposable. You do not fix a container — you fix the image and replace the container.
Anything written inside is lost when the container is removed, unless it goes to a volume. Treating containers as permanent machines is the most common conceptual mistake.
Running one well means understanding a handful of flags: ports, environment, restart policy, resource limits and how to see what it is doing.
Think of it like this #
A meal cooked from a recipe. The recipe is reusable; the meal is consumed.
Changing the meal on the plate does not change the recipe, so the next one is identical again. If you want a different meal, you change the recipe.
Simple example #
You run a database container for local development with a published port, an environment variable for the password, a named volume for data, and a memory limit.
Code #
# Running
docker run -d \
--name shop-db \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=shop \
-p 5432:5432 \
-v shop-data:/var/lib/postgresql/data \
--restart unless-stopped \
--memory 512m --cpus 1 \
postgres:16
# -d detached (background)
# --name a stable name instead of a random one
# -e an environment variable
# -p host:container publish a port to the host
# -v a named volume for data that must survive
# --restart restart automatically unless deliberately stopped
# Seeing what is happening
docker ps # running containers
docker ps -a # including stopped ones
docker logs -f shop-db # follow its output
docker logs --tail 100 shop-db
docker stats # live CPU and memory per container
docker inspect shop-db # full configuration and state
docker top shop-db # processes inside it
# Interacting
docker exec -it shop-db psql -U postgres # run a command inside
docker exec -it shop-db bash # a shell inside a running container
docker run -it --rm alpine sh # a throwaway container
# Lifecycle
docker stop shop-db # SIGTERM, then SIGKILL after 10s
docker stop -t 30 shop-db # allow 30 seconds to shut down cleanly
docker start shop-db
docker restart shop-db
docker rm shop-db # remove (must be stopped first)
docker rm -f shop-db # force
# Cleaning up — Docker accumulates a surprising amount
docker container prune # stopped containers
docker image prune -a # unused images
docker volume prune # unused volumes — check first, this deletes data
docker system df # what is using disk
docker system prune -a # everything unused, aggressively
Port publishing
-p 8080:80 host port 8080 → container port 80
-p 127.0.0.1:8080:80 only reachable from the host, not the network
-p 80 container port 80 → a random host port
A service inside a container must bind to 0.0.0.0, not 127.0.0.1.
Binding to the container's localhost makes it unreachable even when
the port is published — a very common first mistake.
How it works #
docker run creates a container from an image and starts it. Without --name it gets a random name, which makes scripting awkward.
-p 5432:5432 maps a host port to a container port. The container has its own network namespace, so without publishing, nothing outside can reach it.
The binding note is important. Inside a container, 127.0.0.1 refers to the container itself, not the host. A service bound there is unreachable no matter what you publish, which surprises nearly everyone once.
--restart unless-stopped restarts the container after a crash or a host reboot, but respects a deliberate stop. It is the usual choice for services outside an orchestrator.
Resource limits matter because, without them, a container can consume all the host's memory. The limit turns a leak into a container restart rather than a host-wide problem.
docker exec runs a command in a running container, which is how you inspect a live service. docker run -it --rm starts a throwaway container, which is ideal for experimenting.
docker stop sends SIGTERM and waits ten seconds before SIGKILL. Applications with longer shutdown needs require -t, exactly like TimeoutStopSec in systemd.
Cleanup is genuinely necessary. Unused images, stopped containers and dangling volumes accumulate quickly, and docker system df shows where the disk went.
Real-world use #
In production, containers are usually managed by Compose, a scheduler or a platform rather than by hand. The commands here are what you use to debug them.
Treating containers as disposable is the mental shift that matters. Fixing something inside a running container works until it is replaced, at which point the fix is gone — so the fix belongs in the image or the configuration.
docker logs is the primary debugging tool, which is why applications in containers should log to standard output rather than to files.
Resource limits are often omitted in development and then missed in production. Setting them from the start makes behaviour predictable.
Disk exhaustion from accumulated images is a common incident on build servers in particular, where a scheduled docker system prune is standard practice.
Common mistakes #
- Binding a service to 127.0.0.1 inside a container, making it unreachable.
- Treating containers as permanent and fixing things inside them.
- No volume for data, losing the database when the container is replaced.
- No resource limits, letting one container exhaust host memory.
- Never pruning, until the disk fills with unused images and volumes.
Practice #
Run a database container with a named volume, a published port and a memory limit. Write some data, remove the container, recreate it with the same volume and confirm the data survived. Then run one without a volume and confirm the data does not.