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:
| Base | Download | What's inside | Runs as |
|---|---|---|---|
node:22 (full) | about 410 MB | Debian, a shell, compilers, git, curl, apt-get | root |
node:22-slim | about 80 MB | Debian with a shell and apt-get, no build tools | root |
node:22-alpine | about 60 MB | Alpine Linux, a shell, apk | root |
gcr.io/distroless/nodejs22-debian12 | about 53 MB | Node and the libraries it needs; no shell, no package manager | root, 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