Skip to content
beginner Phase 1 · Docker Fundamentals

Docker Images

Pull, build, tag, and manage Docker images from registries.

1h
0 problems
Topic Progress 0%

Anatomy of a Docker Image

Anatomy of a Docker Image

A Docker image is composed of layers — each layer represents a filesystem change. Layers are stacked and merged using a Union Filesystem (overlay2, aufs) to create the final container filesystem.

Layer Structure

Consider this Dockerfile:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
COPY requirements.txt /app/
RUN pip3 install -r /app/requirements.txt
COPY . /app
CMD ["python3", "/app/main.py"]

Each instruction produces a layer:

Layer 1: Ubuntu 22.04 base (~78MB)
Layer 2: Python 3 installation (~60MB)
Layer 3: requirements.txt copy (~1KB)
Layer 4: pip install dependencies (~45MB)
Layer 5: Application code (~2MB)
Layer 6: CMD metadata (0 bytes)

How Layers Work

  • Layers are read-only and shared between images. If two images use ubuntu:22.04 as base, they share the same base layer on disk.
  • When a container runs, Docker adds a thin writable layer on top. All changes (new files, modifications, deletions) go here.
  • Docker uses content-addressable storage — each layer is identified by its SHA256 hash. If the hash matches an existing layer, Docker uses the cached version.

Layer Inspection

# View image layers with sizes
docker history nginx:1.25

IMAGE          CREATED       CREATED BY                                      SIZE
a9edb18cadd1   2 weeks ago  /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
81d61046be0f   2 weeks ago  /bin/sh -c #(nop) STOPSIGNAL SIGQUIT            0B
a2abf6c4d29d   2 weeks ago  /bin/sh -c #(nop) EXPOSE map[80/tcp:{}]         0B
d76491233e58   2 weeks ago  /bin/sh -c set -x     && groupadd --system -…  110MB

Immutable Infrastructure

Images are immutable. Once built, a layer never changes. This immutability provides:

  • Reproducibility: The same image produces identical containers every time
  • Safety: You cannot accidentally modify a running image
  • Efficiency: Layers are shared and cached across hosts
  • Auditability: You can trace exactly what changed between versions

Pull, Tag, and Push Operations

Pull, Tag, and Push Operations

Pulling Images

# Pull latest tag
docker pull nginx

# Pull specific version
docker pull nginx:1.25-alpine

# Pull from Docker Hub (explicit)
docker pull docker.io/library/nginx:1.25

# Pull from private registry
docker pull registry.company.com/myapp:v2.1

# Pull by digest (immutable reference)
docker pull nginx@sha256:abc123def456789...

When you pull an image, Docker downloads only layers not already present locally. This makes subsequent pulls very fast.

Tagging Images

Tags are human-readable labels pointing to image digests:

# Tag an existing image
docker tag nginx:1.25 myrepo/nginx:v1

# Tag with registry prefix
docker tag myapp:latest registry.company.com/myapp:latest

# Tag for multiple registries
docker tag myapp:latest gcr.io/myproject/myapp:latest
docker tag myapp:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest

Pushing Images

Before pushing, you must authenticate:

# Log in to Docker Hub
docker login

# Log in to private registry
docker login registry.company.com

# Push image
docker push myrepo/nginx:v1

# Push to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.us-east-1.amazonaws.com
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest

Image Naming Conventions

# Docker Hub official image
nginx:1.25

# Docker Hub user image
username/myapp:v2

# Private registry
registry.company.com/team/project:tag

# Cloud registry
gcr.io/project-id/image:tag
123456789.dkr.ecr.us-east-1.amazonaws.com/image:tag

Best Practices for Tagging

  • Never use :latest in production — it creates ambiguity
  • Use semantic versioning: myapp:1.2.3
  • Include build metadata: myapp:1.2.3-abc1234 (git SHA)
  • Use immutable digests for critical deployments: myapp@sha256:...

Inspecting Image Contents

Inspecting Image Contents

Inspect Image Metadata

# Full JSON metadata
docker inspect nginx:1.25

# Get architecture
docker inspect --format '{{.Architecture}}' nginx:1.25

# Get environment variables
docker inspect --format '{{.Config.Env}}' nginx:1.25

# Get exposed ports
docker inspect --format '{{.Config.ExposedPorts}}' nginx:1.25

# Get entrypoint
docker inspect --format '{{.Config.Entrypoint}}' nginx:1.25

# Get working directory
docker inspect --format '{{.Config.WorkingDir}}' nginx:1.25

# Get all layers
docker inspect --format '{{.RootFS.Layers}}' nginx:1.25

View Image History

# Show build history with commands
docker history nginx:1.25

# Show non-truncated
docker history --no-trunc nginx:1.25

# Show only created-by column
docker history --format "{{.CreatedBy}}" nginx:1.25

Diff and Changes

# See filesystem changes in a container
docker diff web

# Output format:
# A /tmp/new-file       (Added)
# C /etc/config.conf    (Changed)
# D /old-file           (Deleted)

Export Image Contents

# Export entire image filesystem as tar
docker save nginx:1.25 | tar -tvf - | head -50

# Extract specific files
docker create --name temp nginx:1.25
docker cp temp:/etc/nginx/nginx.conf ./
docker rm temp

# Or use docker export
docker export temp | tar -xf - --include="etc/nginx/nginx.conf"

Analyze Image Size

# See total image size
docker images nginx:1.25
REPOSITORY   TAG    IMAGE ID       SIZE
nginx        1.25   a9edb18cadd1   187MB

# Compare sizes
docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}" | sort -k2 -h

# Find largest images
docker system df -v

Trivy Security Scan

# Install Trivy
sudo apt-get install trivy

# Scan image for vulnerabilities
trivy image nginx:1.25

# Scan with severity filter
trivy image --severity HIGH,CRITICAL nginx:1.25

# Scan Dockerfile
docker scout cves nginx:1.25

Using .dockerignore Effectively

Using .dockerignore Effectively

A .dockerignore file excludes files and directories from the build context sent to the Docker daemon. Without it, every file in the build directory is sent, including .git, node_modules, and build artifacts.

Why .dockerignore Matters

  • Build speed: Sending a 2GB project with node_modules takes minutes. With .dockerignore, it takes seconds
  • Cache efficiency: Changing a file in the build context invalidates the cache for subsequent layers
  • Security: Prevents secrets (.env, .ssh, .aws) from being included in images
  • Image size: Reduces final image size by excluding unnecessary files

Example .dockerignore

# Version control
.git
.gitignore
.github

# Dependencies
node_modules
vendor
*.jar

# Build artifacts
dist
build
*.o
*.a

# Environment and secrets
.env
.env.*
*.pem
*.key

# IDE and editor
.vscode
.idea
*.swp
*.swo

# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore

# Documentation
README.md
LICENSE
docs/

# OS files
.DS_Store
Thumbs.db

# Test files
tests/
spec/
__tests__/
coverage/

Multi-Language Examples

Python:

__pycache__
*.pyc
*.pyo
.pytest_cache
.venv
venv
*.egg-info
dist
build
.env

Go:

*.exe
test/
vendor/
.git

Java:

target/
*.class
*.jar
.mvn/
.idea/
*.iml

Build Context

# Build with default context (current directory)
docker build -t myapp .

# Build from a specific directory
docker build -t myapp -f Dockerfile /path/to/project

# Build from stdin (no build context)
docker build -t myapp - <<EOF
FROM alpine
copy . /app
EOF

When Docker runs docker build ., it tars the entire directory (minus .dockerignore exclusions) and sends it to the daemon. A large build context means slower builds and higher memory usage.