A line-by-line walkthrough of a real Dockerfile and Cloud Build file: what a container image is, how the four build steps work, why images are tagged with the commit, and what Cloud Run does with the result.
What Is a CI/CD Pipeline, Really described the idea: a push triggers a build and a deploy. This chapter opens the two files that make it real for a Node.js website, a Dockerfile and a cloudbuild.yaml, so that "container" and "deploy to Cloud Run" stop being magic words. Both files are short enough to read in full.
What a container image is, what each line of a real Dockerfile does and why the order matters, the four steps of a real Cloud Build file, why every image is tagged with the commit, and what Cloud Run does with the result.
An application needs more than its own code: a particular version of Node.js, its libraries, sometimes system fonts or tools. On a server, any of these can be missing or different from your laptop. A container image fixes that by packaging the application together with everything it needs. Whoever runs the image gets the same environment you tested.
This is the shape of the Dockerfile for this site (comments shortened):
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-core fontconfig && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN chown node:node /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
EXPOSE 8080
CMD ["node", "server.js"]
FROM node:22-slim starts from a ready-made image with Node.js 22 on a small Linux. You never build an operating system yourself.RUN apt-get ... fonts-dejavu-core fontconfig installs two font packages at the operating-system level, then deletes the package lists so the image stays smaller.WORKDIR /app sets the folder where everything after this happens.COPY package*.json ./ and RUN npm ci --omit=dev install the libraries before the rest of the code is copied. This order is deliberate. Each instruction becomes a cached layer, and a layer is rebuilt only if its inputs changed. Libraries change rarely, code changes daily, so the slow install is reused on most builds.COPY . . brings in the rest of the application. Note what this means: it copies whatever is in the build folder. Keep secrets out of that folder. This project lists .env in .gitignore, so a build that starts from a repository checkout never contains it (see Secrets Management: Don't Commit Passwords).USER node makes the application run as an unprivileged user, not as root. If someone found a flaw in the app, they would have fewer rights.EXPOSE 8080 documents the port. Cloud Run actually sets a PORT environment variable at runtime, and the server reads it, with a fallback for local use.CMD ["node", "server.js"] is the command that starts the application when the container runs.The Cloud Build file runs these in order. A failure at any step stops everything after it.
npm ci --omit=dev --ignore-scripts && npm test. If any test fails, the build stops here and nothing is deployed. This is the gate that protects the live site.docker build with the Dockerfile above, producing the image.gcloud run deploy for the service, pointing at the image that was just pushed. The --quiet flag stops the command from asking questions, which matters when no human is watching.The image name ends with :$SHORT_SHA, the first seven characters of the commit hash. Every build therefore gets a unique, traceable name: given a running version, you can find the exact commit it came from, and given a commit, you can find its image. A tag like latest would hide that, because it points at something different each time.
Each successful deploy creates a new revision of the service and, by default, sends traffic to it. Older revisions are kept, which is what makes rolling back quick: you point traffic at an earlier revision instead of rebuilding anything. Cloud Run starts containers when requests arrive, gives each one the PORT variable, and starts the command in CMD.
Open your own project's Dockerfile, or any small one, and answer: (1) Which line would make the slow install re-run on every build if you moved it? (2) Which folder does COPY . . copy, and does it contain anything secret? (3) If a deploy broke the site, which single identifier would tell you the commit to look at? If you have Docker installed, docker build -t demo . followed by docker run -p 8080:8080 demo runs the same image on your own computer.
To make the scripts around deployments safe to re-run, read Writing Idempotent Deployment Scripts. To see how a change reaches the branch that triggers this pipeline, read Git for a Real Website.
CI/CD explained without the buzzwords — what continuous integration and continuous deployment actually automate, and what a real pipeline looks like end to end.
What idempotent actually means for a deployment or automation script, why it matters when a script might be run twice by accident, and concrete patterns for writing scripts that are safe to re-run.
Practical credential hygiene for real projects — why secrets don't belong in source control, how .gitignore and environment variables actually protect you (and where they don't), and what to do if a secret is committed by mistake.