<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6" -->

---
title: Multi-Stage Docker Builds vs Fat Images: What Actually...
description: A Node.js app was shipping a 1.8GB production Docker image for nearly a year due to three overlooked issues: no .dockerignore file (causing .git and test files...
canonical: https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Multi-Stage Docker Builds vs Fat Images: What Actually Shrank Our Prod Image | daily.dev
og:description: A Node.js app was shipping a 1.8GB production Docker image for nearly a year due to three overlooked issues: no .dockerignore file (causing .git and test files...
og:url: https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6
og:image: https://api.daily.dev/og/posts/OlUVS06I6.png
og:image:alt: Multi-Stage Docker Builds vs Fat Images: What Actually Shrank Our Prod Image
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Stage Docker Builds vs Fat Images: What Actually Shrank Our Prod Image

**[Medium](https://daily.dev/sources/medium_js)** · 8 min read · 1 upvotes · 0 comments

## Summary

A Node.js app was shipping a 1.8GB production Docker image for nearly a year due to three overlooked issues: no .dockerignore file (causing .git and test files to be included), all devDependencies bundled into the runtime image, and a single-stage build with no separation between build and runtime concerns. Switching to a multi-stage Dockerfile reduced the image to 412MB (77% reduction), cutting deploy times from 4 minutes to under 90 seconds. Key lessons: run `docker history` before guessing at the cause, write .dockerignore first, separate build and runtime stages from the start, use `npm ci` for deterministic installs, and copy package.json before source files to leverage layer caching.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://blog.stackademic.com/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-3a7b8ebfb6e6>

## Questions this post answers

### How much can a multi-stage Docker build reduce a Node.js production image size compared to a single-stage build?

A single-stage Node.js production image weighing 1.8GB was reduced to 412MB using a multi-stage Dockerfile, a 77% reduction. The savings came from excluding devDependencies, source files, and build artifacts from the runtime stage, and from adding a missing .dockerignore that had let the .git folder and node_modules leak into the build context.

_Trim your own Node.js image bloat with multi-stage patterns developers share and discuss on daily.dev._

### Why didn't switching to a slimmer Docker base image fix a bloated Node.js production image?

Switching from node:20 to node:20-slim only shaved about 150MB off a 1.8GB image because the base image wasn't the main problem. Running docker history revealed the real weight came from npm install pulling in devDependencies, a full node_modules copy before the build step, and a missing .dockerignore letting the entire .git folder into the image.

_daily.dev surfaces practical Docker debugging tactics for engineers chasing down image bloat._

### What should a .dockerignore file exclude to prevent Docker build bloat in a Node.js project?

A .dockerignore should exclude .git, node_modules, dist, markdown files, .env, and tests directories. Skipping this file for a year let the entire git history and unnecessary build context reach the Docker build, adding gigabytes of unneeded data to every single deploy of a production image.

_Developers cleaning up Dockerfiles can find configuration checklists like this shared on daily.dev._

## Similar posts on daily.dev

- [First challenge on DevOpsLedger](https://daily.dev/posts/first-challenge-on-devopsledger-7gkfqjh4b) · Medium · 0 upvotes · 1 comments
- [Your Node.js Docker Image Is Probably Over 1GB. Here’s How to Get It Under 210MB](https://daily.dev/posts/your-node-js-docker-image-is-probably-over-1gb-here-s-how-to-get-it-under-210mb-gb1hpsizg) · Medium · 1 upvotes · 2 comments
- [How to Containerize a Node.js Application with Docker and Deploy with GitHub Actions](https://daily.dev/posts/how-to-containerize-a-node-js-application-with-docker-and-deploy-with-github-actions-ndb3pz7zw) · freeCodeCamp · 1 upvotes · 0 comments
- [Why To Use A Multi-Stage Dockerfile](https://daily.dev/posts/why-to-use-a-multi-stage-dockerfile-ripe33vzt) · RUBYLAND · 0 upvotes · 0 comments

---

Tags: [#nodejs](https://daily.dev/tags/nodejs), [#docker](https://daily.dev/tags/docker), [#containers](https://daily.dev/tags/containers)

[View this post on daily.dev](https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Multi-Stage Docker Builds vs Fat Images: What Actually Shrank Our Prod Image","url":"https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6"},"datePublished":"2026-07-17T07:33:42.491Z","dateModified":"2026-09-14T06:11:57.857Z","description":"A Node.js app was shipping a 1.8GB production Docker image for nearly a year due to three overlooked issues: no .dockerignore file (causing .git and test files...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/30f169e8b8204cd01f03afb1c351d18b?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/30f169e8b8204cd01f03afb1c351d18b?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Medium","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Medium","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/medium","url":"https://daily.dev/sources/medium_js"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"nodejs,docker,containers","timeRequired":"PT8M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Medium","item":"https://daily.dev/sources/medium_js"},{"@type":"ListItem","position":3,"name":"Multi-Stage Docker Builds vs Fat Images: What Actually Shrank Our Prod Image"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/multi-stage-docker-builds-vs-fat-images-what-actually-shrank-our-prod-image-oluvs06i6#faq","mainEntity":[{"@type":"Question","name":"How much can a multi-stage Docker build reduce a Node.js production image size compared to a single-stage build?","acceptedAnswer":{"@type":"Answer","text":"A single-stage Node.js production image weighing 1.8GB was reduced to 412MB using a multi-stage Dockerfile, a 77% reduction. The savings came from excluding devDependencies, source files, and build artifacts from the runtime stage, and from adding a missing .dockerignore that had let the .git folder and node_modules leak into the build context. Trim your own Node.js image bloat with multi-stage patterns developers share and discuss on daily.dev."}},{"@type":"Question","name":"Why didn't switching to a slimmer Docker base image fix a bloated Node.js production image?","acceptedAnswer":{"@type":"Answer","text":"Switching from node:20 to node:20-slim only shaved about 150MB off a 1.8GB image because the base image wasn't the main problem. Running docker history revealed the real weight came from npm install pulling in devDependencies, a full node_modules copy before the build step, and a missing .dockerignore letting the entire .git folder into the image. daily.dev surfaces practical Docker debugging tactics for engineers chasing down image bloat."}},{"@type":"Question","name":"What should a .dockerignore file exclude to prevent Docker build bloat in a Node.js project?","acceptedAnswer":{"@type":"Answer","text":"A .dockerignore should exclude .git, node_modules, dist, markdown files, .env, and tests directories. Skipping this file for a year let the entire git history and unnecessary build context reach the Docker build, adding gigabytes of unneeded data to every single deploy of a production image. Developers cleaning up Dockerfiles can find configuration checklists like this shared on daily.dev."}}]}
```

