For a multi-tenant backend, I keep tenant configuration out of the Docker build whenever the tenants run the same code.
One application image. Tenant configuration at runtime. Separate deployment credentials and data boundaries.
In GitHub Actions, I copy package manifests before application source, use npm ci, and reuse BuildKit cache. A source edit shouldn’t force a fresh dependency installation.
The multi-stage Dockerfile keeps compilers and build tools out of the runtime image.
I also tag images with the commit SHA and promote the same artifact through staging and production.
Build speed matters, but so does knowing that the image tested in staging is the image being deployed. Database migrations get an explicit step, with compatibility checked before rollout.