# How the Docker Build Works in the HowToCook Repository: A Multi-Stage Dockerfile Guide

> Explore the multi stage Dockerfile in the HowToCook repo. Learn how it lints Node.js assets, builds an MkDocs site with Python, and serves it using Nginx for a minimal production image.

- Repository: [Anduin Xue/HowToCook](https://github.com/Anduin2017/HowToCook)
- Tags: internals
- Published: 2026-02-27

---

**The HowToCook repository uses a three-stage Dockerfile that lints Node.js assets, builds a static MkDocs site with Python, and serves the final output via Nginx to create a minimal production image.**

The `Anduin2017/HowToCook` project leverages Docker to package its recipe documentation into a portable, production-ready web server. Understanding how the Docker build works reveals an efficient **multi-stage build pattern** that separates development dependencies from the final runtime environment, resulting in a lightweight image containing only the compiled static site and Nginx.

## Understanding the Multi-Stage Docker Build Architecture

The `Dockerfile` located at the repository root implements a multi-stage build strategy. This approach uses separate intermediate images for linting, building, and serving, ensuring the final `nginx:1-alpine` image contains no build tools, Node modules, or Python packages—only the generated HTML.

### Stage 1: Lint and Node.js Build Environment

The first stage, named `lint-env`, uses `node:22-alpine` to validate and compile JavaScript-based documentation generators.

```dockerfile
FROM node:22-alpine AS lint-env
WORKDIR /app
COPY . .
RUN npm install --loglevel verbose
RUN npm run build
RUN npm run lint

```

This stage executes the build pipeline defined in [`package.json`](https://github.com/Anduin2017/HowToCook/blob/main/package.json), including the [`.github/readme-generate.js`](https://github.com/Anduin2017/HowToCook/blob/main/.github/readme-generate.js) script that processes recipe Markdown files. By running `npm run lint` here, the Docker build fails early if documentation syntax errors exist, preventing broken builds from reaching production.

### Stage 2: Python Environment and Static Site Generation

The second stage, `python-env`, inherits the validated files from `lint-env` and uses `python:3.11` to generate the final static site with MkDocs.

```dockerfile
FROM python:3.11 AS python-env
COPY --from=lint-env /app .
RUN apt-get update && apt-get install -y weasyprint fonts-noto-cjk wget unzip
RUN rm node_modules -rf && pip install -r requirements.txt
RUN mkdocs build

```

Key operations in this stage include:
- Installing system dependencies `weasyprint` and `fonts-noto-cjk` to enable PDF generation and proper Chinese character rendering
- Removing the `node_modules` directory to reduce image size before Python dependency installation
- Executing `mkdocs build` to compile Markdown files from `dishes/`, `tips/`, and other directories into a static HTML site located at `/app/site`

### Stage 3: Nginx Runtime Environment

The final stage produces the production image using `nginx:1-alpine`, copying only the compiled static assets from the previous stage.

```dockerfile
FROM nginx:1-alpine AS runtime
COPY --from=python-env /app/site /usr/share/nginx/html
LABEL org.opencontainers.image.source="https://github.com/Anduin2017/HowToCook"

```

This results in an image size of approximately 15 MB containing only Nginx and the static HTML files, with no trace of Node.js, Python, or build dependencies.

## Building and Running the Docker Image Locally

To replicate the Docker build process on your machine, execute the following commands from the repository root:

Build the complete image:

```bash
docker build -t how-to-cook .

```

Run the container locally exposing port 80:

```bash
docker run -d -p 80:80 --name how-to-cook how-to-cook

```

Inspect a specific build stage (useful for debugging):

```bash
docker build --target python-env -t how-to-cook:build .

```

Push to a container registry:

```bash
docker tag how-to-cook your-registry/how-to-cook:latest
docker push your-registry/how-to-cook:latest

```

## Summary

The HowToCook repository implements a sophisticated multi-stage Docker build that optimizes for both developer experience and production efficiency:

- **Three distinct stages** separate linting (Node.js), building (Python/MkDocs), and serving (Nginx) concerns
- **Early failure detection** via the `lint-env` stage prevents broken documentation from reaching production images
- **Minimal final image** contains only Nginx and static HTML, eliminating all build-time dependencies
- **Reproducible builds** are ensured through explicit dependency management in [`package.json`](https://github.com/Anduin2017/HowToCook/blob/main/package.json) and [`requirements.txt`](https://github.com/Anduin2017/HowToCook/blob/main/requirements.txt)

## Frequently Asked Questions

### How does the multi-stage build reduce the final image size?

The multi-stage build reduces the final image size by copying only the compiled static site from the `python-env` stage into the `nginx:1-alpine` runtime stage. This excludes Node.js, Python, MkDocs, WeasyPrint, and all build dependencies, resulting in a production image of approximately 15 MB instead of several hundred megabytes.

### What happens if the linting stage fails?

If the `lint-env` stage fails—typically due to syntax errors in Markdown files or issues in the [`.github/readme-generate.js`](https://github.com/Anduin2017/HowToCook/blob/main/.github/readme-generate.js) script—the Docker build stops immediately before reaching the `python-env` or `runtime` stages. This early failure mechanism ensures that only validated, error-free documentation gets built and deployed.

### Why does the Python stage install WeasyPrint and CJK fonts?

The Python stage installs `weasyprint` and `fonts-noto-cjk` to support PDF generation capabilities and proper rendering of Chinese, Japanese, and Korean characters in the recipe documentation. These system dependencies are required by the MkDocs build process to generate downloadable PDF versions of the cooking guides with correct typography for CJK content.

### Can I build only specific stages for debugging?

Yes, you can build specific stages using the `--target` flag. For example, running `docker build --target python-env -t how-to-cook:build .` stops the build after the Python stage completes, allowing you to inspect the generated static site in `/app/site` or debug MkDocs configuration issues without completing the full Nginx runtime stage.