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)
- Run a container and experiment.
- Use `docker diff ` to see filesystem changes.
- Translate changes into RUN, COPY, ENV, USER steps in a Dockerfile.
- Rebuild and test.
- 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