How the Docker Build Works in the HowToCook Repository: A Multi-Stage Dockerfile Guide
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.
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, including the .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.
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
weasyprintandfonts-noto-cjkto enable PDF generation and proper Chinese character rendering - Removing the
node_modulesdirectory to reduce image size before Python dependency installation - Executing
mkdocs buildto compile Markdown files fromdishes/,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.
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:
docker build -t how-to-cook .
Run the container locally exposing port 80:
docker run -d -p 80:80 --name how-to-cook how-to-cook
Inspect a specific build stage (useful for debugging):
docker build --target python-env -t how-to-cook:build .
Push to a container registry:
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-envstage 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.jsonandrequirements.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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →