Skip to main content

Docker Images

  • An image is a stack of immutable layers with metadata (labels, env, entrypoint, etc.).
  • You can create an image by saving the state of a container (docker commit) or by declaring steps in a Dockerfile (docker build).
  • Prefer Dockerfiles for repeatability; use docker commit for quick experiments or to capture ad‑hoc changes.

Docker Images Concepts

Layers & caching

  • Each Dockerfile instruction creates a layer (e.g., FROM, RUN, COPY).
  • Layers are content‑addressed; if nothing changes, Docker reuses them from cache, speeding builds.

Immutable + copy‑on‑write

  • Images are read‑only. When you run a container, Docker adds a thin writable layer on top.

Tags & digests

  • Tag: human‑friendly alias (e.g., `myapp:1.4`, `nginx:alpine`).
  • Digest: content hash (`@sha256:...") that uniquely identifies an image.

Useful inspection commands

# list images
docker images

# see low-level JSON metadata
docker inspect myimage:tag

# see layer history
docker history myimage:tag

Creating an Image from a Running Container (docker commit)

Great for: fast prototyping, capturing manual tweaks.

warning

⚠️ Drawbacks: not reproducible by default; hard to review; can bloat images.

Example: capture a customized Alpine container

# 1) Start a throwaway interactive container
docker run -it --name demo alpine:3.20 sh

# 2) Make changes inside the container (examples)
apk update && apk add curl ca-certificates
mkdir -p /opt/demo && echo "hello" > /opt/demo/hello.txt
exit

# 3) Commit the container’s current filesystem into a new image
docker commit \
--author "Your Name" \
--message "Add curl and demo file" \
demo myrepo/alpine-demo:commit-v1

# 4) Verify
docker run --rm myrepo/alpine-demo:commit-v1 ls -l /opt/demo

Improving a commit image

  • Set default CMD/ENTRYPOINT:
docker commit --change 'CMD ["/bin/sh"]' demo myrepo/alpine-demo:commit-v2
  • Add labels for traceability:
docker commit \
--change 'LABEL org.opencontainers.image.source=https://example.com/repo' \
--change 'LABEL org.opencontainers.image.description="Demo image from commit"' \
demo myrepo/alpine-demo:commit-v3

When to use commit

  • Rapid capture of exploratory work
  • One‑off debugging artifacts
  • Transitional step before writing a proper Dockerfile
tip

Best practice: Once you know what changed, codify it as a Dockerfile for repeatable builds.


Creating an Image from a Dockerfile (docker build)

Best for: repeatable, reviewable, and optimized images.

Minimal example (Alpine)

Dockerfile

FROM alpine:3.20
RUN apk add --no-cache curl ca-certificates
COPY ./hello.txt /opt/demo/hello.txt
CMD ["/bin/sh"]

Build & run

echo "hello" > hello.txt

docker build -t myrepo/alpine-demo:df-v1 .

docker run --rm myrepo/alpine-demo:df-v1 ls -l /opt/demo

A more realistic example: NGINX with static site

Dockerfile

FROM nginx:1.27-alpine
# Add labels for provenance
LABEL org.opencontainers.image.title="nginx-static"
LABEL org.opencontainers.image.description="Static site on nginx"

# Copy site content (assumes ./site exists)
COPY site/ /usr/share/nginx/html/

# Healthcheck (container viewpoint)
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:80/ || exit 1

# Expose is documentation; publish with -p at runtime
EXPOSE 80

Build & run

docker build -t myrepo/nginx-static:1.0 .

docker run -d --name web -p 8080:80 myrepo/nginx-static:1.0

curl -I http://localhost:8080/

Multi‑stage builds (small, production‑ready images)

Example: Go app

# ---- build stage ----
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/app ./cmd/app

# ---- runtime stage ----
FROM alpine:3.20
RUN adduser -S -D -H -h /nonexistent appuser
USER appuser
COPY --from=build /bin/app /app
ENTRYPOINT ["/app"]

Benefits:

  • The final image contains only your binary and runtime deps.
  • Build tools and caches stay in the intermediate stage.

.dockerignore (crucial for speed & security)

Create a .dockerignore to prevent unnecessary files from entering the build context.

.git
node_modules
build
.DS_Store
*.log
.env

Helpful build flags

# Use build cache from a previous image (useful in CI)
docker build --cache-from myrepo/app:latest -t myrepo/app:latest .

# Pass build-time variables
docker build --build-arg VERSION=1.2.3 -t myrepo/app:1.2.3 .

# Platform targeting (for multi-arch)
docker buildx build --platform linux/amd64,linux/arm64 -t myrepo/app:1.2.3 .

Metadata & runtime defaults

Add labels and defaults directly in Dockerfile for consistency.

LABEL org.opencontainers.image.source="https://example.com/repo"
ENV APP_ENV=production
WORKDIR /app
ENTRYPOINT ["/app"]
CMD ["--help"]

Tagging, Pushing, Saving

Tagging conventions

  • Use semantic version tags (e.g., `1.4.2`) plus a moving `latest` if your workflow requires it.
  • Consider adding git SHA tags for traceability.
# add an extra tag to an existing image ID
docker tag myrepo/app:1.2.3 myrepo/app:latest

Push to a registry

docker login
# Docker Hub example
docker push myrepo/app:1.2.3

Save / load (air‑gapped or offline)

# Save a tarball
docker save myrepo/app:1.2.3 -o app_1.2.3.tar

# Load later
docker load -i app_1.2.3.tar

Verifying What You Built

# See layers and commands
docker history myrepo/app:1.2.3

# Inspect metadata
docker inspect myrepo/app:1.2.3 | jq '.[0] | {Id,RepoTags,Config:{Env,Cmd,Entrypoint,Labels}}'

# Run a quick health probe
docker run --rm myrepo/app:1.2.3 /bin/sh -lc 'echo ok'

Troubleshooting & Gotchas

  • Issue: “My image is huge.”
  • Fix: Use smaller base images (`alpine` or distro‑less), remove build tools in same layer, use multi‑stage builds, prune caches.

  • Issue: “Builds are slow now.”
  • Fix: Reorder Dockerfile to maximize cache hits (put rare‑changing steps last), add .dockerignore, pin versions.

  • Issue:docker commit image is hard to reproduce.”
  • Fix: Convert the steps into a Dockerfile so teammates and CI can rebuild deterministically.

  • Issue: “CMD vs ENTRYPOINT confusion.”

  • Fix:

    • Use ENTRYPOINT for the main executable.
    • Use CMD for default args (overridable at `docker run`).

  • Issue: “It works on my machine, fails in CI.”
  • Fix: Pin base image versions and dependencies; avoid relying on network state or implicit caches.

From Commit to Dockerfile (codifying ad‑hoc changes)

  1. Run a container and experiment.
  2. Use `docker diff ` to see filesystem changes.
  3. Translate changes into RUN, COPY, ENV, USER steps in a Dockerfile.
  4. Rebuild and test.
  5. Delete the old commit‑based image.

Security & Policy Basics

  • Pin versions in `FROM` and package installs.
  • Drop root: create and `USER` a non‑root account where possible.
  • Keep secrets out of images and build args. Inject at runtime (env vars, secrets managers).
  • Use minimal bases (alpine or distro‑less) to reduce attack surface.

Clean‑up

# dangling images & build cache
docker system prune -f

# unused images only
docker image prune -a

Quick Reference

  • Build: docker build -t name:tag .
  • Run: docker run --rm -it name:tag sh
  • Commit: docker commit [--change ...] name:tag
  • Tag: docker tag src:tag dst:tag
  • Push: docker push name:tag
  • Inspect: docker inspect name:tag
  • History: docker history name:tag
  • Save/Load: docker save / docker load