Every container image starts with docker build. You write a Dockerfile, run one command, and Docker turns your source into a portable image that behaves the same on your laptop, a teammate's machine, and a production server. Knowing how docker build works is the difference between images that rebuild in a few seconds from cache and images that recompile everything on every commit.
This guide follows the full path from Dockerfile instructions to image layers. It covers the anatomy of the build command, the build context, caching, multi-stage builds, and the BuildKit engine behind docker buildx. At the end we look at why production platforms run the build for you, so you get reproducible images without maintaining a builder.
The short answer: docker build reads a Dockerfile and executes its instructions in order, turning each into a cached, read-only layer that stacks into a single container image. You run it as docker build -t name:tag ., where the final dot is the build context. Since Docker 23 the default engine is BuildKit, which parallelizes steps and caches aggressively.
What docker build actually does
A build is a translation. Each instruction in the Dockerfile (FROM, COPY, RUN, ENV, CMD, and the rest) becomes one build step. Most of those steps produce a new filesystem layer stacked on top of the previous one. The finished stack of layers, together with configuration metadata like the default command and exposed ports, is the image.
docker buildis the command that reads a Dockerfile, runs each instruction as a build step, and produces a container image made of stacked read-only layers. The finished image can run as a container locally or push to a registry for others to pull.
Layers are content addressed, which means Docker fingerprints the contents of each one. Two images that share an identical base and identical dependency layers store those bytes only once. This is why a small code change rebuilds only the top of the stack while the heavy base and dependency layers stay untouched. The official Docker build documentation describes each instruction and how it maps to a layer.
The anatomy of the docker build command
The canonical form is short and worth reading slowly:
docker build -t myapp:1.0.0 .Three pieces matter here. docker build invokes the builder. The -t flag tags the resulting image with a name and version (myapp:1.0.0), so you can reference it later instead of a random image ID. The trailing dot is the build context, the directory whose files the builder is allowed to read.
When the build finishes, Docker prints the image ID and stores the image in your local image store. You then run it with docker run myapp:1.0.0 or push it to a registry with docker push. If you are deciding where that image should ultimately live, our guide on hosting a Docker container walks through the options.
The build context and .dockerignore
The build context is the set of files packaged and sent to the builder before any instruction runs. With docker build ... ., the context is the current directory and everything beneath it. Any COPY or ADD instruction can only reference paths inside that context, which is a common source of confusion for newcomers who try to copy a file from a parent folder.
A bloated context slows every build, because the whole directory is scanned and transferred first. A repository that ships node_modules, .git, build output, and local logs into the context wastes seconds on each run. The fix is a .dockerignore file at the project root:
.git
node_modules
dist
*.log
.env
This keeps large and sensitive paths out of the context entirely. Excluding .env also stops secrets from leaking into layers by accident. For a proper way to handle credentials at build time, see how to manage Docker build secrets with BuildKit mounts.
Layer caching and instruction order
Caching is where builds get fast or stay slow. Docker caches each layer and reuses it on the next build as long as the instruction and its inputs are unchanged. The moment one layer changes, that layer and every layer after it are rebuilt. The order of your instructions therefore decides how much of the cache survives an edit.
The rule is to place stable instructions early and volatile ones late. Dependencies change far less often than application code, so copy your manifests and install packages before you copy the source:
COPY package.json package-lock.json ./
RUN npm ci
COPY . .With this order, editing a source file invalidates only the final COPY and the steps below it. The npm ci layer, often the slowest step in the build, stays cached. Reverse the two COPY lines and every code change reinstalls all dependencies from scratch.
Multi-stage builds to shrink images
Compilers, bundlers, and dev dependencies are needed to build an app but not to run it. Shipping them in the final image bloats size, slows pulls, and widens the attack surface. Multi-stage builds solve this cleanly.
A multi-stage build uses several
FROMinstructions in one Dockerfile. Early stages compile or bundle your app, and a final lean stage copies only the built artifacts, so build tools and source never ship in the runtime image.
Here is a realistic multi-stage Dockerfile for a Node service. The first stage installs everything and builds. The second stage starts from a clean base and copies only the production dependencies and the compiled output.
# syntax=docker/dockerfile:1
# Stage 1: install and build
FROM node:20-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: minimal runtime image
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]You build it exactly like a single-stage file:
docker build -t myapp:1.0.0 .The --from=build reference is the key. It reaches back into the first stage and copies just the dist directory, leaving source files, dev dependencies, and the build toolchain behind. The final image contains a production runtime and compiled code, nothing more.
BuildKit, the modern builder
BuildKit is the engine that runs behind docker build on any current Docker install. It replaced the legacy builder and brings real gains in speed and capability.
BuildKit is Docker's modern build engine, the default since Docker 23. It models the build as a graph, runs independent stages in parallel, skips layers no output needs, and handles caching, secrets, and cache mounts far better than the legacy builder.
Because BuildKit understands the dependency graph between stages, two independent stages can build at the same time instead of one after the other. It also mounts secrets into a single RUN step without baking them into a layer, and it supports cache mounts that persist a package cache across builds. The first line of the Dockerfile above, # syntax=docker/dockerfile:1, opts into the current frontend so these features are available. The BuildKit documentation covers the full feature set.
docker buildx and multi-platform builds
docker buildx is the extended build command that drives BuildKit directly. Its headline use is building one image that runs on more than one CPU architecture, most often amd64 for typical servers and arm64 for Apple Silicon laptops and Arm instances.
A single command produces both variants and pushes a combined image to a registry:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registry.example.com/myapp:1.0.0 \
--push .Under the hood, BuildKit builds each platform, using emulation when your machine is not native to a target architecture, and publishes a manifest list. Consumers pulling that tag automatically receive the image for their own architecture. The guide on multi-platform builds explains builder setup and the emulation caveats in detail.
Common docker build flags
A handful of flags cover most day-to-day builds:
-t name:tagsets the image name and tag. Repeat it to apply several tags at once.-f path/to/Dockerfilepoints at a Dockerfile that is not namedDockerfileor lives outside the context root.--build-arg KEY=valuepasses a value into anARGdeclared in the Dockerfile, useful for version pins or non-secret configuration.--target stagenamestops the build at a named stage, handy for building just thebuildstage in CI to run tests.--no-cacheforces a full rebuild that ignores every cached layer.--pullalways fetches the newest base image instead of reusing a local copy.--platform linux/arm64sets the target architecture for a single-platform build.
Why you rarely run docker build by hand in production
Running docker build on your own machine is how you learn and how you debug. Running it by hand for every production release is where teams get into trouble. Local builds depend on your Docker version, your cache state, and your architecture, so the image that ships can differ from the one a colleague built. Maintaining a shared, warm, multi-platform builder is real work.
A PaaS removes that work. You push code to a repository, and the platform builds the image for you on every push, with build caching so repeat builds stay fast. There is no builder to install and no docker build to run in your release pipeline. If you want the background on this model, our explainer on what a PaaS is covers the tradeoffs.
Out Plane follows exactly this pattern. It builds your image from the Dockerfile in your repository, and when no Dockerfile is present it auto-detects a buildpack and builds from plain source. The build runs on the platform's managed builder with caching, HTTPS is issued automatically, and a managed PostgreSQL database can sit alongside your app. For services PostgreSQL does not cover, such as Redis or MongoDB, you self-host them as containers with a persistent volume.
| Aspect | docker build by hand | Build on Out Plane |
|---|---|---|
| Who runs the build | You, on a laptop or CI runner | The platform, on every push |
| Builder engine | BuildKit you install and update | Managed builder, kept current for you |
| Cache | Local, warm only on that machine | Remote cache shared across builds |
| Multi-platform | You configure buildx and emulation | Builds for the platform's runtime architecture |
| Registry push | Manual docker push step | Automatic after a successful build |
| Reproducibility | Varies by machine and cache state | Consistent for the whole team |
| Maintenance | Ongoing builder upkeep | None |
Frequently Asked Questions
What is the difference between docker build and dockerfile build?
There is no separate dockerfile build command. Docker builds images with docker build, which reads a file named Dockerfile by default. People search for dockerfile build meaning the same thing, how to turn a Dockerfile into an image. Point at another file with docker build -f path/to/Dockerfile ..
What does the dot at the end of docker build mean?
The dot sets the build context, the set of files sent to the builder. docker build -t app . uses the current directory as context, so any COPY or ADD instruction can only reference files inside it. Use a .dockerignore file to keep large or secret paths out of the context.
How do I speed up docker build with caching?
Order instructions from least to most frequently changed. Copy dependency manifests like package.json and install packages before copying source code, so a code edit does not invalidate the install layer. BuildKit adds cache mounts and inline remote cache. Avoid --no-cache unless you need a clean rebuild.
What is docker buildx used for?
docker buildx is the extended build CLI that drives BuildKit directly. Its main use is multi-platform builds, producing one image that runs on both amd64 and arm64 from a single docker buildx build --platform linux/amd64,linux/arm64 command. It also manages named builders and advanced cache backends.
Do I need docker build to deploy to a PaaS?
No. A PaaS reads the Dockerfile in your repository and runs the build for you on every push, with managed build caching. You never install a builder or run docker build yourself. Out Plane also auto-detects a buildpack when no Dockerfile is present, so plain source deploys too.
When should I use a multi-stage Dockerfile?
Use multi-stage builds whenever your app needs compilers, bundlers, or dev dependencies at build time but not at run time. A build stage installs everything and produces artifacts. A slim final stage copies only those artifacts, cutting image size, attack surface, and pull time. Node, Go, and Java apps benefit most.
Is BuildKit enabled by default?
Yes. Since Docker Engine 23.0, BuildKit is the default builder, so a plain docker build already uses it. Older versions needed DOCKER_BUILDKIT=1 in the environment. BuildKit powers parallel stages, better caching, build secrets, and the # syntax=docker/dockerfile:1 frontend that enables newer Dockerfile features.
Conclusion
docker build is a small command with a lot of depth. It reads a Dockerfile top to bottom, turns each instruction into a cached layer, and stacks those layers into an image. Order your instructions for cache hits, split build and runtime work into stages, and lean on BuildKit and docker buildx when you need speed or multiple architectures. Those habits alone will cut most build times and image sizes.
In production, the honest move is to stop running the build by hand. Push your Dockerfile and let the platform build it reproducibly with managed caching, so every release is consistent. Create an app in the console at https://console.outplane.com and see the free Hobby tier and pay-as-you-go Pro details on /pricing.