Learning paths / Containers / Images and layers

Choosing a base image

Reading · 6 min · Module 2, lesson 3 of 523 min left in this module

Module 2 · Images and layersLesson 3 of 5

Goal: Compare full, slim, Alpine and distroless base images, and choose one for building and one for running.

Key idea

The base image is everything under your app: every file in it ships with your image and runs next to your code. Pick the smallest base your app can run on. Tools you only need to build the app belong in a separate build stage, not in what you ship.

Four kinds of base

Most language images come in the same four flavours. Here they are for Node 22, with the download size for amd64 at the time of writing:

BaseDownloadWhat's insideRuns as
node:22 (full)about 410 MBDebian, a shell, compilers, git, curl, apt-getroot
node:22-slimabout 80 MBDebian with a shell and apt-get, no build toolsroot
node:22-alpineabout 60 MBAlpine Linux, a shell, apkroot
gcr.io/distroless/nodejs22-debian12about 53 MBNode and the libraries it needs; no shell, no package managerroot, or nonroot with the :nonroot tag

What you trade

Full images have every tool you might need to compile a native module. That's also their problem: every program in the image is something to patch, and something an attacker can use if they get in.

Slim keeps Debian and drops the build tools. It's the safe default when you want a shell to debug with.

Alpine is small, but it uses a different C library (musl instead of glibc). Most apps don't notice. A few native modules and prebuilt binaries behave differently or need rebuilding, so test on it before you switch.

Distroless has your language runtime and almost nothing else. There's no shell, so an attacker who gets in has no tools, and scanners have little to report. The catch: you can't open a shell inside it to look around. Logs, exec and restart behaviour shows how to debug without one.

The lab images are distroless

learn-hello-web is a Go app. Go can compile to a single file that needs no runtime at all, so its image uses the smallest distroless base, gcr.io/distroless/static-debian12:nonroot: certificates, time zones and a non-root user, and nothing else. The whole image is about 3 MB to download.

Try to open a shell in it and there's nothing to run. The error ends:

docker run --rm --entrypoint sh quay.io/computesphere/learn-hello-web:1.0.0
… exec: "sh": executable file not found in $PATH

Build on one, run on another

You don't have to choose one base for everything. Compile or install on a full image, then copy only the result onto slim or distroless. That's a multi-stage build, and it's how learn-hello-web was made: Multi-stage builds shows how.

Whichever you pick, name a version (node:22-slim, not node), so the runtime doesn't change under you.

Check yourself

Your Node app needs a C compiler to install one of its packages, but not to run. Which setup fits best?
Something is wrong inside a running learn-hello-web container. Why can't you open a shell in it?