Chapter 4 of 16 · Visual guide

Building images

How you build decides whether a deploy takes ten seconds or ten minutes: control the context and respect the cache.

key idea / Docker itselfcontainer / good pathhost / watch outproblem / blockedneutral

The build context

The dot in docker build . is the context: the folder that is uploaded to the daemon.

The build contextEverything in the build folder is sent to the daemon unless .dockerignore filters it out.Your folderFilterSent to the daemonsrc/Dockerfilepackage.jsonnode_modules/ 1 GB.git/.dockerignoredrops node_modules.git, local buildssrc/Dockerfilepackage.json
A 1 GB node_modules folder would travel to the daemon on every build.

The layer cache

Docker reuses any layer whose inputs did not change.

Layer cacheAfter hello.txt changes, the first two layers are cached and the COPY layer and everything after it rebuild.Build 1FROMbuiltWORKDIRbuiltCOPYbuiltRUNbuiltBuild 2 · hello.txt changedFROMCACHEDWORKDIRCACHEDCOPYfile changedRUNmust re-runCache is a time saver, until it hides a stale apt-get update: that is what --no-cache is for.
The cache breaks at the first changed layer. Everything below it re-runs.
Order your Dockerfile for the cacheCopying package.json and installing dependencies before copying source means a source edit does not reinstall packages.Slow: any edit reinstallsFast: install is cachedCOPY . .RUN npm ci (re-runs on every edit)COPY package*.json ./RUN npm ci (cached until deps change)COPY . . (only this re-runs)Rule of thumb: put what changes least at the top,and what changes most (your source) at the bottom.

Flags worth knowing

Flag or fileEffect
-t name:1.0tag the image so you can find and run it
.dockerignorefilters the context: node_modules, .git, build output
--no-cacheignore the cache, rebuild every layer
-f path/Dockerfileuse a Dockerfile that is not named Dockerfile

Try it yourself

Stay in the greet directory from the previous chapter, or recreate that Dockerfile first.

  1. Step 1 · See the context get sent

    A large unused file is still part of . until you ignore it.

    $ dd if=/dev/zero of=big.bin bs=1m count=20
    $ docker build -t greet:1 .
    # transferring context: 20MB
  2. Step 2 · Ignore it
    $ printf 'big.bin\n' > .dockerignore
    $ docker build -t greet:1 .
    # transferring context: a few kB
  3. Step 3 · Change a source file and watch the cache

    FROM and WORKDIR are reused. COPY and everything after it run again, because hello.txt changed.

    $ printf 'hello again\n' > hello.txt
    $ docker build -t greet:1 .
    [1/4] FROM docker.io/library/alpine:3.20   CACHED
    [2/4] WORKDIR /app                         CACHED
    [3/4] COPY hello.txt .
    [4/4] RUN printf 'built in the image\n' > /app/built.txt
  4. Step 4 · --no-cache rebuilds every layer
    $ docker build --no-cache -t greet:1 .
    [1/4] FROM docker.io/library/alpine:3.20
    [2/4] WORKDIR /app
    [3/4] COPY hello.txt .
    [4/4] RUN printf 'built in the image\n' > /app/built.txt

Recap

Quick check

You edit one source file and the build reinstalls every npm package. What is likely wrong?
Show answer

The Dockerfile copies the whole source before installing dependencies. Copy package*.json, run the install, then copy the source so the install layer stays cached.