The whole module consisted of separate tools: the image, Compose, the pipeline, secrets, metrics, tracing, the infrastructure plan. Now we gather them into one configuration - and along the way recall why each decision looks the way it does. Those justifications are what separate a copied configuration from an understood one.
1FROM node:20-alpine AS builder
2WORKDIR /app
3COPY package.json yarn.lock ./
4RUN yarn install --frozen-lockfile
5COPY . .
6RUN yarn build
7
8FROM node:20-alpine AS production
9WORKDIR /app
10RUN addgroup -g 1001 -S nodejs && adduser -S nestjs -u 1001
11COPY /app/dist ./dist
12COPY /app/node_modules ./node_modules
13USER nestjs
14EXPOSE 4000
15HEALTHCHECK \
16 CMD curl -f http://localhost:4000/health || exit 1
17CMD ["node", "dist/main"]Three decisions in this file are worth being able to justify.
carries the result from the first stage into the second. The order of its parts is fixed: COPY --from=builder
, COPY
, --from=builder
(from), /app/dist
(to). Thanks to it the production image contains no compiler, no sources and no development dependencies - only the result../dist
- we create a non-root user to limit the potential damage in case of a breach. Not to make the container start faster, not to make the image smaller, not to enable multithreading. A process running as root inside a container, having escaped the isolation, is root on the host as well.USER nestjs
tells Docker how to check whether the application is really alive, rather than merely whether the process exists. The order: HEALTHCHECK
, healthcheck:
, test: curl -f http://localhost:4000/health
, interval: 30s
.retries: 3
In the Compose file, check the two things most often confused.
The
container connects to the database by service name: api
- not by the container's IP address, not through mongodb:27017
localhost:27017 and not through external DNS. localhost inside a container is that same container, so it would point at the API rather than the database.The
flag in -v
removes the volumes along with their data. It does not enable verbose logging, does not validate the file and does not print the version. That command deletes the database - use it deliberately.docker compose down -v
A change's full road to production has four stages:
The fourth stage is often skipped, and it is the one that separates "deployed" from "working". Smoke tests are a handful of checks of the most important paths right after deployment - does the page open, does login work, does health respond. The pipeline may have gone green and the application still not come up, because an environment variable was missing that nobody needed on CI.
Once the image is ready, the cluster accepts a description of resources from a file:
1kubectl apply -f deployment.yamlThe order of parts:
, kubectl
, apply
, -f
. The deployment.yaml
-f flag names the file with the description; apply brings the cluster to the state written in it - creating what is missing and changing what differs.Scaling is a separate command:
1kubectl scale deployment roman-api --replicas=5Here the order reads:
, kubectl scale
, deployment roman-api
. Five replicas means five parallel copies of the application, among which the cluster spreads the traffic.--replicas=5
One last thing from the infrastructure layer. Modules in Terraform are reusable blocks of infrastructure that can be parameterised - not plugins for talking to a cloud (those are providers), not unit tests and not log files.
The point is the same as with a function in code: you describe "an application server with a database and a network" once, then call it three times for the test, staging and production environments, changing only the parameters.
Gather the set and check every point:
HEALTHCHECK,depends_on with service_healthy,push and pull_request, jobs in the order lint → build → test → docker, secrets in the repository settings,.env locally only,/metrics endpoint, RED metrics, tracing with span.end() in finally,.tf files, the state in a remote backend, not in Git.The campaign is ready to march:
COPY --from=builder carries the build result across: COPY, --from=builder, source, destination,HEALTHCHECK in order: healthcheck:, test:, interval:, retries:,mongodb:27017), not by IP and not through localhost,docker compose down -v removes the volumes along with their data,kubectl apply -f deployment.yaml feeds a description into the cluster,kubectl scale deployment name --replicas=5 changes the number of copies,This is the module's last lesson. You can now build an image, describe an environment, push a change through a pipeline, hide the secrets, see what is happening in production, and reproduce the whole infrastructure from a file. For now remember: every element of this configuration has a justification - and it is knowing them, not the file itself, that carries over to the next project.