Best Practices From Day One (Before Bad Habits Stick)
Local labs forgive sloppiness; production does not. The good news: a small set of habits—namespaces, labels, resource declarations, sane security defaults, and config in objects instead of images—will make your manifests look like a platform team wrote them from the start.
In short
Isolate by namespace, label with app.kubernetes.io/*, set requests and limits, run non-root where you can, store config in ConfigMaps, keep secrets out of Git, and treat kubectl apply -f as your default workflow.
Namespaces: soft walls between teams and environments
apiVersion: v1
kind: Namespace
metadata:
name: learn-dev
labels:
environment: dev
kubectl apply -f ns-learn-dev.yaml
kubectl apply -f web.yaml -n learn-dev
kubectl get all -n learn-dev
Namespaces do not replace security by themselves, but they organize RBAC, quotas, and DNS (service.learn-dev.svc.cluster.local). Avoid dumping everything into default once you share a cluster.
Labels: the address book of the cluster
Use the recommended labels where they fit:
app.kubernetes.io/name— application name (used in selectors).app.kubernetes.io/instance— this deployment of the app (e.g.web-prod).app.kubernetes.io/version— version string or image tag.app.kubernetes.io/component—api,worker,database.app.kubernetes.io/part-of— larger system (e.g.billing).app.kubernetes.io/managed-by—helm,kustomize, or your team name.
Consistent labels unlock filtering, NetworkPolicies, cost allocation, and GitOps diffs that humans can read.
Resource requests and limits
Without requests, the scheduler guesses. Without limits, one noisy neighbor can starve a node.
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
Start conservative in labs; tune with metrics later. cpu is measured in cores (100m = 0.1 core). memory uses binary suffixes (Mi, Gi).
Security context basics (least privilege)
securityContext:
runAsNonRoot: true
runAsUser: 101
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
Not every image supports non-root or read-only roots on day one—nginx and distroless examples differ. Treat these fields as goals: fix the image or mount a writable emptyDir for temp paths instead of disabling security “because local.”
Configuration: ConfigMap, not baked into the image
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
namespace: learn-dev
data:
APP_MODE: "lab"
Mount as env vars or files in the Pod template. Changing config becomes a reviewed YAML change, not a rebuild of a golden image for every toggle.
Secrets: never commit real ones
- Local lab:
kubectl create secret generic demo --from-literal=token=testis enough to learn wiring. - Real clusters: use sealed secrets, external secrets operators, or cloud KMS—see GitOps principles.
- Add
*.secret.yamlpatterns to.gitignoreif you must keep local samples.
Workflow habits that scale
- Prefer
kubectl apply -foverkubectl editfor anything you care about—edit drifts from Git. - One concern per manifest file or one kustomization base; avoid thousand-line blobs.
- Pin images to tags or digests (
nginx:1.25-alpine, notlatest). - Probes: add
readinessProbeandlivenessProbebefore you call a Deployment “production ready”—see probes in depth (Part 5 shows debugging when they fail). - Policy later: admission controllers (OPA Gatekeeper, Kyverno) encode what you already practice manually.
Lab challenge
Refactor your Part 3 web Deployment into namespace learn-dev with recommended labels, resource requests/limits, and a ConfigMap env var. Apply from a folder in Git. Delete the namespace to wipe the experiment: kubectl delete namespace learn-dev.