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.