Order Dockerfile steps from least-changing to most-changing. COPY package.json package-lock.json ./ and RUN npm ci belong above COPY . . so deps are cached across most builds.
Use multi-stage builds: a fat build stage with toolchains, a slim runtime stage with just the output. Final images shrink from hundreds of MB to tens.
Pin base images by digest in production. Tag updates change semantics; digest pins do not.