development-monorepo-pnpm skill: initial import
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>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user