Ever needed to combine multiple Docker images? Public images from the Docker Hub are mostly
good at exactly one thing. But often your application consists of multiple technologies. You have a Java backend, but also
need NodeJS to transpile and bundle your TypeScript frontend? If you use a full CI/CD environment, like on GitLab or GitHub,
you can run multiple tasks and assembly the artifacts into a final image. But that is a big step up from the simplicity of a
single Dockerfile
.
git push
deployment anymore, with the convenient GitOps hosters, like Fly.io, Heroku, or Dokku, which work
from a single Dockerfile directly from a git repositoryThis is where Docker Multi-Stage builds really shine.
# Run frontend build in a temporary node image: FROM node:22 AS builder COPY . /tmp/app WORKDIR /tmp/app/ui RUN npm ci RUN npm run build # Create the actual application image: FROM python:3.11 COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv WORKDIR /usr/src/app COPY . . # Install dependencies RUN uv sync # Fetch the frontend build artifacts from the builder image stage COPY --from=builder /tmp/app/static ./static EXPOSE 5000 CMD [ "uv", "run", "gunicorn", "server:app", "--bind", "0.0.0.0:5000"]
This build produces 1 single docker image, but during the build, it relies on 3 different images:
node
image is used to create a throw-away container in which we run the frontend builduv
package manager
(this is much more convenient than some wget
and tar -xzvf
combination)python
image as the base for our applicationThe core elements are multiple FROM
statements to introduce multiple stages, and COPY --from
to
access results from these other stages.
By using Docker Multi-Stage builds, you can keep your setup simple while leveraging the full power of multiple environments in a single image.