# Containers and Helm Building images for both sides of the monorepo, and the Helm assets that deploy them. Read this when editing a Dockerfile, adding a chart, wiring an image tag, changing values or secrets, or propagating a port or base-path change into deployment. --- ## 1. `turbo prune` is what makes image builds sane Both sides build the same way: prune the monorepo to one workspace and its dependencies, install, build, then copy the result into a minimal runtime image. ```bash turbo prune --scope=@/ --docker ``` This produces `out/json/` (package manifests plus the lockfile) and `out/full/` (source). Copying and installing `out/json/` **before** `out/full/` is the whole point: dependency installation caches on manifests alone, so editing source does not reinstall, and an unrelated workspace's change does not invalidate this image at all. Without pruning, every image rebuilds on any change anywhere in the repository. ## 2. The two runtime shapes | | backend | frontend | |---|---|---| | Runtime stage | `node:22-alpine` | `nginx:alpine` | | Ships | compiled server + `node_modules` | static assets only | | Serves | the Node process | nginx | | Needs `try_files` fallback | no | **yes** | | Runtime env vars | yes — read at startup | **no** — baked in at build time | That last row is the one that causes incidents. A frontend build inlines its environment variables into the bundle, so they are public and fixed at build time. **A secret in a frontend build argument is a published secret.** Anything genuinely per-environment on the frontend must be fetched at runtime from an endpoint, not baked in. Full Dockerfiles: `references/applications-backend.md` §6 and `references/applications-frontend.md` §5. ## 3. Chart layout ```text helm-charts// ├── Chart.yaml ├── values.yaml # defaults — never environment-specific secrets ├── values-.yaml # per-environment overrides └── templates/ ├── deployment.yaml ├── service.yaml ├── ingress.yaml ├── configmap.yaml └── _helpers.tpl ``` Rules: - **Never hardcode an image tag in a template.** It comes from values, supplied by the pipeline. A hardcoded tag makes every environment deploy whatever was current when someone last edited the chart. - Ports and base paths in values must match the service's actual configuration (§5). - Resource requests and limits are set for every container. A container without them competes unboundedly with everything else on the node. - Liveness and readiness probes point at the service's two distinct health endpoints — see `references/applications-backend.md` §4. ## 4. Secrets - **No secret values in `values.yaml`, `values-.yaml`, templates, or the repository.** - Reference an existing Kubernetes `Secret` by name, or use an external secrets provider that materialises it in-cluster. - A base64 string in a committed manifest is encoding, not encryption — treat it as plaintext. - Never log a secret's value from a container's startup path. ## 5. Port and base-path propagation Changing a service's port or base path touches, at minimum: 1. the service's own config (`main.ts`, config factory), 2. the Dockerfile `EXPOSE`, 3. the chart's values and `service.yaml`, 4. ingress or gateway routing, 5. every frontend caller, 6. the paired code-agent `memory-bank/` record. Miss any one and the failure appears at runtime, in a different component from the one you edited. Treat it as a blast-radius change (SKILL.md §8) and update all six in the same change. ## 6. Validation ```bash helm lint helm-charts/ helm template helm-charts/ -f helm-charts//values-.yaml # what would actually change in the cluster helm diff upgrade helm-charts/ -f values-.yaml ``` `helm lint` checks structure, not intent — a chart that lints cleanly can still point at the wrong port. Always read the rendered output for the values that matter: image tag, port, base path, probe paths, resource limits. Verify an image locally before shipping it: ```bash docker build -f applications-backend//Dockerfile -t :local . docker run --rm -p : :local ``` Build from the **repository root** — the Dockerfile's `COPY . .` expects the whole monorepo as context, because `turbo prune` runs inside the image. ## 7. Mistakes to avoid - Building an image without `turbo prune`. - Copying `out/full/` before installing from `out/json/`, losing all dependency-layer caching. - Running a container as root. - A secret in a frontend build argument or any committed values file. - A hardcoded image tag in a template. - Missing `try_files` in the frontend nginx config. - Containers with no resource requests or limits. - A liveness probe that checks dependencies. - Changing a port or base path in fewer than all six places in §5. - Building the image from the workspace directory instead of the repository root. - Editing rendered manifests instead of the chart that produces them.