Image Management

Image Management

Bitnesia • Sep 8, 2026 82 •ID

After learning how to create custom images using a Dockerfile, the collection of images on our machine will grow rapidly. Every experiment, tag change, and rebuild leaves behind new images or old layers accumulating silently. This chapter discusses how to manage that image collection: viewing the list of stored images, assigning or changing tags, removing unused images, inspecting image details, and building habits for optimizing image size to streamline distribution and operations. Developers who regularly experiment with Dockerfiles and Sysadmins/DevOps Engineers who maintain server disk usage both need structured image management habits.

6.1 Viewing and Listing Images

The most basic command for viewing stored images on our machine is docker image ls. The alias docker images is also available and functions identically; both display all local images alongside their repository, tag, ID, creation time, and size.

docker image ls

Example output from the above command:

REPOSITORY                 TAG       IMAGE ID       CREATED        SIZE
docker-demo                1.0       a1b2c3d4e5f6   2 hours ago    142MB
nginx                      1.27      f7e8d9c0b1a2   3 days ago     54.6MB
alpine                     3.20      7144f7bab3d4   2 weeks ago    11.7MB

The IMAGE ID column is a short unique identifier for each image, while the combination of REPOSITORY and TAG provides a human-readable name. If the image list grows long, results can be filtered using the --filter option. For example, to display only images containing "alpine" in their name:

docker image ls --filter reference=alpine

The --filter option also accepts other criteria, such as label, to filter based on LABEL metadata defined in a Dockerfile.

docker image ls --filter "[email protected]"

For automation or scripting needs, the --format option allows selecting specific columns using Go template syntax. Available placeholders include .ID, .Repository, .Tag, .CreatedSince, and .Size.

docker image ls --format "{{.Repository}}:{{.Tag}}\t{{.Size}}"

In practice, Sysadmins and DevOps Engineers frequently combine --filter and --format within automated cleanup scripts, such as listing all images belonging to a specific project before deciding which ones to keep.

6.2 Tagging Images

A Tag is a label attached to an image to mark its version or variant, as discussed in the chapter on image naming and tagging. This section focuses on managing tags for existing local images rather than during the build process.

The docker image tag command (aliased as docker tag) adds a new tag to an existing image without duplicating its layers. The new tag simply creates an additional pointer referencing the exact same layer.

docker pull alpine:3.20
docker image tag alpine:3.20 alpine:demo

Verify the result using docker image ls; both tags alpine:3.20 and alpine:demo appear as separate rows but share an identical IMAGE ID, confirming they point to the exact same layer.

docker image ls alpine

This pattern is commonly used to prepare an image before pushing it to a registry, such as tagging it according to the destination repository name.

docker image tag alpine:3.20 registry.contoh.internal/tim-backend/alpine:3.20

Note the tag formatting rules: only lowercase letters, digits, periods, underscores, and hyphens are allowed, with a maximum length of 128 characters. Using uppercase letters or spaces, such as alpine:Demo Baru, results in Docker rejecting the request with an invalid reference format error.

To remove a tag without deleting the underlying image layers (provided another tag still references them), use docker rmi with the tag name to be detached.

docker rmi alpine:demo

The output Untagged: alpine:demo indicates that only the tag reference was removed; the original image layers remain because they are still referenced by alpine:3.20. Clean up the sample registry tag created above as well.

docker rmi registry.contoh.internal/tim-backend/alpine:3.20

6.3 Removing Images

Accumulating images without periodic cleanup eventually consumes significant disk space, particularly on CI/CD servers that build new images daily. The docker image rm command (aliased as docker rmi) removes one or more images specified by name or ID. Pull a sample image and start a container from it to observe how Docker handles images currently in use.

docker pull nginx:1.27
docker run -d --name demo-nginx nginx:1.27

Attempt to remove the image while the demo-nginx container is still running.

docker image rm nginx:1.27

Docker rejects the deletion and returns the error message conflict: unable to remove repository reference "nginx:1.27" (must force) - container <container_id> is using its referenced image <image_id>. A similar message appears if the container is stopped but not removed; Docker considers the image in use as long as the container exists. This safeguard prevents accidental deletion of images needed by existing containers.

The -f option on docker image rm forces removal even if the image is referenced by a container, including running ones like demo-nginx above.

docker image rm -f nginx:1.27

This command succeeds and displays Untagged and Deleted outputs, but it is not a safe practice: the demo-nginx container referencing that image is not removed, leaving it pointing to a non-existent image reference. If that container is stopped and subsequently restarted via docker start, Docker will fail to find its base image. Avoid using -f routinely; the safer workflow is to remove the associated container via docker rm first, then remove the image without requiring the -f flag.

docker rm -f demo-nginx

For bulk cleanup of unused images, Docker provides docker image prune. Without additional options, this command removes only dangling images, which are image layers lacking any tag (typically resulting from rebuilds using an existing tag, causing the old tag to detach and leave an unnamed layer behind).

docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N]

To remove all images not referenced by any container (not just dangling ones), pass the -a option. This command is significantly more aggressive, as it removes tagged images that have not been used by any container on the host.

docker image prune -a

Due to its destructive nature, always review warning prompts before confirming with y, and consider using the --filter option to constrain deletion, such as targeting images older than a specified timeframe.

docker image prune -a --filter "until=24h"

Executing docker image prune -a on a production server without checking affected images carries the risk of deleting standby images reserved for rollback procedures, even if those images are not actively used by running containers. Always inspect images with docker image ls or apply targeted --filter conditions before running prune operations in production environments.

6.4 Inspecting Images

Extracting detailed image configuration beyond basic names and sizes is often necessary, such as checking default environment variables, entrypoints, or target OS architectures. The docker image inspect command displays this comprehensive metadata in JSON format.

docker image inspect nginx:1.27

The output is extensive, containing fields such as Config (which includes Env, Cmd, Entrypoint, ExposedPorts), Architecture, Os, and RootFS (the list of image layers). To extract specific fields without parsing the entire JSON payload, use the --format option with Go template syntax.

docker image inspect --format '{{json .Config.Env}}' nginx:1.27
docker image inspect --format '{{.Architecture}}/{{.Os}}' nginx:1.27

If an invalid image name is provided or the image has not been pulled locally, docker image inspect returns the error Error: No such image: image-name. Verify the image name and tag, or run docker image ls to confirm local availability.

In addition to inspect, the docker image history command displays the sequence of layers forming an image, along with the Dockerfile instructions and sizes associated with each layer.

docker image history nginx:1.27
IMAGE          CREATED        CREATED BY                                      SIZE
f7e8d9c0b1a2   3 days ago     CMD ["nginx" "-g" "daemon off;"]                0B
<missing>      3 days ago     STOPSIGNAL SIGQUIT                              0B
<missing>      3 days ago     EXPOSE 80                                       0B
<missing>      3 days ago     RUN /bin/sh -c set -x && addgroup ...           9.7MB
<missing>      3 days ago     ENV NGINX_VERSION=1.27.0                        0B

The CREATED BY column in docker image history displays the exact Dockerfile instruction generating each layer, while the SIZE column indicates the space contributed by that layer to the total image size. A value of <missing> in the IMAGE column does not indicate corrupt or lost data; it identifies an intermediate layer without its own image ID created during the base image build process rather than by top-level Dockerfile instructions.

Combining docker image inspect and docker image history provides an effective initial diagnostic step when investigating unexpectedly large image sizes prior to optimization.

6.5 Image Optimization and Size Reduction

Large image sizes slow down registry push and pull operations, increase the security attack surface, and extend startup latency in orchestrators downloading images across nodes. This section summarizes strategies for reducing image footprints beyond multi-stage build techniques.

To demonstrate these optimization principles, set up a test project. Create a working directory with an inefficient Dockerfile that generates a 50MB temporary file and deletes it in a separate RUN instruction.

mkdir optimize-demo && cd optimize-demo
FROM alpine:3.20
RUN dd if=/dev/zero of=/tmp/dummy.bin bs=1M count=50
RUN rm -f /tmp/dummy.bin

Build the image using standard commands.

docker build -t optimize-demo:1.0 .

Prior to optimizing, measure image layer sizes rather than relying on assumptions. Use docker image history to identify layers contributing most to total size, focusing cleanup efforts accordingly.

docker image history --no-trunc optimize-demo:1.0

The --no-trunc option displays full instructions without truncation, which is useful when analyzing long RUN commands. In the output, the layer created by the dd instruction accounts for roughly 50MB, whereas the subsequent rm instruction shows 0B, even though the file no longer exists in the final filesystem. This confirms that removing files in a separate RUN instruction does not reduce the image size, as previous layers containing the file persist permanently in the image history.

Combine related commands into a single RUN instruction. Refactor the Dockerfile to execute file creation and deletion within a single RUN statement, then rebuild with a new tag for comparison.

FROM alpine:3.20
RUN dd if=/dev/zero of=/tmp/dummy.bin bs=1M count=50 && rm -f /tmp/dummy.bin
docker build -t optimize-demo:2.0 .

Compare image sizes before and after optimization. Validate size differences directly to confirm the reduction.

docker image ls --format "{{.Repository}}:{{.Tag}}\t{{.Size}}" optimize-demo

The optimize-demo:2.0 image will be approximately 50MB smaller than optimize-demo:1.0, despite both images having identical final filesystems. This efficiency stems entirely from consolidating commands within a single RUN instruction. The same principle applies to downloading archives, extracting them, and removing original artifacts, or installing packages via apt/apk and clearing package caches in the same execution layer. Clean up test images upon completion.

docker rmi optimize-demo:1.0 optimize-demo:2.0

Switch to a smaller base image. Base image sizes vary significantly; full distributions like ubuntu or debian often range from tens to hundreds of megabytes, whereas alpine variants for the same base stack may take only a few megabytes. Compare local base image footprints directly.

docker pull python:3.12
docker pull python:3.12-slim
docker pull python:3.12-alpine
docker image ls python

Thorough testing is recommended when adopting alpine, as it uses musl libc instead of glibc used by Debian/Ubuntu. Some Python packages with native C dependencies may fail to compile or exhibit runtime differences. The slim variant provides a balanced alternative when glibc compatibility is required but full distribution size is unnecessary.

Utilize dedicated layer analysis tools for complex cases. Beyond built-in docker image history commands, third-party tooling can visually inspect image layer composition and highlight high-footprint files when sources of bloat are not immediately evident from Dockerfile instructions alone.

Finally, establish routines for regular image maintenance alongside optimization practices. Schedule automated docker image prune tasks (with appropriate filters) on build servers to prevent obsolete image accumulation from consuming host disk capacity.

TechniquePrimary Impact
Minimal base image (alpine/slim)Reduces base footprint before adding build instructions
Combine download, extract, and cleanup in one RUNPrevents temporary files from persisting in image layers
Clear package manager cache within the same RUNPrevents package caches from increasing layer size
Audit layers using docker image historyIdentifies layers contributing most to overall image size
Scheduled docker image prune (with filters)Prevents accumulation of legacy images on disk