Working with Docker Images¶
Overview¶
A Docker image is the immutable artifact that CI pipelines build, security scanners analyze, and production orchestrators deploy. Unlike containers — which are ephemeral running instances — images are versioned, shareable, and cached. When a deployment fails, rolling back means pulling a previous image tag. When a CVE is announced, identifying affected services starts with docker images and registry digests.
This tutorial covers the complete image workflow: pull from registries, tag for environment promotion, inspect metadata and layers, remove unused images safely, and understand how layers and digests enable reproducible DevOps pipelines.
This is Tutorial 5 in Module 2: Images & Dockerfile of the REBASH Academy Docker series. Complete Running Your First Container first. Git tagging concepts parallel Docker tag strategies; Linux filesystem knowledge helps you interpret layer storage on disk.
Prerequisites¶
- Docker Engine installed and working
- Successful completion of Running Your First Container
- Outbound access to Docker Hub or a configured registry mirror
- At least 2 GB free disk under
/var/lib/docker - Familiarity with Docker Architecture and Components — especially layers and overlay2
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Pull images from registries with specific tags and digests
- List, filter, and interpret local images with
docker images - Tag images for environments (
dev,staging,prod) and registries - Inspect image metadata, layers, environment variables, and exposed ports
- Remove images safely with
docker rmiand understand dependency errors - Explain the difference between tags and immutable digests
- Use
docker historyanddocker image inspectfor troubleshooting and supply-chain audits - Apply disk hygiene practices without breaking running containers
Architecture Diagram¶
Images flow from registries to local storage, where layers are deduplicated across tags. Containers add a writable layer on top without modifying the image.
flowchart LR
REG["(Registry<br/>Docker Hub / ECR")]
PULL[docker pull]
LOCAL["(Local Image Store<br/>/var/lib/docker/image")]
TAG[docker tag]
RUN[docker run]
CONT["Container<br/>writable layer"]
REG --> PULL --> LOCAL
LOCAL --> TAG --> LOCAL
LOCAL --> RUN --> CONT```
## Theory
### What Is a Docker Image?
An image is a **read-only template** containing:
- **Filesystem layers** — stacked diffs from Dockerfile instructions or imported tarballs
- **Metadata** — environment variables, default command, exposed ports, labels, author
- **Config** — architecture (amd64/arm64), OS (linux), user to run as
Images do not run. Containers are instantiated from images. One image can spawn unlimited containers; each container gets its own writable layer.
### Tags vs Digests
| Identifier | Format | Mutability | Use case |
|------------|--------|------------|----------|
| **Tag** | `nginx:1.25-alpine` | Mutable — tag can be repointed | Human-readable versions |
| **Digest** | `nginx@sha256:abc123...` | Immutable — content-addressed | Reproducible deploys, security audits |
**Production rule:** Pin by **digest** in Kubernetes manifests and Terraform when reproducibility matters. Use tags for developer convenience (`latest` is convenient but dangerous in prod).
```bash
docker pull nginx:1.25-alpine
docker inspect nginx:1.25-alpine | grep -i digest Image Layers and Caching¶
Each Dockerfile instruction typically creates a layer:
FROM alpine:3.19 # layer 1 — base
RUN apk add curl # layer 2 — package install
COPY app /app # layer 3 — application code
CMD ["/app/start.sh"] # metadata — may not add layer
Properties:
- Layers are content-addressed by SHA256 hash
- Identical layers are shared across images on the same host
- Rebuilds cache unchanged layers — critical for CI speed
- Order matters: put rarely changing instructions first
docker history shows layer sizes and creation commands — essential for slimming bloated images.
Registries and Repositories¶
| Term | Meaning | Example |
|---|---|---|
| Registry | Server storing images | Docker Hub, Amazon ECR, Google GAR |
| Repository | Collection of related image tags | library/nginx, myorg/payment-api |
| Tag | Label within a repository | 1.25, latest, sha-a1b2c3d4 |
Full reference format:
Docker Hub's official images use the library/ namespace implicitly: nginx equals docker.io/library/nginx.
Pull Policy and Storage¶
When you docker run or docker pull:
- Docker checks local cache for the tag
- If missing or
--pull alwaysspecified, contacts registry - Downloads missing layers (parallel downloads)
- Verifies checksums and unpacks to
/var/lib/docker
Storage consumption is layer-based — deleting one tag may not free disk if layers are shared with other images.
Tagging Strategies for DevOps¶
Common tagging patterns:
| Pattern | Example | Purpose |
|---|---|---|
| Semantic version | myapp:2.4.1 | Release tracking |
| Git SHA | myapp:sha-a1b2c3d4 | CI/CD traceability |
| Environment | myapp:staging | Promotion workflow (mutable) |
| Branch | myapp:feature-auth | Preview deployments |
| Date | myapp:2026-07-27 | Scheduled batch releases |
Align Docker tags with Git tags and CI pipeline variables. GitLab CI uses $CI_COMMIT_SHA; GitHub Actions uses $GITHUB_SHA for immutable build references.
Image Removal and Garbage Collection¶
| Command | Scope |
|---|---|
docker rmi IMAGE | Remove one image (fails if containers reference it) |
docker rmi -f IMAGE | Force remove (dangerous if containers running) |
docker image prune | Remove dangling images (untagged layers) |
docker system prune -a | Remove unused images, containers, networks (careful!) |
Dangling images appear when rebuilds produce new layer sets but old untagged layers remain — common in CI without prune policies.
Hands-on Lab¶
Work through these steps on your Docker host. Requires network access to pull images.
Step 1 – Pull an image with an explicit tag¶
Command:
Explanation: Pinning to 1.25-alpine avoids surprise changes from latest. --digests shows the immutable content hash.
Expected output:
Step 2 – Pull by digest (immutable reference)¶
Command:
DIGEST=$(docker images nginx:1.25-alpine --digests | awk 'NR==2 {print $3}')
echo "Digest: $DIGEST"
docker pull "nginx@${DIGEST}"
Explanation: Digest pulls guarantee exact content regardless of tag changes. Production deploys should reference digests in manifests.
Expected output:
Step 3 – Tag an image for a custom registry workflow¶
Command:
docker tag nginx:1.25-alpine localhost:5000/demo/web:v1
docker tag nginx:1.25-alpine demo/web:staging
docker images | grep -E 'nginx|demo/web'
Explanation: docker tag creates a new reference to the same image ID — no data copied. Simulates promoting an image to a private registry namespace.
Expected output:
Step 4 – Inspect image metadata¶
Command:
docker image inspect nginx:1.25-alpine | grep -E '"Architecture"|"Os"|"ExposedPorts"|"Cmd"' | head -8
docker image inspect demo/web:staging | grep '"Id"' | head -1
Explanation: Inspect reveals architecture, default command, env vars, and labels. Verify multi-arch before deploying to arm64 nodes.
Expected output:
"Architecture": "amd64",
"Os": "linux",
"ExposedPorts": { "80/tcp": {} },
"Cmd": [ "nginx", "-g", "daemon off;" ],
"Id": "sha256:..."
Extract a short image ID with Python when scripting:
docker image inspect demo/web:staging | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['Id'][:12])"
Step 5 – Examine layer history¶
Command:
Explanation: History maps layers to build steps. Large layers indicate optimization opportunities (covered in Dockerfile tutorials).
Expected output:
IMAGE CREATED CREATED BY SIZE
<missing> 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
...
Step 6 – Compare image sizes¶
Command:
Explanation: Base image choice dramatically affects pull time, attack surface, and storage. Alpine variants are typically smaller than Debian-based images.
Expected output:
Step 7 – Run a container and attempt image removal¶
Command:
Explanation: Removal fails while a container references the image — Docker protects running workloads.
Expected output:
Step 8 – Remove container, then remove unused tags¶
Command:
docker stop img-lab && docker rm img-lab
docker rmi demo/web:staging localhost:5000/demo/web:v1
docker image prune -f
docker images | grep -E 'nginx|demo'
Explanation: Remove containers before images they reference. image prune cleans dangling layers from rebuilds. The nginx:1.25-alpine image remains if still tagged.
Expected output:
Commands & Code¶
| Command | Description | Example |
|---|---|---|
docker pull | Download image from registry | docker pull redis:7-alpine |
docker images | List local images | docker images --digests |
docker tag | Create tag alias for image | docker tag app:v1 reg.io/app:v1 |
docker rmi | Remove image | docker rmi app:old |
docker image inspect | Detailed image JSON metadata | docker image inspect nginx |
docker history | Show layer build history | docker history --no-trunc app |
docker image prune | Remove dangling images | docker image prune -f |
docker save / docker load | Export/import image tarballs | docker save -o app.tar app:v1 |
Image audit script for CI/CD¶
Save as ~/bin/docker-image-audit.sh:
#!/usr/bin/env bash
# docker-image-audit.sh — summarize local images for supply-chain review
set -euo pipefail
section() { printf '\n=== %s ===\n' "$1"; }
section "Local Images"
docker images --digests | head -20
section "Dangling Images"
DANGLING=$(docker images -f dangling=true -q | wc -l | tr -d ' ')
echo "Dangling count: $DANGLING"
section "Disk Usage"
docker system df
section "Sample Inspect — first nginx tag"
IMG=$(docker images nginx --no-trunc | awk 'NR==2 {print $1":"$2}')
if [ -n "$IMG" ] && [ "$IMG" != "REPOSITORY:TAG" ]; then
docker image inspect "$IMG" | grep -E '"Architecture"|"Os"|"RootFS"' | head -5
else
echo "No nginx image found — run: docker pull nginx:alpine"
fi
Make executable: chmod +x ~/bin/docker-image-audit.sh
In GitLab CI, tag images with $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA. For GitHub Actions, inject registry credentials via the platform's secrets mechanism — never hard-code tokens in workflow YAML committed to docs.
Common Mistakes¶
Using latest in production
latest is a moving target. A redeploy without code changes can pull a different image. Pin semver tags or digests in production manifests.
Assuming docker rmi frees all disk space
Shared layers persist while other images reference them. Use docker system df to understand actual usage before aggressive pruning.
Tagging without pushing to registry
Local tags do not survive host failure. CI must push to ECR/GCR/Harbor for artifacts to be durable and deployable on other nodes.
Ignoring image architecture
Pulling amd64 images on arm64 hosts fails or runs through slow emulation. Use docker buildx for multi-arch builds in later tutorials.
Best Practices¶
Tag every CI build with the Git commit SHA
Traceability from production back to source requires matching image tags to Git commits. Store the mapping in your deployment records.
Scan images before deployment
Integrate Trivy, Grype, or Snyk in CI. Image size and layer count matter less than known CVEs in production paths.
Prefer minimal base images
Start from alpine, distroless, or slim variants. Smaller images pull faster, reduce attack surface, and save registry storage costs.
Schedule controlled prune jobs on build agents
CI runners accumulate layers. Automate docker system prune --filter until=168h (7 days) rather than manual -a prunes that delete cached dependencies.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
manifest not found | Wrong tag or deleted image | Verify tag on registry UI; check typos |
no space left on device during pull | Disk full under /var/lib/docker | docker system df; prune unused images; expand volume |
conflict: unable to remove on rmi | Container still references image | Stop/rm container first; use docker ps -a |
| Pull is slow | Large image or distant registry | Use registry mirror; slim base images; pre-pull on nodes |
exec format error at runtime | Architecture mismatch | Pull/build correct arch or enable buildx multi-platform |
| Unexpected image content after pull | Tag was repushed by maintainer | Pin by digest; verify signatures (Cosign) |
| Dangling images accumulate | CI rebuilds without prune | Schedule docker image prune; use --rm in CI |
Summary¶
- Images are immutable, layered templates; containers are running instances with writable layers
- Tags are human-readable and mutable; digests are immutable content hashes for reproducible deploys
docker pull,tag,inspect,history, andrmiform the core local image workflow- Layers are shared and cached — order Dockerfile instructions from least to most frequently changing
- Remove containers before images; use
docker system dfand controlled prune policies for disk hygiene - Next: build your own images in Building Images with Dockerfile
Interview Questions¶
- What is the difference between a Docker image and a Docker container?
- Explain the difference between an image tag and a digest.
- Why should you avoid using
latestin production deployments? - What happens when you
docker tagan existing image? - How do image layers affect build speed and disk usage?
- Why does
docker rmifail when a container still exists? - What is a dangling image, and how do you remove it?
- How would you audit what packages are inside an image without running it?
- What is the full image reference format including registry and repository?
- How do Docker image tags relate to Git commit SHAs in CI/CD?
Sample Answers (Questions 2, 6, and 10)
Q2 — Tag vs digest: A tag (e.g., nginx:1.25-alpine) is a mutable label pointing to an image. Maintainers can repush the same tag with updated content. A digest (e.g., nginx@sha256:abc123...) is a SHA256 hash of the manifest — immutable and content-addressed. Pinning by digest guarantees the exact same bits deploy every time.
Q6 — rmi failure: Docker tracks which containers reference each image. If any container (running or stopped) was created from that image, docker rmi refuses to delete it to prevent breaking container inspection, restart, and layer integrity. Remove or retag containers first, then remove the image.
Q10 — Tags and Git SHAs: CI pipelines build an image from a Dockerfile at a specific Git commit, then tag the image with the commit SHA (e.g., myapp:sha-a1b2c3d4) and push to a registry. Deployments reference that tag, creating a direct link from production runtime to source code. Git tags (v2.1.0) often map to release image tags for human-friendly promotion.
Related Tutorials¶
- Docker – Category Overview
- Running Your First Container (previous in Module 2)
- Building Images with Dockerfile (next in Module 2)
- Container Registries and Distribution (Module 4)
- Introduction to Git and Version Control