Measure first: where is the fat
Before touching anything, check the size with `docker images`, then analyze each layer with `dive` to see what was added where. No measurement, no direction.
docker images
# 或逐层分析:
dive <image>Step 1: smaller base image
Swap `ubuntu`/`debian` for `alpine`, `slim` or `distroless`. This alone often cuts hundreds of MB. Note alpine uses musl libc, so some binaries may need recompiling.
FROM node:20-alpine # 而非 node:20(基于 debian)Step 2: multi-stage build
Put build dependencies in a build stage and COPY only the compiled artifact into the final image. The runtime stage does not need gcc or full node_modules source.
FROM golang:1.22 AS build
RUN go build -o app .
FROM gcr.io/distroless/static
COPY --from=build /app /app
ENTRYPOINT ["/app"]Step 3: do not skip .dockerignore
Without `.dockerignore`, `COPY . /app` packs node_modules, .git and local logs into the image. Add an ignore list to save space immediately.
node_modules
.git
*.log
.envStep 4: merge RUN and clean caches
Each RUN is a layer. Put install and cleanup in the same RUN so you do not leave "installed then deleted" bloat in an earlier layer.
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*Step 5: verify and lock in
After rebuilding, compare sizes with `docker images`, then bake the best practices into your project template so it does not bloat again.