0
0
Nginxdevops~15 mins

Multi-stage builds for static sites in Nginx - Deep Dive

Choose your learning style9 modes available
Overview - Multi-stage builds for static sites
What is it?
Multi-stage builds are a way to create Docker images in steps, where each step can use a different environment. For static sites, this means you can build your site files in one step and then copy only the final files into a small, fast web server image like nginx. This keeps the final image small and secure. It helps beginners understand how to separate building from serving in one smooth process.
Why it matters
Without multi-stage builds, Docker images for static sites often include unnecessary tools and files, making them large and slower to start. This wastes storage and bandwidth, and can expose security risks. Multi-stage builds solve this by keeping only what is needed to serve the site, improving performance and safety. This matters because websites load faster and use fewer resources, which users and servers both benefit from.
Where it fits
Before learning multi-stage builds, you should understand basic Docker concepts like images, containers, and Dockerfiles. After mastering this, you can explore advanced Docker optimizations, continuous integration pipelines, and deploying static sites to cloud platforms.
Mental Model
Core Idea
Multi-stage builds let you build your static site in one environment and then copy only the final output into a clean, minimal web server image to serve it efficiently.
Think of it like...
It's like cooking a meal in a big kitchen with all your tools, then packing only the finished dish into a small lunchbox to take with you, leaving the messy kitchen behind.
┌───────────────┐       ┌─────────────────────┐
│ Build Stage   │──────▶│ Final Stage (nginx)  │
│ (Node, tools) │       │ (only static files)  │
└───────────────┘       └─────────────────────┘

Step 1: Build site files with all tools
Step 2: Copy only final files to nginx image
Build-Up - 7 Steps
1
FoundationUnderstanding static sites basics
🤔
Concept: Learn what static sites are and how they are served.
Static sites are websites made of fixed files like HTML, CSS, and images. They don't change on the server and are served directly by web servers like nginx. You can open these files in a browser without needing a backend server.
Result
You know that static sites are simple files served as-is, which makes them fast and easy to host.
Understanding static sites helps you see why separating build and serve steps is useful.
2
FoundationBasics of Docker images and containers
🤔
Concept: Learn how Docker images are built and run as containers.
Docker images are like blueprints for containers. A Dockerfile describes how to build an image step by step. When you run an image, it becomes a container, which is a running instance of your app or site.
Result
You can create and run containers that serve your static site.
Knowing Docker basics is essential before using multi-stage builds.
3
IntermediateSingle-stage Dockerfile for static sites
🤔
Concept: Build a Docker image that includes both build tools and the web server in one stage.
Example Dockerfile: FROM node:18 WORKDIR /app COPY . . RUN npm install && npm run build RUN apt-get update && apt-get install -y nginx COPY ./dist /usr/share/nginx/html CMD ["nginx", "-g", "daemon off;"] This installs Node.js, builds the site, installs nginx, and serves the site all in one image.
Result
The image contains everything but is large and includes unnecessary build tools at runtime.
Combining build and serve in one image works but wastes space and can cause security issues.
4
IntermediateIntroducing multi-stage build syntax
🤔Before reading on: do you think multi-stage builds copy files automatically between stages or require explicit commands? Commit to your answer.
Concept: Learn how to define multiple stages in a Dockerfile and copy files between them.
Multi-stage builds use multiple FROM lines. Each stage can have a name. You explicitly copy files from one stage to another using COPY --from=stage_name. Example: FROM node:18 AS builder WORKDIR /app COPY . . RUN npm install && npm run build FROM nginx:1.25 COPY --from=builder /app/dist /usr/share/nginx/html CMD ["nginx", "-g", "daemon off;"]
Result
The final image only contains nginx and the built static files, making it smaller and cleaner.
Explicit copying between stages gives precise control over what goes into the final image.
5
AdvancedOptimizing multi-stage builds for caching
🤔Before reading on: do you think placing COPY commands before RUN commands helps or hurts Docker build caching? Commit to your answer.
Concept: Learn how to order Dockerfile commands to maximize build cache reuse and speed up rebuilds.
Place COPY commands that change less frequently before those that change often. For example, copy package.json and package-lock.json first, run npm install, then copy source files and build. This way, npm install is cached unless dependencies change. Example: FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM nginx:1.25 COPY --from=builder /app/dist /usr/share/nginx/html CMD ["nginx", "-g", "daemon off;"]
Result
Docker rebuilds are faster because it reuses cached layers when dependencies don't change.
Ordering commands to leverage caching saves time and resources during development.
6
AdvancedSecuring final images with minimal layers
🤔Before reading on: do you think the final image should include build tools like Node.js? Commit to your answer.
Concept: Learn why the final image should exclude build tools and how multi-stage builds help achieve this.
Build tools like Node.js are only needed during build. Including them in the final image increases size and attack surface. Multi-stage builds let you keep the final image minimal by copying only the built static files into a clean nginx image. This reduces vulnerabilities and improves performance.
Result
The final image is small, secure, and fast to deploy.
Separating build and runtime environments improves security and efficiency.
7
ExpertHandling environment variables and secrets safely
🤔Before reading on: do you think environment variables used during build are available in the final image by default? Commit to your answer.
Concept: Understand how multi-stage builds isolate build-time secrets and environment variables from the final image.
Environment variables set during the build stage are not automatically passed to the final stage unless explicitly done. This prevents leaking secrets like API keys into the runtime image. Use ARG for build-time variables and ENV for runtime variables carefully. Example: FROM node:18 AS builder ARG API_KEY RUN echo $API_KEY > secret.txt FROM nginx:1.25 COPY --from=builder /app/dist /usr/share/nginx/html The secret.txt file won't be in the final image unless copied explicitly.
Result
Secrets remain only in the build stage and do not leak into the final image.
Knowing how build-time and runtime environments differ prevents accidental secret exposure.
Under the Hood
Docker builds images layer by layer. Multi-stage builds create multiple intermediate images (stages) in one Dockerfile. Each stage starts fresh from a base image. Files can be copied from one stage to another explicitly. Only the final stage becomes the actual image used to run containers. This means build tools and temporary files in earlier stages do not appear in the final image, reducing size and attack surface.
Why designed this way?
Multi-stage builds were introduced to solve the problem of bloated images containing unnecessary build dependencies. Previously, developers had to create separate Dockerfiles or manually clean images. Multi-stage builds simplify this by allowing multiple build steps in one file, improving maintainability and efficiency.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Stage 1:     │       │ Stage 2:     │       │ Final Image  │
│ Build tools  │──────▶│ Copy files   │──────▶│ Minimal nginx│
│ & build app  │       │ from Stage 1 │       │ + static files│
└───────────────┘       └───────────────┘       └───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does the final Docker image automatically include all files from previous build stages? Commit yes or no.
Common Belief:The final image contains everything from all build stages automatically.
Tap to reveal reality
Reality:Only files explicitly copied into the final stage are included. Previous stages are discarded after the build.
Why it matters:Assuming all files are included can lead to bloated images and security risks by unintentionally including build tools or secrets.
Quick: Can multi-stage builds reduce image size even if you don't copy files between stages? Commit yes or no.
Common Belief:Multi-stage builds always reduce image size regardless of how you use them.
Tap to reveal reality
Reality:If you don't copy only needed files into the final stage, the image size won't improve. Proper copying is essential.
Why it matters:Misusing multi-stage builds can give a false sense of optimization while images remain large.
Quick: Are environment variables set during build available in the running container by default? Commit yes or no.
Common Belief:Build-time environment variables automatically become runtime environment variables.
Tap to reveal reality
Reality:Build-time variables (ARG) are not passed to the runtime container unless explicitly set as ENV in the final stage.
Why it matters:Misunderstanding this can cause runtime errors or accidental secret exposure.
Quick: Does using multi-stage builds eliminate the need to understand Docker layers and caching? Commit yes or no.
Common Belief:Multi-stage builds handle caching automatically, so you don't need to think about Docker layers.
Tap to reveal reality
Reality:You still need to order commands carefully to maximize cache efficiency and speed up builds.
Why it matters:Ignoring caching leads to slow builds and wasted resources.
Expert Zone
1
Multi-stage builds can use different base images optimized for each stage, like alpine for final images and full node images for building, balancing size and functionality.
2
Copying only necessary files with precise paths avoids accidentally including hidden files or build artifacts that increase image size.
3
Using build arguments (ARG) and environment variables (ENV) correctly prevents leaking secrets and ensures proper configuration separation between build and runtime.
When NOT to use
Multi-stage builds are less useful for dynamic sites that require runtime compilation or server-side logic. In those cases, use runtime environments with proper build pipelines or container orchestration. Also, for very simple static sites without build steps, a single-stage image with just nginx may suffice.
Production Patterns
In production, multi-stage builds are combined with CI/CD pipelines to automate building and deploying static sites. Teams use caching strategies and version tagging to speed up builds. Security scanning tools check final images for vulnerabilities, ensuring only minimal, safe images are deployed.
Connections
Continuous Integration/Continuous Deployment (CI/CD)
Multi-stage builds are often integrated into CI/CD pipelines to automate building and deploying static sites efficiently.
Understanding multi-stage builds helps optimize CI/CD workflows by producing smaller, faster images that deploy quickly.
Software Build Systems
Multi-stage builds mirror the concept of separating build and runtime environments found in traditional build systems like Make or Gradle.
Recognizing this separation clarifies why multi-stage builds improve image cleanliness and security.
Supply Chain Security
Multi-stage builds reduce the attack surface by excluding build tools and secrets from final images, enhancing supply chain security.
Knowing this connection highlights the security benefits beyond just image size optimization.
Common Pitfalls
#1Including build tools in the final image unnecessarily.
Wrong approach:FROM node:18 WORKDIR /app COPY . . RUN npm install && npm run build RUN apt-get update && apt-get install -y nginx COPY ./dist /usr/share/nginx/html CMD ["nginx", "-g", "daemon off;"]
Correct approach:FROM node:18 AS builder WORKDIR /app COPY . . RUN npm install && npm run build FROM nginx:1.25 COPY --from=builder /app/dist /usr/share/nginx/html CMD ["nginx", "-g", "daemon off;"]
Root cause:Not separating build and runtime stages causes large, insecure images.
#2Copying entire source directory instead of only build output.
Wrong approach:COPY --from=builder /app /usr/share/nginx/html
Correct approach:COPY --from=builder /app/dist /usr/share/nginx/html
Root cause:Copying everything includes unnecessary files, increasing image size and potential leaks.
#3Setting build-time secrets as environment variables in the final image.
Wrong approach:FROM node:18 AS builder ARG SECRET_KEY ENV SECRET_KEY=$SECRET_KEY RUN build-command FROM nginx COPY --from=builder /app/dist /usr/share/nginx/html
Correct approach:FROM node:18 AS builder ARG SECRET_KEY RUN build-command using $SECRET_KEY FROM nginx COPY --from=builder /app/dist /usr/share/nginx/html
Root cause:Misusing ENV causes secrets to be baked into the final image, risking exposure.
Key Takeaways
Multi-stage builds separate building your static site from serving it, producing smaller and safer Docker images.
Explicitly copying only the final static files into a minimal nginx image keeps your deployment efficient and secure.
Ordering Dockerfile commands to leverage caching speeds up rebuilds and saves resources during development.
Build-time environment variables and secrets do not automatically appear in the final image, preventing accidental leaks.
Understanding multi-stage builds improves your ability to create professional, production-ready container images for static sites.