Images are the units of distribution in the container world. Understanding how they are structured, named, and stored is the foundation for everything else.
Anatomy of an Image
An image is not a single file. It is a manifest pointing to a stack of layers, plus a small JSON config describing how to run it. Each layer is a tarball of filesystem changes — files added, modified, or deleted relative to the layer below.
┌─────────────────────────┐
│ Your app code (layer 4) │
├─────────────────────────┤
│ npm install (layer 3) │
├─────────────────────────┤
│ Node.js runtime (layer 2) │
├─────────────────────────┤
│ Debian base (layer 1) │
└─────────────────────────┘
Layers are content-addressable (named by SHA256 hash) and shared. If two images both use the same Debian base layer, it is downloaded and stored once. This makes images efficient to distribute — pulling a new version of your app may only download the top layers.
Naming and Tags
Image references look like:
[registry/]repository[:tag]
nginx # implicit: docker.io/library/nginx:latest
nginx:1.27 # nginx version 1.27 from Docker Hub
ghcr.io/myorg/myapp:v1.2.3 # from GitHub Container Registry
123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:prod # from AWS ECR
Tags are mutable — the maintainer of nginx:latest can republish a different image under that same tag tomorrow. For reproducible deployments, pin to a specific version (nginx:1.27.0) or, for the strongest guarantee, pin to a digest:
docker pull nginx@sha256:0c... # always the same bytes, forever
Pulling and Listing
docker pull nginx:1.27 # download from registry
docker images # list local images
docker inspect nginx:1.27 # show full metadata, layers, env, ports
docker history nginx:1.27 # show how the image was built, layer by layer
docker rmi nginx:1.27 # remove the image locally
Registries
| Registry | Use case |
|---|---|
| Docker Hub (docker.io) | The default public registry; hosts official images (nginx, postgres, ubuntu) and your own personal images |
| GitHub Container Registry (ghcr.io) | Free for open-source projects; tightly integrated with GitHub Actions |
| Amazon Elastic Container Registry (ECR) | Private images for AWS workloads; integrates with IAM and ECS/EKS |
| Azure Container Registry (ACR) | Private images on Azure with ACR Tasks for builds |
| Google Artifact Registry (GAR) | Replaces the older GCR; hosts container, Maven, npm, and Python artifacts |
| Harbor | Open-source self-hosted registry with vulnerability scanning and signing |
Logging In and Pushing
docker login # Docker Hub
docker login ghcr.io -u USER -p TOKEN # GHCR with personal access token
aws ecr get-login-password | docker login --username AWS --password-stdin \
123456789.dkr.ecr.us-east-1.amazonaws.com
# Tag a local image for the target registry, then push
docker tag myapp:1.0 ghcr.io/myorg/myapp:1.0
docker push ghcr.io/myorg/myapp:1.0
Official vs Verified vs Anyone
On Docker Hub, watch for these badges:
- Docker Official Image — curated by Docker (e.g.,
nginx,postgres,python). Maintained, scanned, and trustworthy. - Verified Publisher — official images from companies (e.g.,
bitnami/redis,elastic/elasticsearch). - Sponsored OSS — supported open-source projects.
- Community — anyone can push. Treat with caution; pin specific versions and scan.
The "latest" Trap
latest is just a default tag — it does not guarantee newest, and it changes silently. In production:
- Always tag your own builds with a version (
myapp:1.2.3) or commit SHA (myapp:git-abc123) - Pin third-party base images to a specific minor version
- For maximum reproducibility, pin to digests in your Dockerfiles and Kubernetes manifests