Skip to main content
Navigation
HomeTechnical ReferenceJournalGitHubGitHub
Sidebar — toggle document categories via the logo
Categories

Docker Overview

Overview

Docker packages applications into portable containers that run consistently across development, staging, and production environments. Each container shares the host kernel but runs in an isolated userspace with its own filesystem, networking, and process tree.

Core concepts

  • Image — a read-only template containing application code, libraries, and dependencies. Built from a Dockerfile.
  • Container — a runnable instance of an image with its own writable layer.
  • Dockerfile — a script of instructions for building an image.
  • Registry — a repository for storing and distributing images (Docker Hub, ECR, GCR, private registries).
  • Volume — persistent storage mounted into a container, managed by Docker or directly from the host.
  • Network — virtual network that containers attach to for communication.

Container lifecycle

Running containers

docker run creates a container from an image and starts it in one step. The example below runs nginx:alpine detached with a name and a published port; the table that follows lists the most useful docker run flags.

# Create and start a container
docker run -d --name webapp -p 8080:80 nginx:alpine
FlagPurpose
-dRun in detached mode (background).
--name <name>Assign a name to the container.
-p <host>:<container>Publish port (host → container).
-p <ip>:<host>:<container>Publish to specific host IP.
-PPublish all exposed ports to random host ports.
-e KEY=VALUESet environment variable.
--env-file <file>Read env vars from a file.
-v <host>:<container>Bind mount a host directory.
-v <volume>:<container>Mount a named volume.
--rmRemove container when it exits.
-itInteractive mode with pseudo-TTY.
--restart <policy>Restart policy: no, always, on-failure, unless-stopped.
--network <name>Attach to a custom network.
--memory=<limit>Memory limit (e.g., 512m, 2g).
--cpus=<limit>CPU limit (e.g., 1.5).
--user <uid>:<gid>Run as specific user.
--initUse init process to forward signals.

Container management

These commands drive the container lifecycle after creation: start, stop, pause, and remove containers. Check what is currently running with docker ps before deleting anything — docker rm -f and docker container prune are destructive and cannot be undone.

docker ps # list running containers
docker ps -a # list all containers, including stopped ones
docker ps -q # print only container IDs (handy for chaining)
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" # custom tabular output

docker start webapp # start an existing stopped container
docker stop webapp # graceful stop: SIGTERM, then SIGKILL after the timeout
docker stop -t 30 webapp # extend the grace period before SIGKILL
docker restart webapp # stop, then start again
docker pause webapp # freeze all processes in place (SIGSTOP)
docker unpause webapp # resume a paused container (SIGCONT)
docker kill webapp # immediate force kill (SIGKILL)
docker kill -s SIGHUP webapp # send an arbitrary signal to the main process

docker rm webapp # delete a stopped container
docker rm -f webapp # force-remove a container, even while running
docker rm $(docker ps -aq) # remove every stopped container at once
docker container prune # remove all stopped containers
docker container prune -f # prune without the confirmation prompt

docker rename oldname newname # give a container a new name

Inspecting containers

When a container misbehaves, these commands give you visibility into its output, metadata, resource usage, and live processes. Start with docker logs for application output and docker stats for resource consumption; docker exec drops you into a shell inside the running container for hands-on debugging.

docker logs webapp # view the container's stdout/stderr
docker logs -f webapp # follow new log lines in real time (like tail -f)
docker logs --tail 100 webapp # show only the last 100 lines
docker logs --since 10m webapp # show logs from the last 10 minutes
docker logs --timestamps webapp # prefix every line with a timestamp

docker inspect webapp # full JSON metadata about the container
docker inspect -f '{{.NetworkSettings.IPAddress}}' webapp # extract one field via a Go template

docker top webapp # list processes running inside the container
docker stats # live CPU/memory usage for all containers
docker stats --no-stream # one-shot stats without the live refresh
docker stats webapp db # stats for specific containers only

docker port webapp # show published port mappings

docker diff webapp # show files added/changed/deleted in the container

docker exec -it webapp sh # open a shell inside a running container
docker exec -it webapp bash # bash shell (if present in the image)
docker exec webapp ls -la /app # run a single command, no shell needed

Copying files

Copy files between the host and a running container with docker cp. Reach for it to pull out log files, drop in a configuration file without rebuilding the image, or retrieve build artifacts from a container that has no volume mounts.

docker cp ./local.conf webapp:/etc/app/config.conf # copy a host file into the container
docker cp webapp:/var/log/app.log ./logs/ # copy a container file back to the host

Committing changes

Snapshot a container's current filesystem as a new image with docker commit. This is useful for preserving a troubleshooting session or capturing runtime changes, but it bypasses the Dockerfile and loses build history — rebuild from a Dockerfile for anything you intend to keep long-term.

docker commit webapp myapp:snapshot # create an image from the container's current state

Image management

Working with images

These commands cover the image lifecycle: listing what is stored locally, pulling and pushing images to registries, tagging, removing, and transferring images between hosts. Tag an image with its registry path before pushing, and note that docker image prune removes only dangling images — add -a to also remove unused tagged images.

docker images # list images stored locally
docker images -a # include intermediate build layers
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" # custom tabular output

docker pull nginx:alpine # download an image from a registry
docker pull nginx@sha256:abc123... # pull a specific digest (immutable reference)
docker push myrepo/myapp:v1.0 # upload a local image to a registry

docker tag myapp:v1.0 myrepo/myapp:v1.0 # retag an image for a registry
docker tag myapp:v1.0 myrepo/myapp:latest # add a second tag to the same image

docker rmi nginx:alpine # remove an image by name:tag
docker rmi $(docker images -q) # remove every image on the host
docker image prune # remove dangling (untagged) images
docker image prune -a # remove all images not used by a container

docker history nginx:alpine # show the layers and commands an image was built from
docker save -o myapp.tar myapp:v1.0 # export an image to a tar file (offline transfer)
docker load -i myapp.tar # import an image from a tar file

Building images

docker build compiles a Dockerfile into an image. The path you pass is the build context, which gets sent to the Docker daemon — keep it lean and exclude junk with .dockerignore. Use --no-cache to force a clean rebuild when stale layers cause problems, and --platform to target a different CPU architecture.

docker build -t myapp:v1.0 . # build from the Dockerfile in the current directory
docker build -t myapp:v1.0 -f Dockerfile.prod . # build using a specific Dockerfile name
docker build --no-cache -t myapp:v1.0 . # rebuild every layer, ignoring the build cache
docker build --build-arg NODE_ENV=production -t myapp:v1.0 . # pass values for ARG instructions
docker build --platform linux/amd64 -t myapp:v1.0 . # cross-platform build (e.g. on Apple Silicon)

Dockerfile reference

Key instructions

These are the Dockerfile instructions you will use in almost every image. FROM selects the base image and starts a build stage, RUN executes commands during the build, COPY adds files, and ARG/ENV handle configuration — note that ARG exists only at build time, while ENV values persist into the running container.

# Base image — must be the first instruction; 'AS builder' names this stage
FROM node:22-alpine AS builder

# Set the working directory for all subsequent instructions
WORKDIR /app

# Copy files: dependency manifests first to leverage layer caching
COPY package*.json ./
COPY . .

# Run commands during the build (installs dependencies here)
RUN npm ci --production

# Build-time variables — supplied via --build-arg, not baked into the image
ARG NODE_ENV=production

# Environment variables — also available when the container runs
ENV NODE_ENV=$NODE_ENV

# Expose ports (informational; does not publish anything by itself)
EXPOSE 3000

# Run the container as a non-root user for security
USER node

# Health check that Docker probes at the configured interval
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

# ENTRYPOINT is the fixed command; CMD supplies its default arguments
ENTRYPOINT ["node"]
CMD ["server.js"]

# Multi-stage: start a fresh stage and copy only the build output
FROM node:22-alpine
COPY --from=builder /app/dist ./dist

Dockerfile best practices

These patterns keep images small, reproducible, and quick to rebuild. Pin base image tags instead of floating majors, combine RUN commands to cut the layer count, copy dependency manifests before source code to maximize layer caching, and prefer multi-stage builds so the final image ships only the runtime.

# GOOD: pin versions, use specific tags for reproducibility
FROM node:22.12.0-alpine3.20

# GOOD: combine RUN commands to reduce the number of layers
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*

# GOOD: copy package files first (leverage layer caching)
COPY package.json package-lock.json ./
RUN npm ci --production
COPY . .

# GOOD: multi-stage builds separate build and runtime
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o /app .

FROM alpine:3.20
COPY --from=builder /app /app
ENTRYPOINT ["/app"]

# GOOD: use .dockerignore to keep secrets and junk out of the build context
# node_modules
# .git
# *.md
# .env

Image minification techniques

These techniques shrink image size, reducing registry storage, pull time, and attack surface. Switch to slim or alpine base images, use multi-stage builds to ship only the compiled output, clear package-manager caches, and inspect the layer breakdown with docker history or dive to find what is bloating the image.

# Use slim/alpine base images (dramatically smaller)
node:22 → ~1.1 GB
node:22-slim → ~250 MB
node:22-alpine → ~130 MB

# Multi-stage builds (only ship the compiled output)
# Example: Go binary in scratch
FROM golang:1.22 AS builder
RUN go build -ldflags="-s -w" -o /app .

FROM scratch
COPY --from=builder /app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/app"]

# Clean up package managers
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*

# Use --no-install-recommends to skip optional packages
RUN apt-get install -y --no-install-recommends curl

# npm: use ci instead of install, prune dev deps
RUN npm ci --production

# python: no cache dir
RUN pip install --no-cache-dir -r requirements.txt

# Combine layers to reduce total image size
RUN curl -sL https://example.com/binary -o /usr/local/bin/binary \
&& chmod +x /usr/local/bin/binary \
&& rm -rf /tmp/*

# Analyze image layers to find where the size comes from
docker history myapp:v1.0
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive myapp:v1.0

Docker networking

Network commands

These commands create and manage the virtual networks containers attach to. User-defined bridge networks provide automatic DNS resolution by container name — the same mechanism Compose uses to connect services — so use docker network connect to attach a running container without recreating it.

docker network ls # list all networks on the host
docker network inspect bridge # show details of the default bridge network
docker network create mynet # create a user-defined bridge network
docker network create --driver overlay mynet # swarm overlay network for multi-host setups
docker network rm mynet # remove a network (detach containers first)
docker network connect mynet webapp # attach a running container to a network
docker network disconnect mynet webapp # detach a container from a network
docker network prune # remove networks no container uses

Network drivers

DriverUse case
bridgeDefault; containers on same host communicate.
hostContainer shares host network stack (no isolation).
noneNo networking.
overlayMulti-host communication (Docker Swarm).
macvlanAssign MAC address to container; appears as physical device.

Docker volumes

Volume commands

Volumes persist data beyond a container's lifetime. Named volumes are managed by Docker and are the right choice for databases; bind mounts point directly at a host directory, which is handy in development; tmpfs mounts live purely in memory. The --mount syntax is more explicit than the -v shorthand for bind mounts.

docker volume ls # list all named volumes
docker volume create app_data # create a named volume explicitly
docker volume inspect app_data # show volume metadata (mount point, driver)
docker volume rm app_data # delete a volume (permanent data loss)
docker volume prune # remove volumes no container uses

# Mount in a container
docker run -v app_data:/app/data nginx
docker run -v "$(pwd)/config:/app/config" nginx # bind mount a host directory
docker run --mount type=bind,src="$(pwd)/config",dst=/app/config nginx # explicit bind-mount syntax
docker run --tmpfs /app/tmp:exec nginx # tmpfs mount (in-memory, wiped on stop)

Docker Compose

See the dedicated Docker Compose Reference for a full guide.

Quick start commands

A minimal Compose workflow: docker compose up -d builds and starts the whole stack, logs -f streams output from every service, and down stops everything and removes the containers and networks Compose created. Use docker compose exec to run a command inside an already-running service rather than spinning up a new container.

docker compose up -d # build and start all services in the background
docker compose down # stop and remove containers, networks
docker compose logs -f # follow logs from all services
docker compose ps # list services and their status
docker compose exec webapp sh # exec a shell into a running service
docker compose build # build or rebuild service images
docker compose restart webapp # restart a single service

Cleaning up

Over time Docker accumulates stopped containers, dangling images, and build cache. Start with docker system df to see what is consuming space, then prune — remember that docker system prune -a also deletes unused images and --volumes deletes volumes, so read the confirmation prompt carefully before agreeing.

docker system df # disk usage by Docker
docker system df -v # detailed per-object usage

docker system prune # remove unused containers, networks, images, build cache
docker system prune -a # also remove unused images (not just dangling)
docker system prune -a --volumes # include volumes (caution)

docker builder prune # clean build cache
docker builder prune -a # clean all build cache

Common patterns

One-shot task container

Run a container just long enough to execute a single command, then discard it. Mounting the current directory with -v "$(pwd):/workspace" and setting -w /workspace gives the container access to your code, while --rm cleans up automatically on exit — ideal for running tests or linters without installing a toolchain locally.

# Run npm test in a throwaway node container using the current directory
docker run --rm -v "$(pwd):/workspace" -w /workspace \
node:22-alpine npm test

Debugging a failing container

When a container crashes immediately or never becomes healthy, these commands help you find out why. Override the entrypoint to get a shell before the application starts, read the logs of the most recently created container with docker ps -lq, and inspect its state and exit code with docker inspect piped through jq.

# Override entrypoint to get a shell
docker run --rm -it --entrypoint sh myapp:v1.0

# Check logs of an exited container
docker logs $(docker ps -lq)

# Inspect container in detail
docker inspect myapp | jq '.[0].State'

See also