Skip to content
Docker core 3 min read

Images & Containers

Images and containers are the two objects you interact with most. An image is the immutable build artifact; a container is a running instance of it. This page covers the day-to-day commands for pulling images, controlling container lifecycles, and wiring containers to ports, storage, and configuration.

Pulling and running images

Images live in registries and are addressed by name:tag. Pull one explicitly, or let docker run pull it on demand:

docker pull nginx:1.27
docker run -d --name web -p 8080:80 nginx:1.27

The -d flag runs the container detached (in the background). --name assigns a stable name so you do not have to reference the generated container ID.

Inspecting and controlling containers

List running containers, or add -a to include stopped ones:

docker ps -a

Output:

CONTAINER ID   IMAGE         COMMAND                  STATUS         PORTS                  NAMES
3f7a9c2b1e44   nginx:1.27    "/docker-entrypoint.…"   Up 2 minutes   0.0.0.0:8080->80/tcp   web

The full lifecycle is managed with a small set of verbs:

docker stop web        # graceful stop (SIGTERM, then SIGKILL)
docker start web       # restart a stopped container
docker logs -f web     # stream stdout/stderr
docker exec -it web sh # open a shell inside the container
docker rm web          # remove a stopped container
docker rm -f web       # force-remove a running container

Containers are ephemeral. Anything written to a container’s writable layer is lost when you docker rm it. Persist important data in volumes instead.

Mapping ports

Containers run on an isolated network. To reach a service from the host, publish its port with -p HOST:CONTAINER:

docker run -d -p 8080:80 nginx:1.27     # host 8080 -> container 80
docker run -d -p 127.0.0.1:5432:5432 postgres:16   # bind to localhost only

Without -p, the port is reachable only from other containers on the same Docker network.

Persisting data with volumes

Volumes store data outside the container’s lifecycle. Use a named volume for managed storage, or a bind mount to map a host directory:

docker volume create pgdata
docker run -d -v pgdata:/var/lib/postgresql/data postgres:16   # named volume
docker run -d -v "$(pwd)/site:/usr/share/nginx/html" nginx:1.27  # bind mount

Named volumes are portable and managed by Docker; bind mounts are ideal for local development where you want live source edits reflected in the container.

Passing environment variables

Configure containers at runtime with -e, or load many from a file with --env-file:

docker run -d -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=app postgres:16
docker run -d --env-file ./.env myapp:1.0

Image layers and tags

Images are built from stacked, read-only layers, each produced by an instruction in the build. Layers are content-addressed and cached: if two images share a base, that base is stored once and reused. This is why pulling a second image from the same base is fast.

Inspect the layer history of any image:

docker history nginx:1.27

Tags are mutable pointers to a specific image version. nginx:1.27 is precise; nginx:latest floats to whatever was most recently published.

Avoid deploying :latest to production — it is non-deterministic. Pin explicit versions (or even digests like nginx@sha256:...) so deployments are reproducible.

Best Practices

  • Pin image tags to explicit versions; never rely on :latest in production.
  • Treat containers as disposable — store state in volumes or external services.
  • Use --name and labels so containers are easy to identify and clean up.
  • Bind sensitive ports to 127.0.0.1 unless they must be externally reachable.
  • Prune unused resources regularly with docker system prune to reclaim disk space.
  • Prefer official or verified base images and keep them updated for security patches.
Last updated June 1, 2026
Was this helpful?