ЁЯПл The SchoolтА║ЁЯН▒ DockerтА║ЁЯСитАНЁЯН│ рдзрдбрд╛ 07 тАФ Multi-stage builds: рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░рд╛рдд рд╢рд┐рдЬрд╡рд╛, рдлрдХреНрдд рдЕрдиреНрди рдкреЕрдХ рдХрд░рд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯСитАНЁЯН│ рдзрдбрд╛ 07 тАФ Multi-stage builds: рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░рд╛рдд рд╢рд┐рдЬрд╡рд╛, рдлрдХреНрдд рдЕрдиреНрди рдкреЕрдХ рдХрд░рд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 07 ┬╖ рдорд╛рдЧреЗ: lesson-06-docker-compose ┬╖ рдкреБрдвреЗ: lesson-08-image-hygiene


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ06, рдЖрдгрд┐ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп: рд▓рд╣рд╛рди, рд╕реБрд░рдХреНрд╖рд┐рдд images рд╕рд╛рдареАрдЪреА рдПрдХрдореЗрд╡ рд╕рд░реНрд╡реЛрддреНрддрдо рдпреБрдХреНрддреА тАФ multi-stage builds. рдЦрд▒реНрдпрд╛ files:

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдЬреЗрд╡рдг рдмрдирд╡рд╛рдпрд▓рд╛ рдореЛрдареЗ, рдкрд╕рд╛рд░рд╛ рдЕрд╕рд▓реЗрд▓реЗ рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░ ЁЯСитАНЁЯН│ рд▓рд╛рдЧрддреЗ: рдУрд╡реНрд╣рди, рдорд┐рдХреНрд╕рд░, рд╕рдЧрд│реАрдХрдбреЗ рдкреАрда, recipe рдЪреЗ рдХрдЪреНрдЪреЗ рдорд╕реБрджреЗ, рдЦрд░рдХрдЯреЗ рдЪрдордЪреЗ.

рдкреНрд░рд╢реНрди: рдбрдмрд╛ рднрд░рддрд╛рдирд╛тАж рддреБрдореНрд╣реА рдУрд╡реНрд╣рди рдбрдмреНрдпрд╛рдд рдареЗрд╡рддрд╛ рдХрд╛? ЁЯди

рдЕрд░реНрдерд╛рддрдЪ рдирд╛рд╣реА! рддрд░реАрд╣реА рд╕рд╛рдзреЗрднреЛрд│реЗ Dockerfiles рдиреЗрдордХреЗ рд╣реЗрдЪ рдХрд░рддрд╛рдд: compilers, package managers, source code, build caches тАФ рд╕рдЧрд│реЗ image рдЪреНрдпрд╛ рдЖрдд production рд▓рд╛ рдкрд╛рдард╡рд▓реЗ рдЬрд╛рддреЗ. рдЧреНрд░рд╛рд╣рдХрд╛рдиреЗ рдЬреЗрд╡рдг рдорд╛рдЧрд╡рд▓реЗ; рддреБрдореНрд╣реА рддреНрдпрд╛рд▓рд╛ рдЖрдкрд▓реЗ рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░рдЪ рдкрд╛рдард╡рд▓реЗ.

Multi-stage рдПрдХрд╛рдЪ recipe рдордзреНрдпреЗ рджреЛрди рдЦреЛрд▓реНрдпрд╛ рджреЗрдКрди рд╣реЗ рд╕реБрдзрд╛рд░рддреЗ:

  1. Stage 1 тАФ рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░ (FROM node:20-alpine AS builder): рд╕рдЧрд│реА tools рдЪрд╛рд▓рддрд╛рдд, рд╣рд╡рд╛ рддреЗрд╡рдврд╛ рдкрд╕рд╛рд░рд╛ рдХрд░рд╛, рдкрджрд╛рд░реНрде (index.html) рддрдпрд╛рд░ рдХрд░рд╛.
  2. Stage 2 тАФ рдбрдмрд╛ (FROM nginx:alpine): рд╕реНрд╡рдЪреНрдЫ рдЫреЛрдЯреНрдпрд╛ рдбрдмреНрдпрд╛рдкрд╛рд╕реВрди рд╕реБрд░реБрд╡рд╛рдд рдХрд░рд╛ рдЖрдгрд┐ рдкрд▓реАрдХрдбреЗ рдлрдХреНрдд рдкрджрд╛рд░реНрде рдШреНрдпрд╛: COPY --from=builder тАж.

рднрд╛рдЬреВрди рдЭрд╛рд▓реНрдпрд╛рд╡рд░ рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░ рдлреЗрдХреВрди рджрд┐рд▓реЗ рдЬрд╛рддреЗ. рдлрдХреНрдд stage 2 рдкрд╛рдард╡рд▓реА рдЬрд╛рддреЗ: рдЗрдереЗ ~180MB рдРрд╡рдЬреА ~50MB тАФ рдЖрдгрд┐ рдЦрд▒реНрдпрд╛ ре▓рдкреНрд╕рдордзреНрдпреЗ 1.2GB тЖТ 80MB рд╣реЗ рдиреЗрд╣рдореАрдЪреЗрдЪ рдЖрд╣реЗ. ЁЯдп

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    subgraph s1["ЁЯСитАНЁЯН│ stage 1: KITCHEN - thrown away ЁЯЧСя╕П"]
        tools["node + tools + source<br/>~180MB of mess"]
        dish["ЁЯН▓ the dish:<br/>index.html"]
        tools -->|"1 RUN node generate.js"| dish
    end
    subgraph s2["ЁЯН▒ stage 2: LUNCHBOX - this ships ЁЯЪА"]
        clean["nginx:alpine ~50MB<br/>+ index.html<br/>nothing else"]
    end
    dish -->|"2 COPY --from=builder<br/>only the food crosses"| clean

тЭУ рдХрд╛рдп

ЁЯза рджреЛрди stages, рдПрдХ artifact

BUILD STAGE     Node + npm + source + compiler
                          тЖУ  build
                     build artifact
                          тЖУ  COPY --from=build
RUNTIME STAGE   minimal runtime + artifact       тЖР THIS ships

Build dependencies build stage рдордзреНрдпреЗрдЪ рд░рд╛рд╣рддрд╛рдд рдЖрдгрд┐ рдЕрдВрддрд┐рдо image рдордзреНрдпреЗ рдЕрд╕рдгреНрдпрд╛рдЪреА рдЧрд░рдЬрдЪ рдирд╕рддреЗ. Production рдордзрд▓реА рдкрд░рд┐рдгрд╛рдорд╛рдВрдЪреА рд╕рд╛рдЦрд│реА:

рд▓рд╣рд╛рди image тЖТ рдЬрд▓рдж pull тЖТ рд╣рд▓реНрд▓реНрдпрд╛рдЪреА рдЬрд╛рдЧрд╛ рдХрдореА тЖТ рдХрдореА storage рдЖрдгрд┐ transfer

ЁЯдФ рдХрд╛

Image рдЪрд╛ рдЖрдХрд╛рд░ рд╣рд╛ рддреБрдореНрд╣реА рдкреНрд░рддреНрдпреЗрдХ push (рдзрдбрд╛ 11), рдкреНрд░рддреНрдпреЗрдХ pull, рдкреНрд░рддреНрдпреЗрдХ pod start, k8s рдХреЛрд░реНрд╕рдордзрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ autoscale-up рд╡рд░ рднрд░рддрд╛ рддреЛ рдХрд░ рдЖрд╣реЗ. Multi-stage рдПрдХрджрд╛рдЪреНрдпрд╛ рдореЗрд╣рдирддреАрдд рд╣рд╛ рдХрд░ 10тАУ20├Ч рдиреЗ рдХрдореА рдХрд░рддреЗ. "рдЖрдордЪреНрдпрд╛ production image рдордзреНрдпреЗ compiler рдЖрд╣реЗ" рд╣рд╛ security рдордзрд▓рд╛ рдореБрджреНрджрд╛рд╣реА рд╕рдВрдкрддреЛ тАФ build tools рддрд┐рдереЗ рдирд╕рддрд╛рддрдЪ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

app/Dockerfile.multi: рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░ generate.js (рдЖрдкрд▓реНрдпрд╛ npm run build рдРрд╡рдЬреАрдЪреЗ) рдЪрд╛рд▓рд╡реВрди index.html рддрдпрд╛рд░ рдХрд░рддреЗ; рдбрдмрд╛ рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд nginx + рддреА рдПрдХ file. Node рдЕрдВрддрд┐рдо image рдкрд░реНрдпрдВрдд рдХрдзреАрдЪ рдкреЛрд╣реЛрдЪрдд рдирд╛рд╣реА тАФ рдЦрд╛рд▓реА рд╕рд┐рджреНрдз рдХрд░рд╛.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

docker build -f app/Dockerfile.multi -t hello-school:static app/
docker run --rm -p 8080:80 hello-school:static
# open http://localhost:8080 тАФ "built inside the BUILDER stage"

# the receipts ЁЯз╛:
docker image ls | grep -E "hello-school|node|nginx"   # compare sizes
docker run --rm hello-school:static node --version 2>&1 | tail -1
# тЖТ "node: not found" тАФ the kitchen truly did not ship ЁЯСитАНЁЯН│ЁЯЪл

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

тПня╕П рдкреБрдвреЗ

рд▓рд╣рд╛рди тЬЕ. рдЖрддрд╛ рддреА рд╕реНрд╡рдЪреНрдЫ рдХрд░реВрдпрд╛: tags, .dockerignore, non-root тАФ рд╣реМрд╢реА images рдЖрдгрд┐ production images рд╡реЗрдЧрд│реНрдпрд╛ рдХрд░рдгрд╛рд░реА hygiene рдЪреА рдпрд╛рджреА.

git checkout lesson-08-image-hygiene

ЁЯСитАНЁЯН│ Lesson 07 тАФ Multi-stage builds: cook in the kitchen, pack only the food

ЁЯУН You are here: Lesson 07 of 12 ┬╖ Previous: lesson-06-docker-compose ┬╖ Next: lesson-08-image-hygiene


ЁЯУж What's in this branch

Lessons 01тАУ06, plus: the single best trick for small, safe images тАФ multi-stage builds. Real files:

ЁЯзТ Explain like I'm 5

To make lunch you need a big messy kitchen ЁЯСитАНЁЯН│: oven, mixer, flour everywhere, recipe drafts, dirty spoons.

Question: when you pack the lunchboxтАж do you put the oven in the box? ЁЯди

Of course not! Yet that's exactly what naive Dockerfiles do: compilers, package managers, source code, build caches тАФ all shipped to production inside the image. The customer ordered lunch; you mailed them your kitchen.

Multi-stage fixes it with two rooms in one recipe:

  1. Stage 1 тАФ the KITCHEN (FROM node:20-alpine AS builder): all tools allowed, make as much mess as needed, produce the dish (index.html).
  2. Stage 2 тАФ the LUNCHBOX (FROM nginx:alpine): start from a clean tiny box and take only the dish across: COPY --from=builder тАж.

The kitchen is thrown away after baking. Only stage 2 ships: ~50MB instead of ~180MB here тАФ and in real apps, 1.2GB тЖТ 80MB is routine. ЁЯдп

ЁЯЧ║я╕П Diagram

flowchart LR
    subgraph s1["ЁЯСитАНЁЯН│ stage 1: KITCHEN - thrown away ЁЯЧСя╕П"]
        tools["node + tools + source<br/>~180MB of mess"]
        dish["ЁЯН▓ the dish:<br/>index.html"]
        tools -->|"1 RUN node generate.js"| dish
    end
    subgraph s2["ЁЯН▒ stage 2: LUNCHBOX - this ships ЁЯЪА"]
        clean["nginx:alpine ~50MB<br/>+ index.html<br/>nothing else"]
    end
    dish -->|"2 COPY --from=builder<br/>only the food crosses"| clean

тЭУ What

ЁЯза Two stages, one artifact

BUILD STAGE     Node + npm + source + compiler
                          тЖУ  build
                     build artifact
                          тЖУ  COPY --from=build
RUNTIME STAGE   minimal runtime + artifact       тЖР THIS ships

Build dependencies stay in the build stage and never need to exist in the final image. The production chain of consequences:

smaller image тЖТ faster pull тЖТ smaller attack surface тЖТ less storage & transfer

ЁЯдФ Why

Image size is a tax you pay on every push (lesson 11), every pull, every pod start, every autoscale-up in the k8s course. Multi-stage cuts the tax by 10тАУ20├Ч for one-time effort. It also ends the "our production image contains the compiler" security finding тАФ build tools simply aren't there.

ЁЯФз How (in this repo)

app/Dockerfile.multi: the kitchen runs generate.js (our stand-in for npm run build) to produce index.html; the lunchbox is pure nginx + that one file. Node never reaches the final image тАФ prove it below.

ЁЯзк Try it

docker build -f app/Dockerfile.multi -t hello-school:static app/
docker run --rm -p 8080:80 hello-school:static
# open http://localhost:8080 тАФ "built inside the BUILDER stage"

# the receipts ЁЯз╛:
docker image ls | grep -E "hello-school|node|nginx"   # compare sizes
docker run --rm hello-school:static node --version 2>&1 | tail -1
# тЖТ "node: not found" тАФ the kitchen truly did not ship ЁЯСитАНЁЯН│ЁЯЪл

тЪая╕П Common mistakes

тПня╕П Next

Small тЬЕ. Now let's make it clean: tags, .dockerignore, non-root тАФ the hygiene checklist that separates hobby images from production ones.

git checkout lesson-08-image-hygiene
тЖР Previousdocker composeNext тЖТimage hygiene

This page is the lesson's README from the lesson-07-multi-stage branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.