- Created a base Docker image (
Dockerfile.base) with Node 20 Alpine and a non-root user for security - Installed
@nitrostack/cliglobally in base image to reuse across all projects and reduce build time - Optimized base image by:
- Combining layers (apk + npm install)
- Cleaning npm cache
- Disabling audit/fund checks
- Ensuring global binaries are available via PATH
- Used this base image in the main multi-stage Dockerfile (builder + production stages)
- Kept npm install and build steps in the main Dockerfile to handle project-specific dependencies
- Optimized builds using:
- Docker cache (
COPY package*.jsonfirst) - BuildKit cache (
--mount=type=cache) for faster npm installs
- Docker cache (
- Ensured clean dependency installation inside Docker (avoiding local
node_modules) - Ensured only production dependencies are included in the final image to reduce size
- Added cleanup steps to remove unnecessary files from
node_modules - Implemented conditional widget handling to avoid build failures if widgets are absent
- Added healthcheck and non-root execution for better production reliability
fresh-pizza:local- Disk Usage: 418MB
- Content Size: 87.7MB
hemantwekan/fresh-pizza:1.4.0- Disk Usage: 313MB
- Content Size: 69.6MB
hemantwekan/fresh-pizza:1.5.0- Disk Usage: 317MB
- Content Size: 70.2MB
hemantwekan/fresh-pizza:1.6.0- Disk Usage: 401MB
- Content Size: 88.3MB
hemantwekan/node-base:20-alpine-v3hemantwekan/fresh-pizza:1.7.0- Disk Usage: 355MB
- Content Size: 77MB
- Reduced ~100MB+ disk usage compared to initial build (~25% improvement)
- Initial CLI integration increased size, but further optimizations reduced ~46MB from that spike
- Improved build time by:
- installing
@nitrostack/clionce in base image instead of every build - optimizing Docker layer caching (
package.jsonfirst) - using BuildKit cache for npm dependencies
- installing
- Eliminated platform-specific issues (e.g., esbuild mismatch) by ensuring clean installs inside Docker
- Achieved a balanced tradeoff between:
- build performance ⚡
- image size 📦
- Maintained overall optimized and reusable Docker architecture
- After optimization, repeated builds complete in ~0.9 seconds
- Most Docker steps show CACHED, meaning:
- dependencies are not reinstalled
- build step is skipped
- only final image assembly runs
- Docker reuses layers when:
package.jsonis unchanged → skipsnpm ci- source code is unchanged → skips build
- BuildKit cache avoids re-downloading npm packages
- Base image already contains required global dependencies
- Changes in
package.json→ reinstall dependencies - Changes in dependencies → rebuild required
- Changes in base image → cache invalidation
- Changes in source code → rebuild only (not reinstall)
- Moved
@nitrostack/clito base image since it is required in both build and runtime - Optimized base image to reduce its impact on final image size
- Focused on time optimization via caching and layering, not just image size
- Ensured fresh dependency installation inside Docker to avoid cross-platform issues
- Kept project-specific dependencies in main Dockerfile to maintain flexibility and proper caching
-
Base image helps:
- ✅ reduce repeated setup
- ✅ improve consistency
- ❌ limited impact on total build time
-
Major time improvements come from:
- 🚀 Docker layer caching
- 🚀
npm cioptimization - 🚀 BuildKit cache
-
Achieved near O(1) build time for cached builds (~1s)
-
Build becomes slower only when dependencies change (expected behavior)
-
- Frequent base image updates can invalidate cache and slow down builds, so it should be versioned and updated only when needed
- Use versioned tags (e.g.,
20-alpine-v3) for stability and rollback support