Full-stack pnpm + Turborepo monorepo skill, independent of the separate backend and frontend monorepo skills it draws on. Repository shape is split four ways at the top level: applications-backend, applications-frontend, packages-backend, packages-frontend — so a path states which runtime a file ends up in. Defaults: NestJS for backend services, React with IBM Carbon Design for browser applications; the project's stated architecture overrides both. Covers workspace globs, the Turborepo pipeline, filtered validation, blast-radius checks, Module Federation, turbo prune image builds, and Helm. Documents one sanctioned cross-boundary dependency: API contract types owned by the backend, imported type-only by the frontend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
129 lines
5.0 KiB
Markdown
129 lines
5.0 KiB
Markdown
# 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=@<scope>/<workspace> --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-name>/
|
|
├── Chart.yaml
|
|
├── values.yaml # defaults — never environment-specific secrets
|
|
├── values-<env>.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-<env>.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/<chart-name>
|
|
helm template <release> helm-charts/<chart-name> -f helm-charts/<chart-name>/values-<env>.yaml
|
|
|
|
# what would actually change in the cluster
|
|
helm diff upgrade <release> helm-charts/<chart-name> -f values-<env>.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/<service>/Dockerfile -t <service>:local .
|
|
docker run --rm -p <port>:<port> <service>: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.
|