Walk through the package-manager flags and cleanup steps you use to keep Debian/Ubuntu and Alpine container images small during a build, and explain what each one saves.
answer
- --no-install-recommends first
- rm -rf /var/lib/apt/lists/* same RUN
- apk add --no-cache
- apk --virtual .build-deps + apk del
- pip --no-cache-dir, npm cache clean
basics
~20 sOn Debian/Ubuntu: apt-get install with --no-install-recommends, then rm -rf /var/lib/apt/lists/* in the same RUN. On Alpine: apk add --no-cache, and apk del a --virtual group for build-only tools. For language tools use pip --no-cache-dir and npm cache clean --force.
solid answer
~50 sFor Debian-family images I write one RUN: `apt-get update && apt-get install -y --no-install-recommends <pkgs> && rm -rf /var/lib/apt/lists/*`. `--no-install-recommends` drops suggested extras, which routinely halves an install; removing `/var/lib/apt/lists` drops the package index (tens of MB) that is useless at runtime. I set `DEBIAN_FRONTEND=noninteractive` for that step so installs never block on prompts. Alpine's `apk add --no-cache` does it in one flag: the index is fetched for the transaction and `/var/cache/apk` is never written. For build-only packages I use a virtual group and delete it in the same RUN: `apk add --no-cache --virtual .build-deps gcc musl-dev && ... && apk del .build-deps`. Language ecosystems have the same trap: `pip install --no-cache-dir`, `npm ci --omit=dev && npm cache clean --force`. Better still, do installs in a builder stage, or use BuildKit cache mounts so caches speed up rebuilds without entering a layer.
code
dockerfile · 10 linesFROM debian:12-slim
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
# Alpine variant
# RUN apk add --no-cache --virtual .build-deps gcc musl-dev make \
# && make install \
# && apk del .build-depsgo deeper
Recite the two canonical one-liners for apt and apk and say what each flag removes.
Explain why update and install must be chained, and add the virtual-package pattern for build-only toolchains.
Rank the levers: base image and multi-stage first, package hygiene second, and use BuildKit cache mounts so speed and size stop competing.
Push this into shared base images and build templates so product teams inherit the hygiene instead of copy-pasting flags.
## Why package managers bloat images A package manager on a normal machine assumes it will be used again: it keeps an index of every available package, keeps the downloaded archives so reinstalls are fast, and installs 'recommended' companions because a human might want them. None of that holds for a container image, which is built once and usually run read-only. Each behaviour becomes permanent bytes in a layer. ## Debian and Ubuntu Three things matter. 1. **`--no-install-recommends`** — apt pulls Recommends by default, which can drag in documentation, editors, or an entire graphical stack behind one dependency. Turning it off is usually the single biggest win; if something breaks, add the specific package back explicitly. 2. **`rm -rf /var/lib/apt/lists/*`** — `apt-get update` downloads package indexes, often 30-80 MB, needed only for the install itself. Delete them in the same RUN, because a separate RUN cannot reclaim them. Official images also ship a docker-clean apt config that discards downloaded `.deb` files from `/var/cache/apt/archives`. 3. **`DEBIAN_FRONTEND=noninteractive`** — not a size win, but it stops installs hanging on configuration prompts. Scope it to the build (an `ARG`, or an `ENV` you accept at runtime). Always combine `apt-get update` and `apt-get install` in one RUN. A cached update layer paired with a later install can resolve against a stale index and fail with 404s or install superseded versions. ## Alpine `apk add --no-cache` skips writing `/var/cache/apk` entirely, so there is nothing to clean afterwards. For toolchains needed only at build time the virtual-package pattern makes removal exact: `apk add --no-cache --virtual .build-deps gcc musl-dev make`, do the work, then `apk del .build-deps` in the same RUN so the compiler never reaches a committed layer. ## Language package managers The same reasoning applies a level up. pip caches wheels in `~/.cache/pip` (`--no-cache-dir`); npm keeps a content-addressed cache in `~/.npm` (`npm ci --omit=dev` plus `npm cache clean --force` in the same RUN); Maven and Gradle caches are large enough that BuildKit cache mounts are the right answer — fast rebuilds, zero image bytes. ## Where to stop These flags are cheap and should be reflexive, but they optimise the wrong end if the base image or shipped toolchain is the real problem: a 900 MB full-JDK image is not rescued by deleting apt lists. Usual order of impact is base image and multi-stage build first, then package hygiene, then micro-optimisation. And do not strip so hard that the image breaks — removing `ca-certificates`, `tzdata` or locale data breaks TLS, timestamps and text handling in ways that only surface in production.
- Why must apt-get update and apt-get install be in the same RUN instruction?Because the update layer can be served from the build cache while the install line changes. The stale index then points at package versions that have been superseded in the repository, so the build fails with 404s or silently installs an old set. Chaining them means any change to the package list also refreshes the index.
- How do BuildKit cache mounts change this advice?A cache mount keeps the package cache on the build host rather than in the image, so you get fast repeated builds and zero shipped bytes at once. You still avoid installing recommends, but you no longer have to choose between deleting the cache for size and keeping it for speed.
saying these in an interview costs you the question
- Running apt-get update in its own RUN instruction
- Using apt-get clean but leaving /var/lib/apt/lists behind
- Thinking apk needs manual cache cleanup even with --no-cache
- Deleting ca-certificates or tzdata to save a few MB and breaking TLS
- Micro-optimising packages while shipping a full JDK or full OS base