Images are built from layers. Each instruction in a Dockerfile that changes the filesystem (RUN, COPY, ADD) produces a new layer, which is a tarball of the files that changed relative to the layer below it. Layers stack using a union filesystem like OverlayFS, so the running container sees one merged view.
Layers are content-addressed by their SHA256 digest. That means two images that share a base layer store and transfer that layer only once. If a base image layer is already on a node, pulling a new app image only downloads the small top layers that differ. This is why a 5 MB code change does not force a 900 MB redownload.
Tying it together is the image manifest: a JSON document listing the digests of each layer plus a config blob that holds the entrypoint, environment variables, exposed ports, and architecture. A multi-arch image uses a manifest list so the same tag (say my-app:1.4) can resolve to an amd64 image on an Intel server and an arm64 image on an Apple Silicon laptop.
All of this follows the OCI (Open Container Initiative) image spec, which is why images built with Docker run under containerd, Podman, or CRI-O without modification.