{"checkpoints":{"s-dk-1":[{"q":"<p>You run notes-api with <code dir=\"ltr\">docker run --memory 256m</code>. Under a load test the container dies and <code dir=\"ltr\">docker ps -a</code> shows <code dir=\"ltr\">Exited (137)</code>. What enforced the limit?</p>","options":[{"text":"The PID namespace, which caps how much the process tree may allocate.","why":"Namespaces control what a process can see, not how much it can use."},{"text":"Node.js itself, which reads <code dir=\"ltr\">--memory</code> and exits with 137.","why":"Node doesn’t read Docker flags. The limit is enforced outside the process."},{"text":"A cgroup memory limit; the kernel’s OOM killer ended the process with SIGKILL (128 + 9 = 137).","why":"Correct. cgroups enforce resource limits, and an out-of-memory kill shows up as exit code 137."},{"text":"The image’s read-only layers, which can’t hold more than 256 MB.","why":"Layers are on disk and unrelated to how much RAM a process uses."}],"answer":2},{"q":"<p>You change one line in <code dir=\"ltr\">src/server.ts</code> and rebuild this image. Roughly how long does the build take compared with an unchanged rebuild?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</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:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node:24-slim</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WORKDIR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /app</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">COPY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> . .</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> npm ci</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">RUN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> npm run build</span></span></code></pre></div>\n","options":[{"text":"Much longer, because <code dir=\"ltr\">COPY . .</code> misses the cache and every step after it, including the full <code dir=\"ltr\">npm ci</code>, reruns.","why":"Correct. The source change invalidates the <code dir=\"ltr\">COPY</code>, and the expensive install sits after it. Copy the package files first."},{"text":"About the same, because only <code dir=\"ltr\">npm run build</code> needs to rerun.","why":"That would be true if dependencies were installed before the source was copied. Here they aren’t."},{"text":"Faster, because Docker reuses the previous <code dir=\"ltr\">node_modules</code> folder.","why":"Once <code dir=\"ltr\">COPY . .</code> misses, <code dir=\"ltr\">npm ci</code> runs again from scratch on the new layer."},{"text":"It fails, because changed files can’t be copied over cached layers.","why":"Changed files simply produce a new layer. Nothing fails; it’s just slow."}],"answer":0},{"q":"<p>notes-api runs with <code dir=\"ltr\">CMD [&quot;node&quot;, &quot;dist/server.js&quot;]</code> and has no SIGTERM handler. What does <code dir=\"ltr\">docker stop</code> do?</p>","options":[{"text":"Node exits immediately with code 143, because SIGTERM’s default action terminates the process.","why":"That’s what happens for a normal process. As PID 1, the default action is not applied, so the signal is ignored."},{"text":"Docker restarts the container instead of stopping it.","why":"<code dir=\"ltr\">docker stop</code> never restarts. A restart policy could restart it later, but not as part of stop."},{"text":"Node finishes in-flight requests and exits 0.","why":"Graceful draining only happens if your code handles SIGTERM. Without a handler there’s no drain."},{"text":"The signal is ignored because Node is PID 1 without a handler, so after 10 seconds Docker sends SIGKILL and the container exits with 137.","why":"Correct. Add a SIGTERM handler that closes the server and the database pool, and stops take well under a second."}],"answer":3},{"q":"<p>You run <code dir=\"ltr\">docker run -d -p 3000:3000 notes-api:dev</code>. The logs say <code dir=\"ltr\">listening on 3000</code>, but <code dir=\"ltr\">curl localhost:3000</code> from your laptop gets <code dir=\"ltr\">connection reset</code>. What do you check first?</p>","options":[{"text":"Whether the image has an <code dir=\"ltr\">EXPOSE 3000</code> line.","why":"<code dir=\"ltr\">EXPOSE</code> is documentation; the <code dir=\"ltr\">-p</code> flag already published the port."},{"text":"Whether the app binds to <code dir=\"ltr\">127.0.0.1</code> inside the container instead of <code dir=\"ltr\">0.0.0.0</code>.","why":"Correct. Published traffic arrives on the container’s network interface, which a loopback-only listener never sees."},{"text":"Whether port 3000 is open in the host firewall.","why":"A blocked port would refuse or time out from the start; a reset after connecting points to nothing listening on that interface inside the container."},{"text":"Whether Docker Desktop is up to date.","why":"Version issues are rarely the cause; the listen address is a far more common culprit."}],"answer":1},{"q":"<p>Which statement about images and containers is correct?</p>","options":[{"text":"Each container gets its own full copy of the image’s files on disk.","why":"Containers share the image’s read-only layers and only add a thin writable layer each."},{"text":"Removing a container also removes its image.","why":"Images stay until you remove them with <code dir=\"ltr\">docker image rm</code> or a prune."},{"text":"Many containers can run from one image; each has a private writable layer that is deleted with the container.","why":"Correct. That’s why data that must survive goes into a volume."},{"text":"A running container’s changes are written back into the image when it stops.","why":"Images are immutable. Changes stay in the container’s writable layer, which <code dir=\"ltr\">docker commit</code> could snapshot, but you shouldn’t."}],"answer":2}],"s-dk-2":[{"q":"<p>notes-api’s runtime stage has <code dir=\"ltr\">COPY --from=build /app/dist ./dist</code> and <code dir=\"ltr\">COPY --from=deps /app/node_modules ./node_modules</code>, but starting the container fails with <code dir=\"ltr\">Cannot use import statement outside a module</code>. What’s missing?</p>","options":[{"text":"TypeScript, which must be installed in the runtime stage.","why":"The runtime runs compiled JavaScript. TypeScript is only needed in the build stage."},{"text":"<code dir=\"ltr\">package.json</code>, whose <code dir=\"ltr\">&quot;type&quot;: &quot;module&quot;</code> tells Node to treat <code dir=\"ltr\">.js</code> files as ES modules.","why":"Correct. Copy <code dir=\"ltr\">package.json</code> into the runtime stage alongside <code dir=\"ltr\">dist</code> and <code dir=\"ltr\">node_modules</code>."},{"text":"The <code dir=\"ltr\">src/</code> folder, which Node reads at startup.","why":"Node runs <code dir=\"ltr\">dist/server.js</code>; it never reads TypeScript sources."},{"text":"An <code dir=\"ltr\">ENTRYPOINT</code> instruction.","why":"<code dir=\"ltr\">CMD</code> alone starts the app. The error comes from how Node parses the file, not how it’s started."}],"answer":1},{"q":"<p>Your security team requires that the notes-api process can’t modify its own code even if an attacker gets remote code execution. Which Dockerfile choice delivers that?</p>","options":[{"text":"Use <code dir=\"ltr\">COPY --chown=node:node</code> for all files so the app owns them.","why":"That gives the app user write access to its own code, the opposite of the requirement."},{"text":"Run as root but mount the filesystem read-only.","why":"Read-only roots help, but running as root weakens everything else. Combine non-root with read-only, don’t trade one for the other."},{"text":"Use the full <code dir=\"ltr\">node:24</code> image so system tools can verify file integrity.","why":"More tools mean more attack surface, not protection."},{"text":"Leave the copied files owned by root and switch to <code dir=\"ltr\">USER node</code> at the end, so the app can read but not write them.","why":"Correct. Root-owned, world-readable files plus an unprivileged user means the code is read-only to the process."}],"answer":3},{"q":"<p>A CI job builds <code dir=\"ltr\">notes-api</code> on an amd64 runner and deploys it to a cluster of arm64 nodes. Pods fail with <code dir=\"ltr\">exec format error</code>. What’s the fix?</p>","options":[{"text":"Build with <code dir=\"ltr\">docker buildx build --platform linux/amd64,linux/arm64</code> and push, so the tag points to an index with both architectures.","why":"Correct. Each node pulls the manifest for its own architecture from the same tag."},{"text":"Pin the base image by digest.","why":"Digest pinning fixes the bytes, but if those bytes are amd64-only, arm64 nodes still can’t run them."},{"text":"Change the base image to <code dir=\"ltr\">node:24-alpine</code>.","why":"Alpine images are also per architecture. The build still produces amd64 only."},{"text":"Add <code dir=\"ltr\">imagePullPolicy: Always</code>.","why":"It pulls the same amd64-only image again."}],"answer":0},{"q":"<p>Your registry has <code dir=\"ltr\">notes-api:1.4.0</code>. A hotfix is ready. What should you tag it?</p>","options":[{"text":"<code dir=\"ltr\">1.4.0</code> again, so the manifests don’t need to change.","why":"Re-pushing a deployed tag means nodes run different code under the same name depending on when they pulled. Never reuse it."},{"text":"<code dir=\"ltr\">latest</code>, since it’s the newest build.","why":"<code dir=\"ltr\">latest</code> is just a default name; it gives no traceability and changes under you."},{"text":"<code dir=\"ltr\">1.4.1</code> plus a <code dir=\"ltr\">sha-&lt;commit&gt;</code> tag, and update the manifest (ideally to the new digest).","why":"Correct. New content gets a new version; the SHA tag and digest make it traceable and exact."},{"text":"<code dir=\"ltr\">1.4.0-fixed</code>, overwriting it if another fix comes.","why":"Overwriting a tag has the same problem as reusing <code dir=\"ltr\">1.4.0</code>."}],"answer":2},{"q":"<p><code dir=\"ltr\">docker scout cves</code> reports a critical CVE in an npm package that notes-api uses, and a fixed version exists. What’s the right response?</p>","options":[{"text":"Switch the base image to distroless.","why":"Distroless removes OS packages, but the vulnerable npm package is in your <code dir=\"ltr\">node_modules</code> and ships either way."},{"text":"Bump the dependency to the fixed version, rebuild, rescan, and keep the CVE gate in CI.","why":"Correct. A fixable app dependency gets fixed, and the gate stops it from coming back."},{"text":"Add the CVE to an ignore list so the build passes.","why":"Ignoring a fixable critical finding hides real risk; ignore lists are for documented, unreachable or unfixable cases."},{"text":"Rebuild with <code dir=\"ltr\">--no-cache</code> so the scanner sees fresh layers.","why":"A fresh build installs the same locked version and gets the same finding."}],"answer":1}],"s-dk-3":[{"q":"<p>In Compose, the <code dir=\"ltr\">api</code> service’s <code dir=\"ltr\">DATABASE_URL</code> is <code dir=\"ltr\">postgres://notes:notes@db:5432/notes</code>. You rename the service from <code dir=\"ltr\">db</code> to <code dir=\"ltr\">postgres</code> and run <code dir=\"ltr\">docker compose up -d --remove-orphans</code>. What happens?</p>","options":[{"text":"Nothing changes; Compose keeps the old DNS name as an alias.","why":"DNS names come from the current service names. <code dir=\"ltr\">--remove-orphans</code> removes the old <code dir=\"ltr\">db</code> container, and its name goes with it. (Without that flag the orphan keeps running and still answers to <code dir=\"ltr\">db</code>, which hides the bug.)"},{"text":"Compose rewrites <code dir=\"ltr\">DATABASE_URL</code> to match.","why":"Compose never edits environment values for you."},{"text":"Both names resolve, because the volume is still called <code dir=\"ltr\">pgdata</code>.","why":"Volume names have nothing to do with network DNS."},{"text":"The API can no longer resolve <code dir=\"ltr\">db</code> and fails to connect until you update the URL host to <code dir=\"ltr\">postgres</code>.","why":"Correct. Services find each other by service name, so the connection string must follow the rename."}],"answer":3},{"q":"<p>You run <code dir=\"ltr\">docker compose down</code>, edit <code dir=\"ltr\">db/init/002-seed.sql</code>, and run <code dir=\"ltr\">docker compose up -d</code>. The seed data doesn’t appear. Why?</p>","options":[{"text":"<code dir=\"ltr\">down</code> deleted the bind mount.","why":"Bind mounts point at your host folder; nothing deletes it."},{"text":"Init scripts run in reverse order.","why":"They run in name order, and that isn’t the issue here."},{"text":"The <code dir=\"ltr\">pgdata</code> volume survived <code dir=\"ltr\">down</code>, so the database is already initialised and init scripts are skipped.","why":"Correct. Use a migration for changes to an existing database, or remove the dev volume on purpose with <code dir=\"ltr\">down -v</code>."},{"text":"Postgres only reads <code dir=\"ltr\">.sql</code> files at image build time.","why":"Init scripts run at container start, but only against an empty data directory."}],"answer":2},{"q":"<p>A <code dir=\"ltr\">migrate</code> service runs schema migrations and exits. You want <code dir=\"ltr\">api</code> to start only after Postgres is healthy and the migration succeeded. Which <code dir=\"ltr\">depends_on</code> for <code dir=\"ltr\">api</code> is right?</p>","options":[{"text":"<code dir=\"ltr\">db</code> with <code dir=\"ltr\">condition: service_healthy</code>, and <code dir=\"ltr\">migrate</code> with <code dir=\"ltr\">condition: service_completed_successfully</code>.","why":"Correct. One waits for the healthcheck; the other waits for the job to exit with code 0."},{"text":"<code dir=\"ltr\">db</code> and <code dir=\"ltr\">migrate</code> in the short list form.","why":"Short form only waits for the containers to start, not for readiness or completion."},{"text":"<code dir=\"ltr\">migrate</code> with <code dir=\"ltr\">condition: service_healthy</code>.","why":"A job that exits never becomes healthy, so <code dir=\"ltr\">api</code> would never start."},{"text":"<code dir=\"ltr\">db</code> with <code dir=\"ltr\">condition: service_started</code> and <code dir=\"ltr\">restart: true</code>.","why":"That still doesn’t wait for Postgres to accept connections or for the migration to finish."}],"answer":0},{"q":"<p>Your project <code dir=\"ltr\">.env</code> contains <code dir=\"ltr\">POSTGRES_PASSWORD=devpass</code>. <code dir=\"ltr\">compose.yaml</code> sets <code dir=\"ltr\">POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}</code> under <code dir=\"ltr\">db.environment</code>. You run <code dir=\"ltr\">POSTGRES_PASSWORD=other docker compose up -d</code> on a fresh volume. Which password does Postgres get?</p>","options":[{"text":"<code dir=\"ltr\">devpass</code>, because <code dir=\"ltr\">.env</code> always wins.","why":"For interpolation, a variable already set in your shell takes precedence over <code dir=\"ltr\">.env</code>."},{"text":"<code dir=\"ltr\">other</code>, because shell environment variables take precedence over <code dir=\"ltr\">.env</code> for interpolation.","why":"Correct. Handy for one-off overrides, and a reason to check <code dir=\"ltr\">docker compose config</code> when values surprise you."},{"text":"Both, joined with a comma.","why":"Variables have one value; nothing gets concatenated."},{"text":"Neither; Compose errors on conflicting values.","why":"There’s no conflict error; precedence rules decide."}],"answer":1},{"q":"<p>Your teammate’s dev setup bind-mounts the whole project (<code dir=\"ltr\">.:/app</code>) into the API container on macOS. The app fails with a native module error and file changes feel slow. What do you recommend?</p>","options":[{"text":"Add <code dir=\"ltr\">:ro</code> to the bind mount.","why":"Read-only doesn’t stop the host’s macOS-built <code dir=\"ltr\">node_modules</code> from shadowing the container’s Linux one."},{"text":"Switch to a named volume for the whole project.","why":"A named volume would never see their edits at all."},{"text":"Run <code dir=\"ltr\">npm ci</code> on the Mac before every start.","why":"That still produces macOS binaries, which Linux can’t load."},{"text":"Use Compose watch to sync <code dir=\"ltr\">./src</code> into the container and rebuild on lockfile changes, leaving <code dir=\"ltr\">node_modules</code> inside the image.","why":"Correct. Only source files cross the VM boundary, and dependencies stay built for Linux."}],"answer":3}],"s-dk-4":[{"q":"<p>You apply a Deployment with 3 replicas, then delete its ReplicaSet with <code dir=\"ltr\">kubectl delete rs notes-api-6c8f9d7b5</code>. What happens?</p>","options":[{"text":"The Deployment controller creates a new ReplicaSet for the same template, which creates 3 Pods again.","why":"Correct. The Deployment still declares 3 replicas of that template, so its controller reconciles the missing ReplicaSet back."},{"text":"The Deployment is deleted too, because it owned the ReplicaSet.","why":"Ownership flows downward: deleting a child doesn’t delete its owner."},{"text":"The Pods keep running without an owner forever.","why":"Deleting the ReplicaSet deletes its Pods by default, and the Deployment recreates both anyway."},{"text":"Nothing, because ReplicaSets can’t be deleted directly.","why":"You can delete them; the controller simply restores what the Deployment declares."}],"answer":0},{"q":"<p>Pods in namespace <code dir=\"ltr\">web</code> need to call notes-api, whose Service <code dir=\"ltr\">notes-api</code> (port 80) lives in namespace <code dir=\"ltr\">notes</code>. Which URL works?</p>","options":[{"text":"<code dir=\"ltr\">http://notes-api</code> (port 80)","why":"The short name resolves only within the same namespace, so from <code dir=\"ltr\">web</code> it doesn’t find the <code dir=\"ltr\">notes</code> Service."},{"text":"<code dir=\"ltr\">http://notes-api.notes</code> (port 80), or the full <code dir=\"ltr\">notes-api.notes.svc.cluster.local</code>","why":"Correct. Adding the namespace to the name makes it resolvable from any namespace."},{"text":"<code dir=\"ltr\">http://notes:3000</code>","why":"<code dir=\"ltr\">notes</code> is the namespace, not a Service, and 3000 is the Pod port rather than the Service port."},{"text":"<code dir=\"ltr\">http://localhost:80</code> with a NodePort","why":"<code dir=\"ltr\">localhost</code> is the calling Pod itself."}],"answer":1},{"q":"<p>After a change, notes-api Pods are Running and Ready, but the Service’s EndpointSlice is empty. What changed most likely?</p>","options":[{"text":"The image tag was bumped.","why":"A new image creates new Pods with the same labels; endpoints follow them."},{"text":"The Service type changed from ClusterIP to LoadBalancer.","why":"Type changes add external exposure; they don’t change which Pods back the Service."},{"text":"The Pod template’s labels changed and no longer match the Service’s selector.","why":"Correct. Ready Pods without matching labels are invisible to the Service. Compare <code dir=\"ltr\">kubectl get pods --show-labels</code> with the selector."},{"text":"The ConfigMap was updated.","why":"Configuration values don’t affect label matching."}],"answer":2},{"q":"<p>You update the <code dir=\"ltr\">notes-db</code> Secret with a new password, and the Postgres password is changed to match. notes-api reads <code dir=\"ltr\">DATABASE_URL</code> via <code dir=\"ltr\">secretKeyRef</code>. Existing Pods start failing to authenticate. Why, and what’s the fix?</p>","options":[{"text":"Secrets are cached for 24 hours; wait for the cache to expire.","why":"There’s no such cache rule for env vars. They never change in a running container."},{"text":"The Secret must be deleted and recreated, not updated.","why":"Updating is fine; the issue is how the running Pods consume it."},{"text":"The Deployment’s selector must be changed to pick up the new Secret.","why":"Selectors choose Pods, not configuration. Changing them would break the Deployment."},{"text":"Environment variables are read at container start, so running Pods still use the old value; run <code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>.","why":"Correct. New Pods read the updated Secret. For zero-downtime rotation, make the database accept both passwords during the switch."}],"answer":3},{"q":"<p>Why do teams run <code dir=\"ltr\">kubectl diff -f k8s/</code> before <code dir=\"ltr\">kubectl apply -f k8s/</code>?</p>","options":[{"text":"To see exactly which fields on the live cluster will change, catching surprises such as resetting an emergency scale-up.","why":"Correct. It’s the review step for the cluster, like reading a pull request before merging."},{"text":"Because <code dir=\"ltr\">apply</code> fails unless <code dir=\"ltr\">diff</code> ran first.","why":"<code dir=\"ltr\">apply</code> works on its own; <code dir=\"ltr\">diff</code> is a safety habit, not a requirement."},{"text":"To validate YAML syntax only.","why":"It does need valid YAML, but its value is comparing your files against the live objects."},{"text":"To download the current manifests into the <code dir=\"ltr\">k8s/</code> folder.","why":"<code dir=\"ltr\">diff</code> doesn’t write files; it prints differences."}],"answer":0}],"s-dk-5":[{"q":"<p>notes-api’s readiness probe hits <code dir=\"ltr\">/readyz</code> (which checks Postgres) and liveness hits <code dir=\"ltr\">/healthz</code>. Postgres goes down for two minutes. What happens?</p>","options":[{"text":"All Pods restart repeatedly until Postgres returns.","why":"Liveness only checks <code dir=\"ltr\">/healthz</code>, which doesn’t touch Postgres, so no restarts happen."},{"text":"Kubernetes reschedules the Pods to other nodes.","why":"Probes never move Pods between nodes."},{"text":"All Pods become not ready and leave the Service’s endpoints; they rejoin without restarting when Postgres is back.","why":"Correct. Requests fail fast at the Service instead of hanging in the app, and recovery needs no restarts."},{"text":"Nothing, because probes are paused when a dependency is down.","why":"Kubernetes doesn’t know about dependencies; it runs probes as configured."}],"answer":2},{"q":"<p>A node has 4 CPU cores and its Pods’ CPU requests add up to 3.8 cores, though actual usage is only 1 core. A new notes-api Pod requests 250m. What happens?</p>","options":[{"text":"It’s scheduled there, because actual usage is low.","why":"The scheduler places Pods by requests, not current usage."},{"text":"It’s scheduled there and other Pods are throttled to make room.","why":"Scheduling never squeezes existing Pods’ requests."},{"text":"It’s scheduled with a reduced request of 200m.","why":"Requests are never adjusted to fit a node."},{"text":"It isn’t scheduled on that node, because only 200m of requestable CPU is left; it goes elsewhere or stays Pending.","why":"Correct. Requests are reservations. Over-requesting wastes capacity even when nodes look idle."}],"answer":3},{"q":"<p>During a rollout to 1.6.0 with <code dir=\"ltr\">maxUnavailable: 0</code>, <code dir=\"ltr\">kubectl rollout status</code> hangs and then reports <code dir=\"ltr\">exceeded its progress deadline</code>. The new Pod’s readiness probe fails. What’s the state of the service?</p>","options":[{"text":"Users are getting errors from the 1.6.0 Pod.","why":"An unready Pod isn’t in the Service’s endpoints, so it receives no traffic."},{"text":"The old 1.5.x Pods are still serving at full capacity; the rollout is stuck and needs an undo or a fix.","why":"Correct. With no unavailability allowed and readiness gating, the old ReplicaSet was never scaled down."},{"text":"Kubernetes has already rolled back to the previous version.","why":"Kubernetes reports the failure but doesn’t roll back by itself."},{"text":"The Deployment has scaled to zero.","why":"Nothing in a rolling update scales the whole Deployment to zero."}],"answer":1},{"q":"<p>Production uses a shared Gateway. The notes team’s HTTPRoute shows <code dir=\"ltr\">Accepted: False</code> with reason <code dir=\"ltr\">NotAllowedByListeners</code>. What’s the likely cause?</p>","options":[{"text":"The Gateway’s listener <code dir=\"ltr\">allowedRoutes</code> doesn’t permit routes from the <code dir=\"ltr\">notes</code> namespace.","why":"Correct. Gateways choose which namespaces may attach routes; ask the platform team to allow <code dir=\"ltr\">notes</code> or use a selector it matches."},{"text":"The notes-api Service has no endpoints.","why":"Endpoints affect whether traffic reaches Pods, not whether the Gateway accepts the route."},{"text":"The HTTPRoute uses <code dir=\"ltr\">apiVersion: gateway.networking.k8s.io/v1</code>.","why":"That’s the current, correct version."},{"text":"The Pods are missing a readiness probe.","why":"Probes don’t influence route attachment."}],"answer":0},{"q":"<p>A distroless notes-api Pod is Running but every request to it times out. <code dir=\"ltr\">kubectl exec</code> fails because there’s no shell. Which command lets you test, from inside the Pod’s network, whether the app answers on port 3000?</p>","options":[{"text":"<code dir=\"ltr\">kubectl logs &lt;pod&gt; --previous</code>","why":"Logs show past output; they can’t test connectivity, and the container hasn’t crashed."},{"text":"<code dir=\"ltr\">kubectl port-forward pod/&lt;pod&gt; 3000:3000</code> followed by <code dir=\"ltr\">kubectl events</code>","why":"Port-forwarding tests from your laptop through the API server, not from inside the Pod’s network, and events won’t show request timeouts."},{"text":"<code dir=\"ltr\">kubectl debug -it &lt;pod&gt; --image=busybox:1.37 --target=api</code>, then <code dir=\"ltr\">wget -qO- localhost:3000/healthz</code>","why":"Correct. The ephemeral container shares the Pod’s network namespace, so <code dir=\"ltr\">localhost:3000</code> is the app itself."},{"text":"<code dir=\"ltr\">kubectl describe node</code>","why":"Node details won’t tell you whether the app answers inside the Pod."}],"answer":2}]},"final":[{"q":"<p>A Skylane engineer wants faster CI builds for notes-api. Builds run on fresh runners with an empty Docker cache, and the Dockerfile is already ordered correctly. What helps most?</p>","options":[{"text":"Switching <code dir=\"ltr\">npm ci</code> to <code dir=\"ltr\">npm install</code>.","why":"It’s not meaningfully faster on a cold cache and gives up reproducible installs."},{"text":"Exporting and importing the build cache with <code dir=\"ltr\">--cache-to</code> and <code dir=\"ltr\">--cache-from</code> (for example to a registry).","why":"Correct. Fresh runners can then reuse layers built by earlier jobs."},{"text":"Building with <code dir=\"ltr\">--no-cache</code> for consistency.","why":"That guarantees every step runs, which is the slowest possible build."},{"text":"Adding more <code dir=\"ltr\">RUN</code> steps so there are more cache points.","why":"More layers don’t help when there’s no cache to read in the first place."}],"answer":1},{"q":"<p>Which Dockerfile line puts a credential at risk even though it’s deleted later in the same build?</p>","options":[{"text":"<code dir=\"ltr\">RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci</code>","why":"A secret mount exists only for that step and is never written into a layer. This is the safe pattern."},{"text":"<code dir=\"ltr\">COPY package.json package-lock.json ./</code>","why":"Manifests and lockfiles don’t contain credentials."},{"text":"<code dir=\"ltr\">USER node</code>","why":"Changing user doesn’t expose any secret."},{"text":"<code dir=\"ltr\">COPY .npmrc .</code> followed later by <code dir=\"ltr\">RUN rm .npmrc</code>","why":"Correct. The file lives on in the <code dir=\"ltr\">COPY</code> layer; deleting it in a later layer only hides it."}],"answer":3},{"q":"<p>notes-api’s image runs as <code dir=\"ltr\">USER node</code>, and the Deployment’s securityContext sets <code dir=\"ltr\">runAsNonRoot: true</code>. Pods fail with <code dir=\"ltr\">CreateContainerConfigError</code>. What’s the cleanest fix?</p>","options":[{"text":"Set a numeric user, either <code dir=\"ltr\">USER 1000</code> in the Dockerfile or <code dir=\"ltr\">runAsUser: 1000</code> in the securityContext, so the kubelet can verify it isn’t root.","why":"Correct. The kubelet can’t prove a user <em>name</em> isn’t root, so it refuses to start the container."},{"text":"Remove <code dir=\"ltr\">runAsNonRoot</code> from the securityContext.","why":"That removes a safety check instead of satisfying it."},{"text":"Switch the image back to running as root.","why":"Then <code dir=\"ltr\">runAsNonRoot</code> rejects it outright, and you’ve lost the protection anyway."},{"text":"Add <code dir=\"ltr\">allowPrivilegeEscalation: true</code>.","why":"Unrelated to user verification, and a weaker setting."}],"answer":0},{"q":"<p>A Compose stack starts reliably on your laptop, but in CI the integration tests sometimes run before the API is ready. Which change fixes it without guesswork?</p>","options":[{"text":"Add <code dir=\"ltr\">sleep 30</code> before the tests.","why":"Too short on a slow runner, wasteful on a fast one."},{"text":"Use <code dir=\"ltr\">restart: always</code> on the API.","why":"Restarting doesn’t tell the test step when the API is ready."},{"text":"Give the API and database healthchecks and start the stack with <code dir=\"ltr\">docker compose up -d --wait</code>.","why":"Correct. <code dir=\"ltr\">--wait</code> returns only when services are healthy and fails if one turns unhealthy."},{"text":"Publish the database port so tests can poll it.","why":"The database being up doesn’t prove the API is ready."}],"answer":2},{"q":"<p>You inherit a Deployment with no readiness probe and default rolling-update settings. A release with a broken database URL goes out. What happens?</p>","options":[{"text":"The rollout stalls and the old Pods keep serving.","why":"Without a readiness probe, the new Pods count as ready as soon as they’re running, so nothing stalls."},{"text":"Kubernetes detects the bad URL and rejects the release.","why":"Kubernetes knows nothing about your app’s configuration values."},{"text":"Only one Pod is replaced, because of <code dir=\"ltr\">maxSurge</code>.","why":"<code dir=\"ltr\">maxSurge</code> paces the rollout; it doesn’t stop it."},{"text":"New Pods are marked ready as soon as their containers start, so the rollout replaces every working Pod with a failing one.","why":"Correct. Readiness is what gates rollouts. Without it, a broken version takes all the traffic."}],"answer":3},{"q":"<p>notes-api has CPU request 500m and no CPU limit, memory request and limit 512Mi. Under a traffic spike its CPU usage reaches 1.5 cores on a node with idle capacity. What happens?</p>","options":[{"text":"It’s OOM-killed for exceeding its request.","why":"OOM kills relate to memory limits, not CPU, and requests are never enforced as ceilings."},{"text":"It uses the idle CPU, since there’s no CPU limit to throttle it; its request still guarantees 500m under contention.","why":"Correct. Requests reserve; only limits cap. This is the reason some teams leave CPU limits off."},{"text":"It’s throttled to 500m.","why":"Throttling applies at a CPU <em>limit</em>. A request is a minimum reservation, not a cap."},{"text":"The scheduler moves it to a bigger node.","why":"Running Pods are never moved by the scheduler; new Pods get new placements."}],"answer":1},{"q":"<p>Which manifest is valid for current Kubernetes?</p>","options":[{"text":"<code dir=\"ltr\">apiVersion: extensions/v1beta1</code> with <code dir=\"ltr\">kind: Deployment</code>","why":"That group and version for Deployments was removed long ago."},{"text":"<code dir=\"ltr\">apiVersion: autoscaling/v1</code> with an HPA that uses <code dir=\"ltr\">metrics</code> and <code dir=\"ltr\">behavior</code>","why":"<code dir=\"ltr\">metrics</code> and <code dir=\"ltr\">behavior</code> are <code dir=\"ltr\">autoscaling/v2</code> fields."},{"text":"<code dir=\"ltr\">apiVersion: networking.k8s.io/v1beta1</code> with <code dir=\"ltr\">kind: Ingress</code>","why":"Ingress is served as <code dir=\"ltr\">networking.k8s.io/v1</code>; the beta version was removed."},{"text":"<code dir=\"ltr\">apiVersion: apps/v1</code> with <code dir=\"ltr\">kind: Deployment</code>","why":"Correct. Deployments, ReplicaSets and StatefulSets live in <code dir=\"ltr\">apps/v1</code>."}],"answer":3},{"q":"<p>notes-api 1.5.0 added a column, and its code writes to both the old and new columns. 1.5.0 has a bug, so you run <code dir=\"ltr\">kubectl rollout undo</code>. Why is this rollback safe?</p>","options":[{"text":"Because <code dir=\"ltr\">rollout undo</code> restores a database snapshot.","why":"Kubernetes never touches your database."},{"text":"Because the schema change was additive, 1.4.0 still finds everything it expects.","why":"Correct. Expand-then-contract changes keep adjacent versions compatible, which makes rollbacks boring."},{"text":"Because Postgres ignores unknown columns in UPDATE statements.","why":"Postgres doesn’t ignore anything; 1.4.0 simply never references the new column."},{"text":"Because rollbacks are always safe in Kubernetes.","why":"Code rollbacks are only safe when data and schema are compatible with the older code."}],"answer":1},{"q":"<p>notes-api’s HPA targets 60% CPU utilization with a CPU request of 200m. Four Pods average 240m each. Roughly how many replicas will the HPA want?</p>","options":[{"text":"4, because the HPA only scales when usage exceeds the request.","why":"The target is 60% of the request (120m). 240m is double that."},{"text":"5","why":"Desired = 4 × 240 / 120 = 8, not 5."},{"text":"8","why":"Correct. Usage is 200% of the 120m target per Pod, so the HPA doubles the replicas (within <code dir=\"ltr\">maxReplicas</code>)."},{"text":"2, because it averages the request and the usage.","why":"The HPA scales by the ratio of usage to target; it doesn’t average them."}],"answer":2},{"q":"<p>The ingress-nginx controller your cluster used is retired and receives no security fixes. What’s the recommended direction for new HTTP routing setups?</p>","options":[{"text":"Expose every service with its own NodePort.","why":"NodePorts aren’t a public entry point; you’d lose TLS termination and routing."},{"text":"Keep the retired controller; it will be maintained by Kubernetes core.","why":"Retired means no releases and no security fixes."},{"text":"Replace Ingress with a LoadBalancer Service per app.","why":"Workable for one service; costly and unroutable by host or path for many."},{"text":"Gateway API: a Gateway owned by the platform team and HTTPRoutes owned by each app team.","why":"Correct. Gateway API is the project’s recommended successor, with role-oriented resources and many implementations."}],"answer":3},{"q":"<p>A notes-api Pod shows <code dir=\"ltr\">CrashLoopBackOff</code> and <code dir=\"ltr\">Last State: Terminated, Exit Code: 137</code>. <code dir=\"ltr\">kubectl logs --previous</code> shows normal request logs, then nothing. What’s the likely cause and fix?</p>","options":[{"text":"The container exceeded its memory limit and was OOM-killed; check <code dir=\"ltr\">describe</code> for <code dir=\"ltr\">OOMKilled</code>, then raise the limit or fix the leak and cap the V8 heap.","why":"Correct. Exit 137 with no error output points to SIGKILL, and memory is the usual reason in a crash loop."},{"text":"A JavaScript exception crashed the process.","why":"An uncaught exception prints a stack trace and exits with code 1, not 137."},{"text":"The image can’t be pulled.","why":"A pull failure never gets as far as running the app or logging requests."},{"text":"The readiness probe failed.","why":"Readiness failures don’t kill containers at all."}],"answer":0},{"q":"<p>Which combination gives notes-api zero-downtime deploys?</p>","options":[{"text":"<code dir=\"ltr\">Recreate</code> strategy plus a liveness probe on <code dir=\"ltr\">/readyz</code>.","why":"<code dir=\"ltr\">Recreate</code> stops everything first, and a dependency-checking liveness probe adds restart storms."},{"text":"No probes, <code dir=\"ltr\">maxUnavailable: 25%</code>, and a high <code dir=\"ltr\">terminationGracePeriodSeconds</code>.","why":"Without readiness, new Pods take traffic before they’re ready, and capacity drops during the rollout."},{"text":"A readiness probe, <code dir=\"ltr\">maxUnavailable: 0</code>, a short <code dir=\"ltr\">preStop</code> sleep, and a SIGTERM handler that drains in-flight requests.","why":"Correct. New Pods take traffic only when ready, capacity never drops, and old Pods leave without dropping requests."},{"text":"<code dir=\"ltr\">replicas: 1</code> with <code dir=\"ltr\">kubectl rollout restart</code> after every change.","why":"A single replica means any restart is downtime."}],"answer":2}],"glossary":[{"id":"container","term":"Container","definition":"An ordinary process that the Linux kernel isolates with namespaces and limits with cgroups, running from an image’s filesystem plus a private, disposable writable layer.","lesson":"l-dk-1-1"},{"id":"image","term":"Image","definition":"A read-only, layered filesystem snapshot plus metadata such as the default command and user. One image can be the template for any number of containers.","lesson":"l-dk-1-1"},{"id":"namespaces-cgroups","term":"Namespaces and cgroups","definition":"The two kernel features behind containers: namespaces control what a process can see (processes, network, mounts), and cgroups limit what it can use (CPU, memory, I/O).","lesson":"l-dk-1-1"},{"id":"build-context","term":"Build context","definition":"The set of files sent to the builder by <code dir=\"ltr\">docker build</code>, usually the directory you pass as the last argument, minus anything matched by <code dir=\"ltr\">.dockerignore</code>. <code dir=\"ltr\">COPY</code> can only read from it.","lesson":"l-dk-1-2"},{"id":"exec-form","term":"Exec form","definition":"Writing <code dir=\"ltr\">CMD</code> or <code dir=\"ltr\">ENTRYPOINT</code> as a JSON array, such as <code dir=\"ltr\">[&quot;node&quot;, &quot;dist/server.js&quot;]</code>, so the program runs as PID 1 without a shell and receives stop signals directly.","lesson":"l-dk-1-2"},{"id":"layer-cache","term":"Build cache","definition":"Stored results of earlier build steps. A step is reused when its instruction and inputs are unchanged; the first changed step and every step after it rebuild.","lesson":"l-dk-1-3"},{"id":"sigterm","term":"SIGTERM","definition":"The polite stop signal Docker and Kubernetes send to PID 1 before a forced SIGKILL. Apps should handle it by finishing in-flight work and exiting cleanly.","lesson":"l-dk-1-4"},{"id":"multi-stage-build","term":"Multi-stage build","definition":"A Dockerfile with several <code dir=\"ltr\">FROM</code> stages where only the final stage ships, so compilers, dev dependencies and sources stay in earlier stages and out of the production image.","lesson":"l-dk-2-1"},{"id":"dockerignore","term":".dockerignore","definition":"A file listing patterns to exclude from the build context, such as <code dir=\"ltr\">node_modules</code>, <code dir=\"ltr\">.git</code> and <code dir=\"ltr\">.env</code> files, which speeds up builds and keeps secrets out of images.","lesson":"l-dk-2-1"},{"id":"distroless","term":"Distroless image","definition":"A minimal base image containing only a language runtime and its libraries, with no shell or package manager, which shrinks the attack surface and the vulnerability report.","lesson":"l-dk-2-2"},{"id":"digest","term":"Image digest","definition":"A SHA-256 hash that identifies an image’s exact content. Unlike a tag, it can never point to different bytes, so pinning by digest guarantees every node runs the same image.","lesson":"l-dk-2-3"},{"id":"secret-mount","term":"Secret mount","definition":"A BuildKit feature, <code dir=\"ltr\">RUN --mount=type=secret</code>, that exposes a credential to one build step as a file or environment variable without writing it into any image layer.","lesson":"l-dk-2-4"},{"id":"compose-service","term":"Compose service","definition":"One component of a Compose application, such as <code dir=\"ltr\">api</code> or <code dir=\"ltr\">db</code>, defined in <code dir=\"ltr\">compose.yaml</code> and reachable from other services on the project network by its service name.","lesson":"l-dk-3-1"},{"id":"named-volume","term":"Named volume","definition":"Docker-managed storage that exists independently of containers, used for data such as database files that must survive container removal. <code dir=\"ltr\">docker compose down -v</code> deletes it.","lesson":"l-dk-3-2"},{"id":"healthcheck","term":"Healthcheck","definition":"A command Docker runs inside a container on a schedule to decide whether it is healthy. Compose can make other services wait for it with <code dir=\"ltr\">condition: service_healthy</code>.","lesson":"l-dk-3-3"},{"id":"compose-watch","term":"Compose watch","definition":"A development mode, started with <code dir=\"ltr\">docker compose up --watch</code>, that syncs edited files into running containers or rebuilds the image when files such as the lockfile change.","lesson":"l-dk-3-4"},{"id":"control-loop","term":"Control loop","definition":"The pattern behind Kubernetes controllers: continuously observe actual state, compare it with the desired state stored in the API, and act to close the difference.","lesson":"l-dk-4-1"},{"id":"pod","term":"Pod","definition":"The smallest unit Kubernetes schedules: one or more containers that share a network identity and volumes. Pods are disposable and replaced, not repaired.","lesson":"l-dk-4-2"},{"id":"deployment","term":"Deployment","definition":"A Kubernetes object that keeps a set of identical Pods running through ReplicaSets and replaces them gradually when the Pod template changes.","lesson":"l-dk-4-2"},{"id":"label-selector","term":"Label selector","definition":"A query over key-value labels that Kubernetes objects use to find each other, for example a Service selecting the Pods it sends traffic to.","lesson":"l-dk-4-2"},{"id":"service","term":"Service","definition":"A stable virtual IP and DNS name in front of a changing set of ready Pods chosen by a label selector. Types include ClusterIP, NodePort and LoadBalancer.","lesson":"l-dk-4-3"},{"id":"configmap-secret","term":"ConfigMap and Secret","definition":"Objects that hold configuration for Pods, injected as environment variables or files. Secrets are for credentials but are only base64-encoded, so access control matters.","lesson":"l-dk-4-4"},{"id":"namespace","term":"Namespace","definition":"A Kubernetes scope for object names, permissions and quotas, typically one per app or team. It does not block network traffic without a NetworkPolicy.","lesson":"l-dk-4-4"},{"id":"readiness-probe","term":"Readiness probe","definition":"A periodic check that decides whether a Pod should receive traffic. Failing removes the Pod from Service endpoints without restarting it, and it gates rolling updates.","lesson":"l-dk-5-1"},{"id":"liveness-probe","term":"Liveness probe","definition":"A periodic check that decides whether a container is stuck. Failing restarts the container, so it must not depend on databases or other services.","lesson":"l-dk-5-1"},{"id":"requests-limits","term":"Requests and limits","definition":"Per-container resource settings: requests are what the scheduler reserves, limits are enforced ceilings. Exceeding a CPU limit throttles; exceeding a memory limit triggers an OOM kill.","lesson":"l-dk-5-2"},{"id":"hpa","term":"HorizontalPodAutoscaler","definition":"A controller (<code dir=\"ltr\">autoscaling/v2</code>) that adjusts a workload’s replica count to keep a metric, such as average CPU utilization relative to requests, near a target.","lesson":"l-dk-5-2"},{"id":"rolling-update","term":"Rolling update","definition":"The default Deployment strategy: a new ReplicaSet scales up while the old one scales down, paced by <code dir=\"ltr\">maxSurge</code> and <code dir=\"ltr\">maxUnavailable</code> and gated by readiness.","lesson":"l-dk-5-3"},{"id":"helm-chart","term":"Helm chart","definition":"A package of templated Kubernetes manifests plus default values. <code dir=\"ltr\">helm upgrade --install</code> deploys it as a numbered release revision that <code dir=\"ltr\">helm rollback</code> can return to.","lesson":"l-dk-5-3"},{"id":"gateway-api","term":"Gateway API","definition":"The Kubernetes successor to Ingress for routing external traffic, split into GatewayClass, Gateway and HTTPRoute resources so platform and app teams each own their part.","lesson":"l-dk-5-4"}]}