Governance Basics
This section covers the concrete artifacts of container governance: the Dockerfile rules every team follows, the manifest fields every workload sets, and the automation that checks both.
Search across all documentation pages
This section covers the concrete artifacts of container governance: the Dockerfile rules every team follows, the manifest fields every workload sets, and the automation that checks both.
Governance here means encoded, testable standards - not a wiki page nobody reads.
kubectl, kubeconform, and hadolint available on CI runners.Quick install of the local checkers:
brew install hadolint kubeconform
docker run --rm aquasec/trivy:latest --versionPin the base by digest, build as a non-root user, and keep the runtime stage minimal.
# syntax=docker/dockerfile:1
FROM node:22-bookworm-slim@sha256:aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888 AS build
WORKDIR /src
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
FROM node:22-bookworm-slim@sha256:aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa7777bbbb8888
WORKDIR /app
COPY --from=build /src /app
USER 10001
CMD ["node", "server.js"]# syntax line opts into the current Dockerfile frontend so BuildKit features stay available.USER 10001 sets a numeric UID, which lets Kubernetes enforce runAsNonRoot without resolving a username.Fail the build on rule violations rather than reviewing them by hand.
hadolint --failure-threshold error Dockerfilehadolint catches unpinned apt-get install, a missing USER, and ADD where COPY belongs.--failure-threshold error lets style warnings pass while real defects block..hadolint.yaml per org so every repo inherits the same ignore list.Requests, limits, and probes are the minimum contract for a scheduled workload.
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
spec:
replicas: 3
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
containers:
- name: app
image: registry.example.com/checkout@sha256:1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
readinessProbe:
httpGet:
path: /readyz
port: 8080
livenessProbe:
httpGet:
path: /healthz
port: 8080Pod Security admission is built in and needs only labels.
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.36
pod-security.kubernetes.io/warn: restrictedenforce rejects violating pods at admission; warn only prints a message to the client.enforce-version stops a cluster upgrade from silently tightening the rules.warn first, read what it reports, then flip to enforce.runAsNonRoot, dropped capabilities, and a seccomp profile.The namespace label sets the bar; the pod has to clear it.
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]allowPrivilegeEscalation: false blocks setuid binaries from gaining privileges.ALL capabilities is required by restricted; add back only what a workload proves it needs.readOnlyRootFilesystem is not required by restricted but is cheap hardening.Schema validation catches typos that a policy engine would otherwise catch too late.
kubeconform -strict -kubernetes-version 1.36.2 -summary manifests/
kubectl apply --dry-run=server -f manifests/-strict rejects unknown fields, which is how most silent misconfigurations start.Governance depends on knowing who owns each object.
metadata:
labels:
app.kubernetes.io/name: checkout
app.kubernetes.io/part-of: payments
app.kubernetes.io/managed-by: Helm
annotations:
owner-team: payments-platform
slack-channel: "#payments-oncall"app.kubernetes.io/* keys are the recommended common labels and existing tools already read them.A policy applies the rule to code nobody reviewed.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-owner-annotation
spec:
validationFailureAction: Enforce
rules:
- name: check-owner
match:
any:
- resources:
kinds: ["Deployment", "StatefulSet"]
validate:
message: "owner-team annotation is required"
pattern:
metadata:
annotations:
owner-team: "?*"validationFailureAction: Enforce blocks the request; Audit records a policy report instead.Audit, read the reports for a week, then enforce.?* means "any non-empty value" in Kyverno's pattern matching.Make the compliant path the shortest path.
# charts/org-base/templates/_security.tpl - consumers include this named template
{{- define "org-base.podSecurity" -}}
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
{{- end -}}Scan and sign before anything reaches a registry teams can deploy from.
trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/checkout:build-123
cosign sign --yes registry.example.com/checkout@sha256:1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff--exit-code 1 turns a report into a gate; without it the scan is decoration.Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026