{"id":"l-dk-4-4","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Move notes-api’s settings into a ConfigMap and its credentials into a Secret, put everything in a <code dir=\"ltr\">notes</code> namespace, and run a reviewable apply-and-diff workflow from the k8s folder.","html":"<p>Right now the notes-api Deployment has <code dir=\"ltr\">PORT</code> written into it, the database Deployment has its password in plain text, and everything lives in the <code dir=\"ltr\">default</code> namespace alongside whatever else people try in this cluster. That’s three ways to cause an incident: change a setting and you must edit the workload; read the repository and you have the password; run a careless <code dir=\"ltr\">kubectl delete</code> in <code dir=\"ltr\">default</code> and you hit someone else’s work.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A namespace for notes-api</h2>\n<p>A <strong>namespace</strong> is a scope for names. Two teams can both have a Deployment called <code dir=\"ltr\">api</code> in different namespaces. Namespaces are also where you attach permissions (RBAC) and resource quotas, which makes them the natural unit for “one app” or “one team”.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">k8s/namespace.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Namespace</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span></code></pre></div>\n<p>Add <code dir=\"ltr\">namespace: notes</code> to the <code dir=\"ltr\">metadata</code> of every other object in <code dir=\"ltr\">k8s/</code>, then make it your default for this context so you don’t type <code dir=\"ltr\">-n notes</code> every time:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/namespace.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> config</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> set-context</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --current</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --namespace=notes</span></span></code></pre></div>\n<p>What namespaces don’t do is isolate the network. A Pod in <code dir=\"ltr\">notes</code> can connect to a Service in any other namespace unless a <strong>NetworkPolicy</strong> says otherwise, and your cluster’s network plugin enforces it. Don’t treat a namespace as a security wall on its own.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ConfigMaps for settings</h2>\n<p>A <strong>ConfigMap</strong> holds non-sensitive key-value configuration:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">k8s/configmap.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ConfigMap</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-config</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  namespace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">data</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  PORT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  LOG_LEVEL</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">info</span></span></code></pre></div>\n<p>Values are always strings, which is why <code dir=\"ltr\">&quot;3000&quot;</code> is quoted. Now the same notes-api image can run with <code dir=\"ltr\">info</code> logging in production and <code dir=\"ltr\">debug</code> in staging, by applying a different ConfigMap rather than building a different image.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Secrets for credentials</h2>\n<p>A <strong>Secret</strong> has the same shape as a ConfigMap but is meant for sensitive values. Create it from the command line (or from your secret manager) rather than from a committed file:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> generic</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --from-literal=POSTGRES_PASSWORD=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'dev-only-change-me'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --from-literal=DATABASE_URL=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'postgres://notes:dev-only-change-me@notes-db:5432/notes'</span></span></code></pre></div>\n<p>Here’s the uncomfortable part. Look at what’s stored:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -o</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> jsonpath='{.data.POSTGRES_PASSWORD}'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> base64</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span></span></code></pre></div>\n<p>The value comes straight back. Secret data is <strong>base64-encoded, not encrypted</strong>. What makes Secrets safer than ConfigMaps is everything around them: RBAC can grant read access to ConfigMaps but not Secrets, the cluster can encrypt Secrets at rest in etcd (managed providers usually do), and kubelets only receive Secrets for Pods scheduled on their node.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Secret manifests in Git</span></p>\n<p>A Secret YAML with base64 values in a repository is a plain-text password with one extra step. I’ve rotated more credentials for this reason than any other. Keep real values out of Git: create Secrets from a secret manager through a tool such as External Secrets Operator, or commit only encrypted forms with a tool built for that, and treat anything already committed as leaked.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Wiring them into notes-api</h2>\n<p>Update the container in <code dir=\"ltr\">k8s/deployment.yaml</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">k8s/deployment.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          envFrom</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">configMapRef</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-config</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          env</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">DATABASE_URL</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              valueFrom</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                secretKeyRef</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-db</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                  key</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">DATABASE_URL</span></span></code></pre></div>\n<p><code dir=\"ltr\">envFrom</code> turns every key in the ConfigMap into an environment variable. <code dir=\"ltr\">secretKeyRef</code> pulls one key from the Secret. In <code dir=\"ltr\">k8s/db.yaml</code>, replace the literal <code dir=\"ltr\">POSTGRES_PASSWORD</code> value with a <code dir=\"ltr\">secretKeyRef</code> to the same Secret.</p>\n<p>If a referenced key doesn’t exist, the container never starts. The Pod shows <code dir=\"ltr\">CreateContainerConfigError</code>, and <code dir=\"ltr\">kubectl describe pod</code> names the missing key.</p>\n<p>Secrets and ConfigMaps can also be <strong>mounted as files</strong> through a volume. Mounted files are updated in running Pods after a short delay; environment variables are fixed at container start. After changing env-based config, roll the Pods:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span></code></pre></div>\n<h2 id=\"sec-5\" tabindex=\"-1\">The everyday workflow</h2>\n<p>Your <code dir=\"ltr\">k8s/</code> folder now holds <code dir=\"ltr\">namespace.yaml</code>, <code dir=\"ltr\">configmap.yaml</code>, <code dir=\"ltr\">db.yaml</code>, <code dir=\"ltr\">deployment.yaml</code> and <code dir=\"ltr\">service.yaml</code>. The loop for every change is the same:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"7\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> diff</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> all</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> app.kubernetes.io/name=notes-api</span></span></code></pre></div>\n<p><code dir=\"ltr\">diff</code> is your review step: it shows what will change on the live cluster, field by field, before you change it. Make it a habit and you’ll catch the stray edit that would have reset someone’s emergency fix. When you need different values per environment, Kustomize (built into kubectl as <code dir=\"ltr\">kubectl apply -k</code>) layers small overlays on top of these base files; Helm, in lesson 5.3, is the other common answer.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Labels are your query language</span></p>\n<p>Put <code dir=\"ltr\">app.kubernetes.io/name</code> on everything, and <code dir=\"ltr\">app.kubernetes.io/part-of: notes</code> if several components make up the app. Then <code dir=\"ltr\">kubectl get all,cm,secret -l app.kubernetes.io/part-of=notes</code> shows the whole system in one command, and cleanup is one <code dir=\"ltr\">kubectl delete -l</code> away.</p>\n</aside>\n<p>notes-api now runs on Kubernetes with proper networking and configuration. It’s not production-ready yet: Kubernetes has no idea whether it’s healthy, how much CPU it needs, or how to update it safely. Section 5 covers all three.</p>\n","toc":[{"id":"sec-1","text":"A namespace for notes-api","level":2},{"id":"sec-2","text":"ConfigMaps for settings","level":2},{"id":"sec-3","text":"Secrets for credentials","level":2},{"id":"sec-4","text":"Wiring them into notes-api","level":2},{"id":"sec-5","text":"The everyday workflow","level":2}],"takeaways":["ConfigMaps hold non-sensitive settings; Secrets hold credentials and are injected as environment variables or mounted files.","Secret values are only base64-encoded, not encrypted; protect them with RBAC, encryption at rest, and by keeping them out of Git.","Environment variables from a ConfigMap or Secret are read once at container start, so restart the Deployment after changing them.","Namespaces scope names, permissions and quotas for a team or app, but they don’t isolate network traffic on their own.","<code dir=\"ltr\">kubectl diff -f k8s/</code> before <code dir=\"ltr\">kubectl apply -f k8s/</code> shows exactly what will change, like a pull request for the cluster."],"further":[{"title":"ConfigMaps","url":"https://kubernetes.io/docs/concepts/configuration/configmap/"},{"title":"Secrets","url":"https://kubernetes.io/docs/concepts/configuration/secret/"},{"title":"Good practices for Kubernetes Secrets","url":"https://kubernetes.io/docs/concepts/security/secrets-good-practices/"},{"title":"Namespaces","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/"}],"quiz":[{"q":"<p>A teammate commits a Secret manifest whose <code dir=\"ltr\">data</code> values are base64 strings, saying “it’s fine, they’re encoded”. What’s the problem?</p>","options":[{"text":"Base64 values must be in <code dir=\"ltr\">stringData</code>, not <code dir=\"ltr\">data</code>.","why":"It’s the other way round; <code dir=\"ltr\">data</code> takes base64 and <code dir=\"ltr\">stringData</code> takes plain text. The format isn’t the issue."},{"text":"Base64 is an encoding anyone can reverse with <code dir=\"ltr\">base64 -d</code>, so the credentials are now in Git history in effect as plain text.","why":"Correct. Treat the secret as leaked, rotate it, and keep Secret values out of Git (use a secret manager or encrypted tooling)."},{"text":"Kubernetes rejects Secrets that are committed to Git.","why":"Kubernetes can’t see your Git history. It will happily accept the manifest."},{"text":"Base64 makes values too long for environment variables.","why":"Kubernetes decodes values before injecting them; length isn’t the issue."}],"answer":1},{"q":"<p>You change <code dir=\"ltr\">LOG_LEVEL</code> in the <code dir=\"ltr\">notes-api-config</code> ConfigMap and apply it. The Pods consume it through <code dir=\"ltr\">envFrom</code>. What happens?</p>","options":[{"text":"Every Pod picks up the new value within a minute.","why":"Mounted ConfigMap <em>files</em> refresh over time; environment variables never change in a running container."},{"text":"The Deployment automatically rolls out new Pods.","why":"A ConfigMap change doesn’t touch the Deployment’s Pod template, so no rollout starts."},{"text":"Nothing changes in running Pods until they restart; run <code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>.","why":"Correct. Env vars are set at container start. A rollout restart replaces Pods gradually with the new values."},{"text":"The Pods crash because their environment changed underneath them.","why":"Running processes keep their environment; nothing is pushed to them."}],"answer":2},{"q":"<p>A new Pod is stuck with status <code dir=\"ltr\">CreateContainerConfigError</code>. What’s the most likely cause?</p>","options":[{"text":"The Pod references a Secret or ConfigMap key that doesn’t exist.","why":"Correct. The kubelet can’t build the container’s environment, so it never starts. <code dir=\"ltr\">kubectl describe pod</code> names the missing key."},{"text":"The image can’t be pulled from the registry.","why":"That shows as <code dir=\"ltr\">ErrImagePull</code> or <code dir=\"ltr\">ImagePullBackOff</code>."},{"text":"The app crashed on startup.","why":"A crash after start shows as <code dir=\"ltr\">CrashLoopBackOff</code>, with logs to read."},{"text":"The node is out of memory.","why":"A Pod that can’t be placed for lack of resources stays <code dir=\"ltr\">Pending</code> with a scheduling event."}],"answer":0},{"q":"<p>notes-api and a payments service run in separate namespaces on the same cluster. Can a notes-api Pod open a connection to the payments database Service?</p>","options":[{"text":"No, namespaces block all traffic between them.","why":"Namespaces scope names and permissions. Without a NetworkPolicy, Pods can reach Services in any namespace."},{"text":"Only if both namespaces have the same labels.","why":"Namespace labels matter for NetworkPolicy selectors, but without a policy there is no restriction at all."},{"text":"Only through a LoadBalancer Service.","why":"ClusterIP Services are reachable from every namespace in the cluster by default."},{"text":"Yes, by default; use a NetworkPolicy (with a network plugin that enforces it) to restrict it.","why":"Correct. Namespaces aren’t a network boundary. NetworkPolicies are how you add one."}],"answer":3}],"exercise":{"kind":"fill","language":"bash","template":"# Create the namespace and make it the default for this context\nkubectl create {{0}} notes\nkubectl config set-context --current --{{1}}=notes\n\n# Store the database password as a Secret\nkubectl create secret {{2}} notes-db --from-literal=POSTGRES_PASSWORD=dev-only-change-me\n\n# Read it back (and see that it isn't encrypted)\nkubectl get secret notes-db -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 {{3}}\n\n# Pick up changed environment variables in running Pods\nkubectl rollout {{4}} deployment/notes-api","blanks":[{"answers":["namespace","ns"]},{"answers":["namespace"]},{"answers":["generic"]},{"answers":["-d","--decode","-D"]},{"answers":["restart"]}],"title":"Namespace, Secret, restart","prompt":"<p>Fill in the blanks to create the <code dir=\"ltr\">notes</code> namespace and make it your default, store the database password in a Secret, decode it to prove it’s only base64, and restart notes-api so it picks up changed environment variables.</p>\n","hints":["The kind of object you’re creating first is also the name of the <code dir=\"ltr\">set-context</code> flag.","The Secret type for arbitrary key-value pairs is the opposite of “specific”.","<code dir=\"ltr\">base64</code> decodes with a short flag; <code dir=\"ltr\">kubectl rollout</code> has a subcommand that replaces Pods without changing the template by hand."],"explanation":"<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> namespace</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> config</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> set-context</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --current</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --namespace=notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> generic</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --from-literal=POSTGRES_PASSWORD=dev-only-change-me</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -o</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> jsonpath='{.data.POSTGRES_PASSWORD}'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> base64</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span></code></pre></div>\n<p><code dir=\"ltr\">generic</code> is the Secret type for arbitrary keys (others include <code dir=\"ltr\">tls</code> and <code dir=\"ltr\">docker-registry</code>). The decode step shows why Secret manifests must never be committed. <code dir=\"ltr\">rollout restart</code> replaces Pods gradually, and the new ones read the current ConfigMap and Secret values at start.</p>\n","checkLabels":{}},"words":691,"readMinutes":3}