跪拜 Guibai
← All articles
Docker · Nginx · NestJS

A Dockerfile Is a Milk-Tea SOP: Build and Ship a Full-Stack Todos App

By 烬羽 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A Dockerfile turns a project into a single reproducible artifact that runs the same way in dev, test, and production, eliminating environment drift. Understanding the build-cache model and multi-stage builds directly cuts image size and CI minutes.

Summary

Dockerfiles work like a chain store's standard operating procedure: FROM selects the base, COPY brings in code, RUN installs dependencies, and CMD starts the service. Every instruction executes in order and caches its layer, so rebuilds are fast. The four-command pipeline—build, login, push, pull—turns that recipe into a portable image that runs identically on any machine.

A real todos full-stack project demonstrates the pattern across three pieces: a React frontend built with Vite and served by nginx, a NestJS backend compiled from TypeScript, and an nginx reverse proxy that makes frontend and backend same-origin to eliminate CORS issues in production. Each piece gets its own Dockerfile, and the frontend uses a multi-stage build to keep the final image lean.

Cross-origin handling splits into two strategies: nginx reverse proxy for production (all traffic on port 80, no browser CORS block) and backend CORS headers for local development. The article closes with a command quick-reference table and a question that bridges into Docker networking—what hostname should proxy_pass use when the backend sits inside a container network behind nginx.

Takeaways
A Dockerfile is a sequential instruction list: FROM picks the base image, COPY/RUN do the work, CMD starts the process.
docker build executes every instruction and caches each layer; only changed layers rebuild, making incremental builds fast.
The publish pipeline is four commands: docker build -t name ., docker login, docker push name, docker pull name.
Multi-stage builds (FROM node AS build … FROM nginx … COPY --from=build) keep production images small by discarding build toolchains.
In production, nginx reverse proxy makes frontend and backend same-origin on port 80, sidestepping CORS entirely.
For local development, enabling CORS on the backend (NestJS: app.enableCors()) is a one-line shortcut.
docker images lists local images; docker run -p host:container name starts a container with port mapping.
Conclusions

The SOP analogy is unusually effective for Dockerfiles because both enforce deterministic, repeatable output by following a fixed sequence—deviation is the bug, not the feature.

Teaching Docker through a concrete full-stack project with separate frontend, backend, and proxy Dockerfiles exposes the real multi-service pattern early, rather than leaving learners stuck on single-container tutorials.

The article deliberately ends with an open question about container networking (localhost vs. service name in proxy_pass), which is the exact friction point that trips people moving from docker run to docker compose.

Concepts & terms
Docker build cache
Each instruction in a Dockerfile produces a layer; Docker reuses unchanged layers on subsequent builds, so only instructions after a change (and their descendants) get rebuilt.
Multi-stage build
A Dockerfile with multiple FROM statements that copies artifacts (e.g., compiled static files) from an earlier build stage into a slimmer final image, discarding compilers and dev dependencies.
Same-origin via reverse proxy
Configuring nginx to serve both frontend static files and backend API routes on the same port (e.g., 80), so the browser sees a single origin and never triggers CORS checks.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗