[{"data":1,"prerenderedAt":10763},["ShallowReactive",2],{"lesson-course-meta-kubernetes-for-web-applications":3,"lesson-kubernetes-for-web-applications-day-28":85,"sidebar-kubernetes-for-web-applications":776},[4,36,63],{"id":5,"title":6,"color":7,"description":8,"extension":9,"lessons":10,"meta":11,"order":12,"slug":13,"stem":14,"weeks":15,"__hash__":35},"courses\u002Fcourses\u002Fagentic-coding.yml","30 Days of Agentic Coding","#00E5CC","A structured series on building agent systems — from mental models to production.","yml",30,{},1,"agentic-coding","courses\u002Fagentic-coding",[16,20,25,30],{"number":12,"name":17,"subtitle":18,"color":19},"Foundations","What Is an Agent?","#3B82F6",{"number":21,"name":22,"subtitle":23,"color":24},2,"Architecture","Designing Agent Systems","#10B981",{"number":26,"name":27,"subtitle":28,"color":29},3,"Implementation","Building Real Agents","#8B5CF6",{"number":31,"name":32,"subtitle":33,"color":34},4,"Production","Advanced & Real-World","#FF006E","yAbNKWI2IummYlFcshKTZwQIjk9jKNlOt5zmEpvtmek",{"id":37,"title":38,"color":39,"description":40,"extension":9,"lessons":10,"meta":41,"order":26,"slug":42,"stem":43,"weeks":44,"__hash__":62},"courses\u002Fcourses\u002Fkubernetes-for-web-applications.yml","Kubernetes for Web Applications","#326CE5","A practical 30-day path to package, deploy, operate, secure, and evolve Node.js and Nuxt applications on Kubernetes.",{},"kubernetes-for-web-applications","courses\u002Fkubernetes-for-web-applications",[45,48,51,54,57],{"number":12,"name":46,"subtitle":47,"color":19},"Container Foundations","From Processes to Images",{"number":21,"name":49,"subtitle":50,"color":24},"Kubernetes Fundamentals","From Local Containers to a Cluster",{"number":26,"name":52,"subtitle":53,"color":29},"Shipping Web Applications","Networking, Configuration & Capacity",{"number":31,"name":55,"subtitle":56,"color":34},"Reliable Operations","Health, Delivery & Security",{"number":58,"name":59,"subtitle":60,"color":61},5,"Platform Blueprint","Architecture Decisions & Capstone","#F59E0B","i_RPm-rwLOy9m3y2Lw8-XLwsT6BgKj9EO1pwGl7wQC8",{"id":64,"title":65,"color":66,"description":67,"extension":9,"lessons":10,"meta":68,"order":21,"slug":69,"stem":70,"weeks":71,"__hash__":84},"courses\u002Fcourses\u002Fsystems-design-ai-native.yml","Systems Design for AI-Native Apps","#6366F1","Master the architectural patterns, trade-offs, and engineering practices to build reliable, scalable, production-grade AI-native applications.",{},"systems-design-ai-native","courses\u002Fsystems-design-ai-native",[72,74,77,80],{"number":12,"name":17,"subtitle":73,"color":66},"Thinking in AI-Native",{"number":21,"name":75,"subtitle":76,"color":24},"Data & Context","Data & Context Management",{"number":26,"name":78,"subtitle":79,"color":61},"Reliability","Reliability, Observability & Cost",{"number":31,"name":81,"subtitle":82,"color":83},"Multi-Agent & Production","Multi-Agent Patterns & Production","#EF4444","IG9z5-jTVCYeX2_y9cFDIGvBVYfApIURmtKiXnRqpjM",{"id":86,"title":87,"body":88,"day":767,"description":768,"extension":769,"meta":770,"navigation":147,"path":771,"seo":772,"stem":773,"tag":774,"week":58,"weekName":59,"__hash__":775},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-28.md","Common failure modes and anti-patterns",{"type":89,"value":90,"toc":759},"minimark",[91,96,100,105,108,111,453,460,464,467,531,534,537,541,544,547,693,696,700,703,706,717,720,724,731,735,755],[92,93,95],"h1",{"id":94},"day-28-common-failure-modes-and-anti-patterns","Day 28 — Common failure modes and anti-patterns",[97,98,99],"p",{},"Kubernetes failures are often design failures expressed through YAML. Senior engineers create more leverage by removing unnecessary complexity and clarifying ownership than by memorizing another controller. The recurring anti-patterns below are signals that the platform is compensating for weak application or organizational boundaries.",[101,102,104],"h2",{"id":103},"_1-treating-a-pod-like-a-long-lived-vm","1. Treating a Pod like a long-lived VM",[97,106,107],{},"A Pod is replaceable. Its name, IP, writable container filesystem, and node placement are temporary. SSH-style patching, writing important files beside the application, or depending on one replica’s local memory fights the reconciliation model.",[97,109,110],{},"A Node service should externalize durable state and terminate cleanly:",[112,113,118],"pre",{"className":114,"code":115,"language":116,"meta":117,"style":117},"language-ts shiki shiki-themes github-dark","import http from \"node:http\";\n\nlet ready = true;\nconst server = http.createServer(async (req, res) => {\n  if (req.url === \"\u002Fready\") {\n    res.writeHead(ready ? 200 : 503).end();\n    return;\n  }\n  \u002F\u002F Handle request without assuming this replica owns durable session state.\n  res.writeHead(200).end(\"ok\");\n});\n\nserver.listen(3000);\n\nprocess.on(\"SIGTERM\", () => {\n  ready = false;\n  server.close((error) => process.exit(error ? 1 : 0));\n  setTimeout(() => process.exit(1), 25_000).unref();\n});\n","ts","",[119,120,121,143,149,166,212,229,262,270,276,283,308,314,319,335,340,361,374,415,448],"code",{"__ignoreMap":117},[122,123,125,129,133,136,140],"span",{"class":124,"line":12},"line",[122,126,128],{"class":127},"snl16","import",[122,130,132],{"class":131},"s95oV"," http ",[122,134,135],{"class":127},"from",[122,137,139],{"class":138},"sU2Wk"," \"node:http\"",[122,141,142],{"class":131},";\n",[122,144,145],{"class":124,"line":21},[122,146,148],{"emptyLinePlaceholder":147},true,"\n",[122,150,151,154,157,160,164],{"class":124,"line":26},[122,152,153],{"class":127},"let",[122,155,156],{"class":131}," ready ",[122,158,159],{"class":127},"=",[122,161,163],{"class":162},"sDLfK"," true",[122,165,142],{"class":131},[122,167,168,171,174,177,180,184,187,190,193,197,200,203,206,209],{"class":124,"line":31},[122,169,170],{"class":127},"const",[122,172,173],{"class":162}," server",[122,175,176],{"class":127}," =",[122,178,179],{"class":131}," http.",[122,181,183],{"class":182},"svObZ","createServer",[122,185,186],{"class":131},"(",[122,188,189],{"class":127},"async",[122,191,192],{"class":131}," (",[122,194,196],{"class":195},"s9osk","req",[122,198,199],{"class":131},", ",[122,201,202],{"class":195},"res",[122,204,205],{"class":131},") ",[122,207,208],{"class":127},"=>",[122,210,211],{"class":131}," {\n",[122,213,214,217,220,223,226],{"class":124,"line":58},[122,215,216],{"class":127},"  if",[122,218,219],{"class":131}," (req.url ",[122,221,222],{"class":127},"===",[122,224,225],{"class":138}," \"\u002Fready\"",[122,227,228],{"class":131},") {\n",[122,230,232,235,238,241,244,247,250,253,256,259],{"class":124,"line":231},6,[122,233,234],{"class":131},"    res.",[122,236,237],{"class":182},"writeHead",[122,239,240],{"class":131},"(ready ",[122,242,243],{"class":127},"?",[122,245,246],{"class":162}," 200",[122,248,249],{"class":127}," :",[122,251,252],{"class":162}," 503",[122,254,255],{"class":131},").",[122,257,258],{"class":182},"end",[122,260,261],{"class":131},"();\n",[122,263,265,268],{"class":124,"line":264},7,[122,266,267],{"class":127},"    return",[122,269,142],{"class":131},[122,271,273],{"class":124,"line":272},8,[122,274,275],{"class":131},"  }\n",[122,277,279],{"class":124,"line":278},9,[122,280,282],{"class":281},"sAwPA","  \u002F\u002F Handle request without assuming this replica owns durable session state.\n",[122,284,286,289,291,293,296,298,300,302,305],{"class":124,"line":285},10,[122,287,288],{"class":131},"  res.",[122,290,237],{"class":182},[122,292,186],{"class":131},[122,294,295],{"class":162},"200",[122,297,255],{"class":131},[122,299,258],{"class":182},[122,301,186],{"class":131},[122,303,304],{"class":138},"\"ok\"",[122,306,307],{"class":131},");\n",[122,309,311],{"class":124,"line":310},11,[122,312,313],{"class":131},"});\n",[122,315,317],{"class":124,"line":316},12,[122,318,148],{"emptyLinePlaceholder":147},[122,320,322,325,328,330,333],{"class":124,"line":321},13,[122,323,324],{"class":131},"server.",[122,326,327],{"class":182},"listen",[122,329,186],{"class":131},[122,331,332],{"class":162},"3000",[122,334,307],{"class":131},[122,336,338],{"class":124,"line":337},14,[122,339,148],{"emptyLinePlaceholder":147},[122,341,343,346,349,351,354,357,359],{"class":124,"line":342},15,[122,344,345],{"class":131},"process.",[122,347,348],{"class":182},"on",[122,350,186],{"class":131},[122,352,353],{"class":138},"\"SIGTERM\"",[122,355,356],{"class":131},", () ",[122,358,208],{"class":127},[122,360,211],{"class":131},[122,362,364,367,369,372],{"class":124,"line":363},16,[122,365,366],{"class":131},"  ready ",[122,368,159],{"class":127},[122,370,371],{"class":162}," false",[122,373,142],{"class":131},[122,375,377,380,383,386,389,391,393,396,399,402,404,407,409,412],{"class":124,"line":376},17,[122,378,379],{"class":131},"  server.",[122,381,382],{"class":182},"close",[122,384,385],{"class":131},"((",[122,387,388],{"class":195},"error",[122,390,205],{"class":131},[122,392,208],{"class":127},[122,394,395],{"class":131}," process.",[122,397,398],{"class":182},"exit",[122,400,401],{"class":131},"(error ",[122,403,243],{"class":127},[122,405,406],{"class":162}," 1",[122,408,249],{"class":127},[122,410,411],{"class":162}," 0",[122,413,414],{"class":131},"));\n",[122,416,418,421,424,426,428,430,432,435,438,441,443,446],{"class":124,"line":417},18,[122,419,420],{"class":182},"  setTimeout",[122,422,423],{"class":131},"(() ",[122,425,208],{"class":127},[122,427,395],{"class":131},[122,429,398],{"class":182},[122,431,186],{"class":131},[122,433,434],{"class":162},"1",[122,436,437],{"class":131},"), ",[122,439,440],{"class":162},"25_000",[122,442,255],{"class":131},[122,444,445],{"class":182},"unref",[122,447,261],{"class":131},[122,449,451],{"class":124,"line":450},19,[122,452,313],{"class":131},[97,454,455,456,459],{},"Use a PersistentVolume only when the workload genuinely needs filesystem semantics. For uploads, object storage is usually easier to scale and back up. For sessions, use signed cookies or an external store. Debug through logs, metrics, traces, ",[119,457,458],{},"kubectl debug",", and reproducible images—not manual mutation.",[101,461,463],{"id":462},"_2-confusing-encoded-configuration-with-secret-management","2. Confusing encoded configuration with secret management",[97,465,466],{},"A Kubernetes Secret is base64-encoded and access-controlled through the API; it is not automatically encrypted end to end, safe to commit, or invisible to a process that receives it. This is unsafe:",[112,468,472],{"className":469,"code":470,"language":471,"meta":117,"style":117},"language-yaml shiki shiki-themes github-dark","apiVersion: v1\nkind: Secret\nmetadata:\n  name: production-db\nstringData:\n  DATABASE_URL: postgres:\u002F\u002Fadmin:password@db\u002Fprod\n","yaml",[119,473,474,486,496,504,514,521],{"__ignoreMap":117},[122,475,476,480,483],{"class":124,"line":12},[122,477,479],{"class":478},"s4JwU","apiVersion",[122,481,482],{"class":131},": ",[122,484,485],{"class":138},"v1\n",[122,487,488,491,493],{"class":124,"line":21},[122,489,490],{"class":478},"kind",[122,492,482],{"class":131},[122,494,495],{"class":138},"Secret\n",[122,497,498,501],{"class":124,"line":26},[122,499,500],{"class":478},"metadata",[122,502,503],{"class":131},":\n",[122,505,506,509,511],{"class":124,"line":31},[122,507,508],{"class":478},"  name",[122,510,482],{"class":131},[122,512,513],{"class":138},"production-db\n",[122,515,516,519],{"class":124,"line":58},[122,517,518],{"class":478},"stringData",[122,520,503],{"class":131},[122,522,523,526,528],{"class":124,"line":231},[122,524,525],{"class":478},"  DATABASE_URL",[122,527,482],{"class":131},[122,529,530],{"class":138},"postgres:\u002F\u002Fadmin:password@db\u002Fprod\n",[97,532,533],{},"The syntax is valid; committing it to Git is not. Prefer an external secret manager with workload identity and a controller or CSI integration. If encrypted secrets are stored in Git, define who can decrypt them, how keys rotate, and how access is audited. Limit Secret reads with namespace-scoped RBAC, avoid exposing values in command-line arguments and logs, and restart or reload workloads intentionally when values rotate.",[97,535,536],{},"Also keep ordinary configuration disciplined. A giant shared ConfigMap couples unrelated services and creates accidental blast radius. Give each application a small, validated configuration contract.",[101,538,540],{"id":539},"_3-splitting-into-microservices-without-independent-reasons","3. Splitting into microservices without independent reasons",[97,542,543],{},"Kubernetes makes deploying many services possible; it does not make distributed systems cheap. Each new service introduces versioning, network failure, authentication, observability, deployment coordination, on-call ownership, and data consistency problems.",[97,545,546],{},"A modular Node monolith is often a stronger starting point:",[112,548,550],{"className":114,"code":549,"language":116,"meta":117,"style":117},"\u002F\u002F Explicit internal boundaries without a network hop.\nexport interface BillingPort {\n  createInvoice(orderId: string): Promise\u003C{ invoiceId: string }>;\n}\n\nexport class CheckoutService {\n  constructor(private readonly billing: BillingPort) {}\n  async complete(orderId: string) {\n    return this.billing.createInvoice(orderId);\n  }\n}\n",[119,551,552,557,570,607,612,616,628,651,669,685,689],{"__ignoreMap":117},[122,553,554],{"class":124,"line":12},[122,555,556],{"class":281},"\u002F\u002F Explicit internal boundaries without a network hop.\n",[122,558,559,562,565,568],{"class":124,"line":21},[122,560,561],{"class":127},"export",[122,563,564],{"class":127}," interface",[122,566,567],{"class":182}," BillingPort",[122,569,211],{"class":131},[122,571,572,575,577,580,583,586,589,591,594,597,600,602,604],{"class":124,"line":26},[122,573,574],{"class":182},"  createInvoice",[122,576,186],{"class":131},[122,578,579],{"class":195},"orderId",[122,581,582],{"class":127},":",[122,584,585],{"class":162}," string",[122,587,588],{"class":131},")",[122,590,582],{"class":127},[122,592,593],{"class":182}," Promise",[122,595,596],{"class":131},"\u003C{ ",[122,598,599],{"class":195},"invoiceId",[122,601,582],{"class":127},[122,603,585],{"class":162},[122,605,606],{"class":131}," }>;\n",[122,608,609],{"class":124,"line":31},[122,610,611],{"class":131},"}\n",[122,613,614],{"class":124,"line":58},[122,615,148],{"emptyLinePlaceholder":147},[122,617,618,620,623,626],{"class":124,"line":231},[122,619,561],{"class":127},[122,621,622],{"class":127}," class",[122,624,625],{"class":182}," CheckoutService",[122,627,211],{"class":131},[122,629,630,633,635,638,641,644,646,648],{"class":124,"line":264},[122,631,632],{"class":127},"  constructor",[122,634,186],{"class":131},[122,636,637],{"class":127},"private",[122,639,640],{"class":127}," readonly",[122,642,643],{"class":195}," billing",[122,645,582],{"class":127},[122,647,567],{"class":182},[122,649,650],{"class":131},") {}\n",[122,652,653,656,659,661,663,665,667],{"class":124,"line":272},[122,654,655],{"class":127},"  async",[122,657,658],{"class":182}," complete",[122,660,186],{"class":131},[122,662,579],{"class":195},[122,664,582],{"class":127},[122,666,585],{"class":162},[122,668,228],{"class":131},[122,670,671,673,676,679,682],{"class":124,"line":278},[122,672,267],{"class":127},[122,674,675],{"class":162}," this",[122,677,678],{"class":131},".billing.",[122,680,681],{"class":182},"createInvoice",[122,683,684],{"class":131},"(orderId);\n",[122,686,687],{"class":124,"line":285},[122,688,275],{"class":131},[122,690,691],{"class":124,"line":310},[122,692,611],{"class":131},[97,694,695],{},"Extract a service when there is evidence: independent scaling, distinct security isolation, a separate release cadence, specialized runtime needs, or clear team ownership. “We use Kubernetes” is not evidence. A queue-backed worker can be a useful separate workload while still sharing a repository and release process.",[101,697,699],{"id":698},"_4-running-stateful-systems-without-accepting-operational-ownership","4. Running stateful systems without accepting operational ownership",[97,701,702],{},"A database Deployment plus a PVC is not a database service. Production ownership includes replication, point-in-time recovery, tested restores, upgrades, failover, capacity, encryption, monitoring, and an on-call response. Operators automate procedures, but they do not supply missing expertise or accountability.",[97,704,705],{},"Use managed databases and queues by default for a small product unless cost, regulation, performance, or specialized capability justifies in-cluster ownership. If you do run stateful software, define recovery point and recovery time objectives, schedule restore drills, use Pod disruption and topology controls, and understand storage-zone behavior.",[97,707,708,709,712,713,716],{},"Other warning signs deserve the same scrutiny: privileged containers used to avoid fixing permissions; unlimited CPU\u002Fmemory; ",[119,710,711],{},"latest"," image tags; broad ",[119,714,715],{},"cluster-admin"," access; one giant namespace; liveness probes that call every dependency and trigger restart storms; and Helm templates so abstract that nobody can predict the rendered manifests.",[97,718,719],{},"Complexity needs a budget. Every controller, custom resource, policy engine, and service mesh should have an owner, upgrade path, failure model, and measurable benefit.",[101,721,723],{"id":722},"try-this-today","Try this today",[97,725,726,727,730],{},"Audit one existing web application or a representative design. Make four columns: “VM assumption,” “secret exposure,” “unnecessary distribution,” and “stateful ownership.” Add every concrete example you find, then choose one high-leverage correction. For a local exercise, run the Node server above in a Deployment with two replicas, send requests continuously, and delete one Pod. Confirm traffic continues and observe graceful termination during ",[119,728,729],{},"kubectl rollout restart deployment\u002F\u003Cname>",". Record any state or shutdown assumption that breaks.",[101,732,734],{"id":733},"resources","Resources",[736,737,738,748],"ul",{},[739,740,741],"li",{},[742,743,747],"a",{"href":744,"rel":745},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fconfiguration\u002Foverview\u002F",[746],"nofollow","Kubernetes: Configuration best practices",[739,749,750],{},[742,751,754],{"href":752,"rel":753},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fsetup\u002Fbest-practices\u002Fcluster-large\u002F",[746],"Kubernetes: Considerations for large clusters",[756,757,758],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":117,"searchDepth":21,"depth":21,"links":760},[761,762,763,764,765,766],{"id":103,"depth":21,"text":104},{"id":462,"depth":21,"text":463},{"id":539,"depth":21,"text":540},{"id":698,"depth":21,"text":699},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},28,"Kubernetes failures are often design failures expressed through YAML. Senior engineers create more leverage by removing unnecessary complexity and…","md",{},"\u002Fkubernetes-for-web-applications\u002Fday-28",{"title":87,"description":768},"kubernetes-for-web-applications\u002Fday-28","Kubernetes","V6UPNskwuXTRsPaOdSmYml9QqF39lsq9ddvWlkYqlmA",[777,1090,1211,1349,1480,1724,1961,2313,2414,2641,2957,3317,3648,3862,4067,4271,4656,4881,5147,5363,5468,6232,6644,7018,7590,8151,8785,9214,9699,10321],{"id":778,"title":779,"body":780,"day":12,"description":1084,"extension":769,"meta":1085,"navigation":147,"path":1086,"seo":1087,"stem":1088,"tag":774,"week":12,"weekName":46,"__hash__":1089},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-01.md","From processes to infrastructure abstractions",{"type":89,"value":781,"toc":1074},[782,786,789,793,800,803,981,984,988,991,994,998,1001,1004,1008,1011,1014,1019,1022,1025,1027,1053,1055,1071],[92,783,785],{"id":784},"day-1-from-processes-to-infrastructure-abstractions","Day 1 — From processes to infrastructure abstractions",[97,787,788],{},"A web service is ultimately a process reading files, allocating memory, and exchanging bytes over a network. For a senior engineer, the useful infrastructure model begins there: every higher-level platform is an opinionated way to allocate, isolate, connect, and replace those basic resources.",[101,790,792],{"id":791},"a-process-is-the-real-unit-of-execution","A process is the real unit of execution",[97,794,795,796,799],{},"When ",[119,797,798],{},"node server.js"," starts, the operating system creates a process with an identity, virtual memory, file descriptors, environment variables, and one or more threads. The process sees a filesystem tree and network interfaces supplied by its host. It does not inherently know whether the host is a laptop, VM, container, or Kubernetes node.",[97,801,802],{},"This makes application behavior the right starting point for operations. A Node HTTP server should bind to a configurable address and port, write logs to standard output, and handle termination:",[112,804,808],{"className":805,"code":806,"language":807,"meta":117,"style":117},"language-js shiki shiki-themes github-dark","import http from 'node:http';\n\nconst port = Number(process.env.PORT ?? 3000);\nconst server = http.createServer((req, res) => {\n  res.writeHead(200, { 'content-type': 'application\u002Fjson' });\n  res.end(JSON.stringify({ pid: process.pid, path: req.url }));\n});\n\nserver.listen(port, '0.0.0.0');\nprocess.on('SIGTERM', () => server.close(() => process.exit(0)));\n","js",[119,809,810,823,827,853,879,903,923,927,931,945],{"__ignoreMap":117},[122,811,812,814,816,818,821],{"class":124,"line":12},[122,813,128],{"class":127},[122,815,132],{"class":131},[122,817,135],{"class":127},[122,819,820],{"class":138}," 'node:http'",[122,822,142],{"class":131},[122,824,825],{"class":124,"line":21},[122,826,148],{"emptyLinePlaceholder":147},[122,828,829,831,834,836,839,842,845,848,851],{"class":124,"line":26},[122,830,170],{"class":127},[122,832,833],{"class":162}," port",[122,835,176],{"class":127},[122,837,838],{"class":182}," Number",[122,840,841],{"class":131},"(process.env.",[122,843,844],{"class":162},"PORT",[122,846,847],{"class":127}," ??",[122,849,850],{"class":162}," 3000",[122,852,307],{"class":131},[122,854,855,857,859,861,863,865,867,869,871,873,875,877],{"class":124,"line":31},[122,856,170],{"class":127},[122,858,173],{"class":162},[122,860,176],{"class":127},[122,862,179],{"class":131},[122,864,183],{"class":182},[122,866,385],{"class":131},[122,868,196],{"class":195},[122,870,199],{"class":131},[122,872,202],{"class":195},[122,874,205],{"class":131},[122,876,208],{"class":127},[122,878,211],{"class":131},[122,880,881,883,885,887,889,892,895,897,900],{"class":124,"line":58},[122,882,288],{"class":131},[122,884,237],{"class":182},[122,886,186],{"class":131},[122,888,295],{"class":162},[122,890,891],{"class":131},", { ",[122,893,894],{"class":138},"'content-type'",[122,896,482],{"class":131},[122,898,899],{"class":138},"'application\u002Fjson'",[122,901,902],{"class":131}," });\n",[122,904,905,907,909,911,914,917,920],{"class":124,"line":231},[122,906,288],{"class":131},[122,908,258],{"class":182},[122,910,186],{"class":131},[122,912,913],{"class":162},"JSON",[122,915,916],{"class":131},".",[122,918,919],{"class":182},"stringify",[122,921,922],{"class":131},"({ pid: process.pid, path: req.url }));\n",[122,924,925],{"class":124,"line":264},[122,926,313],{"class":131},[122,928,929],{"class":124,"line":272},[122,930,148],{"emptyLinePlaceholder":147},[122,932,933,935,937,940,943],{"class":124,"line":278},[122,934,324],{"class":131},[122,936,327],{"class":182},[122,938,939],{"class":131},"(port, ",[122,941,942],{"class":138},"'0.0.0.0'",[122,944,307],{"class":131},[122,946,947,949,951,953,956,958,960,963,965,967,969,971,973,975,978],{"class":124,"line":285},[122,948,345],{"class":131},[122,950,348],{"class":182},[122,952,186],{"class":131},[122,954,955],{"class":138},"'SIGTERM'",[122,957,356],{"class":131},[122,959,208],{"class":127},[122,961,962],{"class":131}," server.",[122,964,382],{"class":182},[122,966,423],{"class":131},[122,968,208],{"class":127},[122,970,395],{"class":131},[122,972,398],{"class":182},[122,974,186],{"class":131},[122,976,977],{"class":162},"0",[122,979,980],{"class":131},")));\n",[97,982,983],{},"The application contract remains stable while infrastructure changes around it.",[101,985,987],{"id":986},"isolation-creates-reliable-boundaries","Isolation creates reliable boundaries",[97,989,990],{},"Two processes on one machine can otherwise compete for CPU and memory, collide when they try to bind the same port, read each other’s files, or inherit incompatible library versions. Isolation reduces this interference. It gives a workload its own apparent filesystem, process tree, network namespace, and resource budget while retaining controlled paths for communication.",[97,992,993],{},"Isolation is not absolute security. Containers share a host kernel, and configuration mistakes can weaken boundaries. Treat isolation as layered risk reduction: use unprivileged processes, narrow filesystem permissions, explicit resource controls, and network policy rather than assuming the container boundary solves everything.",[101,995,997],{"id":996},"servers-vms-containers-and-managed-platforms","Servers, VMs, containers, and managed platforms",[97,999,1000],{},"A physical server provides maximum hardware control but has slow provisioning and coarse utilization. A VM virtualizes hardware and runs a complete guest OS, creating a strong, heavyweight boundary. A container packages application files and metadata while using the host kernel; it starts quickly and supports high workload density. A managed application platform goes further by hiding much of the machine and scheduler lifecycle behind a deployment API.",[97,1002,1003],{},"These are composable layers, not mutually exclusive products. A managed Kubernetes cluster commonly runs containers inside VMs on physical cloud hosts. Choose at the highest abstraction that preserves the control you actually need. If a managed container service satisfies networking, scaling, and compliance requirements, Kubernetes may add cost without product value.",[101,1005,1007],{"id":1006},"the-operational-promise-of-containers","The operational promise of containers",[97,1009,1010],{},"Containers address build-to-runtime drift by distributing an immutable image containing the application and its runtime dependencies. They also standardize lifecycle operations: start a process, attach networking and storage, collect output, send signals, and remove the instance. This makes deployment more repeatable, but it does not make the application reliable by itself.",[97,1012,1013],{},"The image must still be correctly built, secrets must arrive at runtime, persistent data must live outside the container’s writable layer, and operators need health signals. Kubernetes builds on this contract by continuously creating and replacing container instances to match declared intent.",[1015,1016,1018],"h3",{"id":1017},"turn-runtime-facts-into-an-application-contract","Turn runtime facts into an application contract",[97,1020,1021],{},"Before choosing infrastructure, write down the service’s minimum operating contract. Include the command, listening port, startup duration, memory behavior, writable paths, outbound dependencies, shutdown deadline, and evidence emitted during failure. For example, a service that stores sessions in memory is not horizontally replaceable, while one that writes uploads beside its source cannot tolerate an immutable filesystem. These are application constraints, not platform defects.",[97,1023,1024],{},"This contract also improves design reviews. Instead of debating whether containers are “portable,” verify whether the image supports the target CPU, whether configuration is external, whether state survives replacement, and whether the process works behind a proxy. Kubernetes later expresses these requirements as Pod settings, probes, resources, Services, configuration objects, and storage choices.",[101,1026,723],{"id":722},[97,1028,1029,1030,1033,1034,1037,1038,199,1041,1044,1045,1048,1049,1052],{},"Save the example as ",[119,1031,1032],{},"server.mjs",", run it with ",[119,1035,1036],{},"PORT=4000 node server.mjs",", and inspect it with ",[119,1039,1040],{},"ps",[119,1042,1043],{},"lsof -i :4000",", and ",[119,1046,1047],{},"curl localhost:4000\u002Ftest",". Send ",[119,1050,1051],{},"kill -TERM \u003Cpid>"," and confirm that it exits cleanly. Write down which application assumptions concern processes, files, network, configuration, and shutdown; these become deployment requirements later.",[101,1054,734],{"id":733},[736,1056,1057,1064],{},[739,1058,1059],{},[742,1060,1063],{"href":1061,"rel":1062},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Fprocess.html",[746],"Node.js process documentation",[739,1065,1066],{},[742,1067,1070],{"href":1068,"rel":1069},"https:\u002F\u002Fgithub.com\u002Fopencontainers\u002Fruntime-spec",[746],"OCI runtime specification",[756,1072,1073],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":1075},[1076,1077,1078,1079,1082,1083],{"id":791,"depth":21,"text":792},{"id":986,"depth":21,"text":987},{"id":996,"depth":21,"text":997},{"id":1006,"depth":21,"text":1007,"children":1080},[1081],{"id":1017,"depth":26,"text":1018},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A web service is ultimately a process reading files, allocating memory, and exchanging bytes over a network. For a senior engineer, the useful…",{},"\u002Fkubernetes-for-web-applications\u002Fday-01",{"title":779,"description":1084},"kubernetes-for-web-applications\u002Fday-01","kZH5I1_T18bcmaUYz9c9qRuqBQXoU4ExXWzlHQzWMnY",{"id":1091,"title":1092,"body":1093,"day":21,"description":1205,"extension":769,"meta":1206,"navigation":147,"path":1207,"seo":1208,"stem":1209,"tag":774,"week":12,"weekName":46,"__hash__":1210},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-02.md","Virtual machines: the right amount of knowledge",{"type":89,"value":1094,"toc":1195},[1095,1099,1102,1106,1109,1112,1116,1119,1122,1126,1129,1132,1136,1139,1142,1146,1149,1156,1158,1177,1179],[92,1096,1098],{"id":1097},"day-2-virtual-machines-the-right-amount-of-knowledge","Day 2 — Virtual machines: the right amount of knowledge",[97,1100,1101],{},"You do not need to administer hypervisors to operate web apps on Kubernetes, but you should understand the boundary beneath the cluster. VM behavior explains node capacity, failure domains, startup time, and why a “managed” cluster still exposes infrastructure decisions.",[101,1103,1105],{"id":1104},"hypervisors-and-guest-operating-systems","Hypervisors and guest operating systems",[97,1107,1108],{},"A hypervisor presents virtual CPU, memory, storage, and network devices to a guest operating system. Type 1 hypervisors run directly on server hardware; hosted hypervisors run through another OS. In both cases, the guest boots its own kernel and manages its own processes, users, packages, and network stack.",[97,1110,1111],{},"A VM image is a bootable disk template, not the same artifact as a container image. It usually includes an entire operating system and may be customized at startup with cloud-init. Provisioning a VM therefore involves selecting hardware shape, attaching disks and network interfaces, booting the guest, and configuring services. That lifecycle is measured in seconds or minutes rather than the milliseconds or seconds typical for containers.",[101,1113,1115],{"id":1114},"vm-and-container-isolation-differ","VM and container isolation differ",[97,1117,1118],{},"VMs isolate through virtual hardware and separate kernels. A kernel failure or exploit inside one guest normally remains separated from another guest. Containers isolate processes through facilities provided by a shared host kernel. They package far less and start faster, but their security boundary depends more directly on that kernel and runtime configuration.",[97,1120,1121],{},"The comparison is about trade-offs, not superiority. VMs are useful boundaries for different tenants, security zones, kernels, and operating systems. Containers are effective units for packaging and scheduling application processes. Many platforms deliberately combine them: VMs supply a durable infrastructure boundary, while containers provide application-level density and consistency.",[101,1123,1125],{"id":1124},"kubernetes-nodes-are-usually-vms","Kubernetes nodes are usually VMs",[97,1127,1128],{},"In a managed cluster, each worker node is often a cloud VM with a fixed CPU and memory shape. Kubernetes schedules Pods onto the allocatable capacity of those nodes. A Pod request does not create hardware; it reserves part of a node that already exists. If no existing node fits, a cluster autoscaler may request another VM, whose boot time becomes part of application scale-up latency.",[97,1130,1131],{},"Node loss also becomes a web-app concern. Kubernetes can recreate a Pod on another node, but only if capacity exists and state is externalized. Spreading replicas across nodes and cloud availability zones reduces correlated failure. A Deployment with three replicas all placed on one VM is three process copies but only one infrastructure failure domain.",[101,1133,1135],{"id":1134},"virtual-networks-and-storage-survive-at-the-edges","Virtual networks and storage survive at the edges",[97,1137,1138],{},"Cloud VMs attach to virtual networks with subnets, routes, firewall rules, and security groups. Kubernetes adds Pod and Service networking, but traffic eventually crosses these cloud primitives. Likewise, a PersistentVolume often maps to a cloud block disk whose zone and attachment constraints affect where a Pod can run.",[97,1140,1141],{},"This layered model helps during incidents. If a Service has endpoints but clients still time out, investigate Kubernetes policy, node routes, cloud firewall rules, and load balancer health rather than treating “the network” as one component. If a stateful Pod is pending, check whether its disk can attach in the node’s zone.",[1015,1143,1145],{"id":1144},"design-for-node-replacement","Design for node replacement",[97,1147,1148],{},"Assume a node will disappear during upgrades, scaling, or hardware failure. Stateless replicas should start elsewhere from the same image and retrieve configuration without depending on node-local files. Give the scheduler enough spare capacity to place replacements, and use topology constraints when correlated node or zone loss matters to the service objective.",[97,1150,1151,1152,1155],{},"Node-local caches can still be valuable, but treat them as accelerators. Rebuilding a cache should affect latency, not correctness. Likewise, a ",[119,1153,1154],{},"hostPath"," volume couples a Pod to one node and is rarely appropriate for application data. This reasoning is more useful to an application engineer than memorizing hypervisor brands: it directly shapes whether a rollout, drain, or autoscaling event is routine.",[101,1157,723],{"id":722},[97,1159,1160,1161,199,1164,199,1167,199,1170,1044,1173,1176],{},"On any VM or cloud instance you can inspect safely, run ",[119,1162,1163],{},"uname -a",[119,1165,1166],{},"cat \u002Fproc\u002Fcpuinfo",[119,1168,1169],{},"free -h",[119,1171,1172],{},"ip route",[119,1174,1175],{},"lsblk",". Identify which outputs describe virtualized hardware and which describe the guest OS. Then sketch your likely Kubernetes stack as physical host → VM\u002Fnode → Pod → container process and mark where CPU, network, and disk limits are applied.",[101,1178,734],{"id":733},[736,1180,1181,1188],{},[739,1182,1183],{},[742,1184,1187],{"href":1185,"rel":1186},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Farchitecture\u002Fnodes\u002F",[746],"Kubernetes: Nodes",[739,1189,1190],{},[742,1191,1194],{"href":1192,"rel":1193},"https:\u002F\u002Fwww.redhat.com\u002Fen\u002Ftopics\u002Fvirtualization\u002Fwhat-is-a-hypervisor",[746],"Red Hat: What is a hypervisor?",{"title":117,"searchDepth":21,"depth":21,"links":1196},[1197,1198,1199,1200,1203,1204],{"id":1104,"depth":21,"text":1105},{"id":1114,"depth":21,"text":1115},{"id":1124,"depth":21,"text":1125},{"id":1134,"depth":21,"text":1135,"children":1201},[1202],{"id":1144,"depth":26,"text":1145},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"You do not need to administer hypervisors to operate web apps on Kubernetes, but you should understand the boundary beneath the cluster. VM behavior…",{},"\u002Fkubernetes-for-web-applications\u002Fday-02",{"title":1092,"description":1205},"kubernetes-for-web-applications\u002Fday-02","ukwNGddztMtpJ_U_Lp-MSGwMSFWblrV2n24HWfDQtL8",{"id":1212,"title":1213,"body":1214,"day":26,"description":1343,"extension":769,"meta":1344,"navigation":147,"path":1345,"seo":1346,"stem":1347,"tag":774,"week":12,"weekName":46,"__hash__":1348},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-03.md","Linux primitives behind containers",{"type":89,"value":1215,"toc":1333},[1216,1220,1223,1227,1230,1241,1245,1248,1255,1259,1262,1265,1269,1272,1275,1279,1282,1285,1287,1315,1317],[92,1217,1219],{"id":1218},"day-3-linux-primitives-behind-containers","Day 3 — Linux primitives behind containers",[97,1221,1222],{},"A container is not a miniature VM. It is a process whose view of the Linux system has been constrained and whose filesystem has been assembled from image layers; understanding that statement makes debugging and security decisions far less mysterious.",[101,1224,1226],{"id":1225},"namespaces-change-what-a-process-can-see","Namespaces change what a process can see",[97,1228,1229],{},"Linux namespaces provide separate views of system resources. A PID namespace gives a process its own process numbering; a network namespace supplies interfaces, routes, and ports; mount, UTS, IPC, user, and cgroup namespaces isolate other views. The process still executes through the host kernel.",[97,1231,1232,1233,1236,1237,1240],{},"This explains common surprises. ",[119,1234,1235],{},"localhost"," inside a container refers to that container’s network namespace, not the developer laptop or another Pod. PID 1 inside the namespace has special signal and child-reaping behavior, even though the process has a different PID on the host. A Node service should therefore listen on ",[119,1238,1239],{},"0.0.0.0",", avoid daemonizing, and respond correctly to termination signals.",[101,1242,1244],{"id":1243},"cgroups-control-resource-consumption","Cgroups control resource consumption",[97,1246,1247],{},"Control groups account for and constrain CPU, memory, process count, and other resources. A CPU limit generally throttles execution; a memory limit is a hard boundary that can cause an out-of-memory kill. These controls are behind Kubernetes resource requests and limits, although requests primarily influence scheduling while limits configure enforcement.",[97,1249,1250,1251,1254],{},"Node’s defaults can interact poorly with constrained memory because the runtime may infer limits differently across versions and environments. Set realistic Kubernetes resources, observe heap and resident memory, and use a deliberate ",[119,1252,1253],{},"--max-old-space-size"," only when measurements justify it. Application-level concurrency limits are also important: a cgroup cannot make an unbounded work queue healthy.",[101,1256,1258],{"id":1257},"capabilities-divide-root-privilege","Capabilities divide root privilege",[97,1260,1261],{},"Linux capabilities split traditionally all-powerful root operations into narrower permissions such as binding low ports or administering networks. Container runtimes can drop capabilities even when a process has user ID 0 inside its namespace. Production web services usually need no added capabilities.",[97,1263,1264],{},"Run Node as a non-root user, bind to an unprivileged port such as 3000, drop all capabilities where supported, and make the root filesystem read-only when the app permits it. A process that needs to write temporary data should use an explicitly mounted temporary directory rather than broad filesystem access.",[101,1266,1268],{"id":1267},"layered-filesystems-assemble-the-root-tree","Layered filesystems assemble the root tree",[97,1270,1271],{},"An image contains immutable filesystem layers. At runtime, the container receives a thin writable layer on top. Reads see a merged view; writes go to the ephemeral top layer. Deleting a file from a later image layer does not remove its bytes from earlier layers, which matters for secrets accidentally copied during a build.",[97,1273,1274],{},"The writable layer is not durable application storage. When Kubernetes replaces a Pod, that layer disappears. Logs should go to standard streams, uploaded files should go to object storage or a persistent volume, and mutable business data should live in an external data system.",[1015,1276,1278],{"id":1277},"the-primitives-compose-into-one-runtime-boundary","The primitives compose into one runtime boundary",[97,1280,1281],{},"A runtime creates namespaces, attaches cgroups, applies capabilities and seccomp policy, mounts an image root filesystem, then executes the configured process. Kubernetes does not replace these mechanisms; it describes how they should be configured and repeatedly asks the node runtime to realize them. This is why a Pod security setting can ultimately change kernel enforcement.",[97,1283,1284],{},"Debug at the correct layer. A process that cannot bind a port may lack a capability or may already have a listener in its network namespace. A write failure may come from Unix permissions, a read-only mount, or a missing volume. CPU latency may be cgroup throttling rather than Node computation alone. Connecting the symptom to the primitive produces a narrower, testable hypothesis.",[101,1286,723],{"id":722},[97,1288,1289,1290,1293,1294,199,1297,1044,1300,1303,1304,1306,1307,1310,1311,1314],{},"Run ",[119,1291,1292],{},"docker run --rm -it --memory=128m --cpus=0.5 node:22-bookworm-slim sh",". Inside it, inspect ",[119,1295,1296],{},"\u002Fproc\u002F1\u002Fstatus",[119,1298,1299],{},"\u002Fproc\u002Fself\u002Fmountinfo",[119,1301,1302],{},"\u002Fsys\u002Ffs\u002Fcgroup",", then run ",[119,1305,1040],{}," and ",[119,1308,1309],{},"ip addr"," if available. Compare those views with the host. Start a Node process that logs ",[119,1312,1313],{},"process.pid",", send it SIGTERM, and confirm how PID 1 and signal handling behave.",[101,1316,734],{"id":733},[736,1318,1319,1326],{},[739,1320,1321],{},[742,1322,1325],{"href":1323,"rel":1324},"https:\u002F\u002Fman7.org\u002Flinux\u002Fman-pages\u002Fman7\u002Fnamespaces.7.html",[746],"Linux namespaces manual",[739,1327,1328],{},[742,1329,1332],{"href":1330,"rel":1331},"https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Fcontainers\u002Fresource_constraints\u002F",[746],"Docker: Runtime resource constraints",{"title":117,"searchDepth":21,"depth":21,"links":1334},[1335,1336,1337,1338,1341,1342],{"id":1225,"depth":21,"text":1226},{"id":1243,"depth":21,"text":1244},{"id":1257,"depth":21,"text":1258},{"id":1267,"depth":21,"text":1268,"children":1339},[1340],{"id":1277,"depth":26,"text":1278},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A container is not a miniature VM. It is a process whose view of the Linux system has been constrained and whose filesystem has been assembled from…",{},"\u002Fkubernetes-for-web-applications\u002Fday-03",{"title":1213,"description":1343},"kubernetes-for-web-applications\u002Fday-03","CvYE_dBdpzAIVYyu1irKs_NGnDSSZpswavNgK8TIxPY",{"id":1350,"title":1351,"body":1352,"day":31,"description":1474,"extension":769,"meta":1475,"navigation":147,"path":1476,"seo":1477,"stem":1478,"tag":774,"week":12,"weekName":46,"__hash__":1479},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-04.md","Container images, layers, and registries",{"type":89,"value":1353,"toc":1464},[1354,1358,1361,1365,1371,1374,1378,1388,1398,1402,1405,1408,1412,1415,1418,1422,1431,1434,1436,1446,1448],[92,1355,1357],{"id":1356},"day-4-container-images-layers-and-registries","Day 4 — Container images, layers, and registries",[97,1359,1360],{},"An image is the deployable supply-chain artifact for a containerized web app. Treating it as immutable, addressable content—rather than “whatever the latest build produced”—is foundational to reproducible releases and reliable rollback.",[101,1362,1364],{"id":1363},"oci-images-are-content-addressed-bundles","OCI images are content-addressed bundles",[97,1366,1367,1368,916],{},"An OCI image consists of a manifest, configuration, and ordered filesystem layers. Each component is identified by a cryptographic digest. The configuration records metadata such as the default command, environment, working directory, and layer history; the manifest ties it to a particular platform such as ",[119,1369,1370],{},"linux\u002Famd64",[97,1372,1373],{},"Layers are immutable and reusable. If two images share a base layer, a registry and local runtime can avoid transferring it twice. Layer order matters for build performance: stable dependency files should be copied and installed before frequently changing source code so ordinary edits do not invalidate dependency cache layers.",[101,1375,1377],{"id":1376},"tags-are-movable-digests-are-immutable","Tags are movable; digests are immutable",[97,1379,1380,1383,1384,1387],{},[119,1381,1382],{},"registry.example.com\u002Fapi:1.4.0"," is a human-friendly tag that points to a manifest. A registry can move that tag. ",[119,1385,1386],{},"registry.example.com\u002Fapi@sha256:..."," identifies exact content. Deploying by digest ensures that every node retrieves the same artifact and makes a rollback deterministic.",[97,1389,1390,1391,1393,1394,1397],{},"Use unique tags—often a Git commit SHA—for traceability, but promote and deploy the digest produced by the build. Avoid relying on ",[119,1392,711],{},": it hides release identity, interacts confusingly with pull policies, and makes incident reconstruction harder. The version exposed by your ",[119,1395,1396],{},"\u002Fversion"," endpoint should correspond to build metadata, not an arbitrary mutable tag.",[101,1399,1401],{"id":1400},"registries-distribute-and-govern-artifacts","Registries distribute and govern artifacts",[97,1403,1404],{},"A registry stores image manifests and blobs and exposes APIs used by builders and runtimes. Production concerns include authentication, retention, vulnerability scanning, geographic availability, and rate limits. Kubernetes nodes need credentials or workload identity to pull private images; build pipelines need narrowly scoped permission to push.",[97,1406,1407],{},"Image distribution occurs on each node. Large images extend cold-start and scale-up time, especially when a new node has no cached layers. Prefer small runtime images and exclude source maps, tests, caches, local secrets, and development dependencies unless the runtime genuinely needs them.",[101,1409,1411],{"id":1410},"build-once-promote-the-same-artifact","Build once, promote the same artifact",[97,1413,1414],{},"Environment-specific image builds create drift. Build and scan once, then promote the same digest from test to staging and production while injecting environment configuration at runtime. This separates code identity from operational configuration.",[97,1416,1417],{},"A useful release record includes source commit, build job, image digest, software bill of materials, scan result, and deployment revision. Signing and provenance attestations can later let admission policy verify that an image came from the approved pipeline. These controls matter more than a perfect Dockerfile style guide because they answer what code is actually running.",[1015,1419,1421],{"id":1420},"multi-platform-images-require-deliberate-testing","Multi-platform images require deliberate testing",[97,1423,1424,1425,1306,1427,1430],{},"A manifest index can point to different image manifests for ",[119,1426,1370],{},[119,1428,1429],{},"linux\u002Farm64"," under one tag. The runtime selects the matching platform. This is convenient for developers on ARM laptops and production on x86 nodes, but only if every variant is built from the same source and tested. Native Node add-ons are a common source of architecture-specific failures.",[97,1432,1433],{},"Do not assume a local build automatically supplies the production platform. Use a multi-platform builder or explicitly target the cluster architecture, and make CI validate that the registry contains each promised variant. When diagnosing an “exec format error,” inspect the node architecture and image manifest before changing application code. Platform identity belongs in the release evidence alongside the digest.",[101,1435,723],{"id":722},[97,1437,1438,1439,1306,1442,1445],{},"Build any local image, then run ",[119,1440,1441],{},"docker image inspect \u003Cname>:\u003Ctag>",[119,1443,1444],{},"docker history \u003Cname>:\u003Ctag>",". Tag it twice and observe that both names initially share an image ID. If you have a test registry, push it and inspect the resulting digest; pull by digest and confirm that the image contents remain fixed even if the tag is rebuilt.",[101,1447,734],{"id":733},[736,1449,1450,1457],{},[739,1451,1452],{},[742,1453,1456],{"href":1454,"rel":1455},"https:\u002F\u002Fgithub.com\u002Fopencontainers\u002Fimage-spec",[746],"OCI Image Format Specification",[739,1458,1459],{},[742,1460,1463],{"href":1461,"rel":1462},"https:\u002F\u002Fdocs.docker.com\u002Fdhi\u002Fcore-concepts\u002Fdigests\u002F",[746],"Docker: Image digests",{"title":117,"searchDepth":21,"depth":21,"links":1465},[1466,1467,1468,1469,1472,1473],{"id":1363,"depth":21,"text":1364},{"id":1376,"depth":21,"text":1377},{"id":1400,"depth":21,"text":1401},{"id":1410,"depth":21,"text":1411,"children":1470},[1471],{"id":1420,"depth":26,"text":1421},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"An image is the deployable supply-chain artifact for a containerized web app. Treating it as immutable, addressable content—rather than “whatever the…",{},"\u002Fkubernetes-for-web-applications\u002Fday-04",{"title":1351,"description":1474},"kubernetes-for-web-applications\u002Fday-04","TaiLUPVuABxNLP-3aMfquckc8GfheT8clT2IY96HZVg",{"id":1481,"title":1482,"body":1483,"day":58,"description":1718,"extension":769,"meta":1719,"navigation":147,"path":1720,"seo":1721,"stem":1722,"tag":774,"week":12,"weekName":46,"__hash__":1723},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-05.md","Dockerfiles for production Node.js services",{"type":89,"value":1484,"toc":1708},[1485,1489,1492,1496,1503,1588,1591,1595,1606,1623,1627,1630,1637,1641,1650,1653,1657,1663,1670,1672,1687,1689,1705],[92,1486,1488],{"id":1487},"day-5-dockerfiles-for-production-nodejs-services","Day 5 — Dockerfiles for production Node.js services",[97,1490,1491],{},"A production Dockerfile should produce a small, deterministic runtime artifact, not reproduce a developer workstation. For Node and Nuxt, the key is separating dependency\u002Fbuild work from the runtime, preserving cache efficiency, and making privilege and secret boundaries explicit.",[101,1493,1495],{"id":1494},"multi-stage-builds-separate-concerns","Multi-stage builds separate concerns",[97,1497,1498,1499,1502],{},"Use one stage with compilers and development dependencies, then copy only runtime output into a minimal final stage. A Nuxt 3 production build emits a self-contained ",[119,1500,1501],{},".output"," tree that can run with Node:",[112,1504,1508],{"className":1505,"code":1506,"language":1507,"meta":117,"style":117},"language-dockerfile shiki shiki-themes github-dark","# syntax=docker\u002Fdockerfile:1\nFROM node:22-bookworm-slim AS build\nWORKDIR \u002Fapp\nCOPY package.json package-lock.json .\u002F\nRUN --mount=type=cache,target=\u002Froot\u002F.npm npm ci\nCOPY . .\nRUN npm run build\n\nFROM node:22-bookworm-slim AS runtime\nENV NODE_ENV=production PORT=3000\nWORKDIR \u002Fapp\nRUN useradd --system --uid 10001 --create-home app\nCOPY --from=build --chown=app:app \u002Fapp\u002F.output .\u002F.output\nUSER 10001\nEXPOSE 3000\nCMD [\"node\", \".output\u002Fserver\u002Findex.mjs\"]\n","dockerfile",[119,1509,1510,1515,1520,1525,1530,1535,1540,1545,1549,1554,1559,1563,1568,1573,1578,1583],{"__ignoreMap":117},[122,1511,1512],{"class":124,"line":12},[122,1513,1514],{},"# syntax=docker\u002Fdockerfile:1\n",[122,1516,1517],{"class":124,"line":21},[122,1518,1519],{},"FROM node:22-bookworm-slim AS build\n",[122,1521,1522],{"class":124,"line":26},[122,1523,1524],{},"WORKDIR \u002Fapp\n",[122,1526,1527],{"class":124,"line":31},[122,1528,1529],{},"COPY package.json package-lock.json .\u002F\n",[122,1531,1532],{"class":124,"line":58},[122,1533,1534],{},"RUN --mount=type=cache,target=\u002Froot\u002F.npm npm ci\n",[122,1536,1537],{"class":124,"line":231},[122,1538,1539],{},"COPY . .\n",[122,1541,1542],{"class":124,"line":264},[122,1543,1544],{},"RUN npm run build\n",[122,1546,1547],{"class":124,"line":272},[122,1548,148],{"emptyLinePlaceholder":147},[122,1550,1551],{"class":124,"line":278},[122,1552,1553],{},"FROM node:22-bookworm-slim AS runtime\n",[122,1555,1556],{"class":124,"line":285},[122,1557,1558],{},"ENV NODE_ENV=production PORT=3000\n",[122,1560,1561],{"class":124,"line":310},[122,1562,1524],{},[122,1564,1565],{"class":124,"line":316},[122,1566,1567],{},"RUN useradd --system --uid 10001 --create-home app\n",[122,1569,1570],{"class":124,"line":321},[122,1571,1572],{},"COPY --from=build --chown=app:app \u002Fapp\u002F.output .\u002F.output\n",[122,1574,1575],{"class":124,"line":337},[122,1576,1577],{},"USER 10001\n",[122,1579,1580],{"class":124,"line":342},[122,1581,1582],{},"EXPOSE 3000\n",[122,1584,1585],{"class":124,"line":363},[122,1586,1587],{},"CMD [\"node\", \".output\u002Fserver\u002Findex.mjs\"]\n",[97,1589,1590],{},"Pin the Node major version and commit the lockfile. For stricter reproducibility, pin the base image digest and use an automated dependency process to update it.",[101,1592,1594],{"id":1593},"cache-dependencies-deliberately","Cache dependencies deliberately",[97,1596,1597,1598,1601,1602,1605],{},"Docker rebuilds from the first changed instruction onward. Copying ",[119,1599,1600],{},"package.json"," and the lockfile before application source allows ",[119,1603,1604],{},"npm ci"," to remain cached while source files change. A BuildKit cache mount preserves the package download cache without baking it into an image layer.",[97,1607,1608,1609,1612,1613,199,1616,1619,1620,1622],{},"Use ",[119,1610,1611],{},".dockerignore"," to exclude ",[119,1614,1615],{},".git",[119,1617,1618],{},"node_modules",", build output, coverage, local environment files, and editor data. Sending a smaller build context is faster and reduces accidental secret exposure. Do not copy host ",[119,1621,1618],{},"; native packages may target the wrong OS or CPU architecture.",[101,1624,1626],{"id":1625},"runtime-images-should-be-boring-and-unprivileged","Runtime images should be boring and unprivileged",[97,1628,1629],{},"Choose a maintained base that provides the compatibility and debugging surface you require. Alpine is small but uses musl rather than glibc and can complicate native Node dependencies. A Debian slim image is often a pragmatic default. “Distroless” images reduce utilities and attack surface but require stronger external debugging practices.",[97,1631,1632,1633,1636],{},"Run as a fixed non-root UID, listen above port 1024, and avoid installing shells or package managers in the final image unless needed. The application should write logs to stdout\u002Fstderr and treat its filesystem as disposable. Kubernetes can then enforce ",[119,1634,1635],{},"runAsNonRoot",", a read-only root filesystem, and dropped capabilities.",[101,1638,1640],{"id":1639},"secrets-and-configuration-belong-outside-the-build","Secrets and configuration belong outside the build",[97,1642,1643,1306,1646,1649],{},[119,1644,1645],{},"ARG",[119,1647,1648],{},"ENV"," values can appear in image metadata or build history. Never use them for registry tokens, private npm credentials, or runtime secrets. With BuildKit, mount build secrets temporarily—for example, an npm configuration file—so they never enter a layer. Rotate any secret that was copied into a build context, even if a later instruction deleted it.",[97,1651,1652],{},"Public Nuxt configuration may be compiled into browser assets and is not secret. Runtime server configuration should be read from environment variables or mounted files. Validate required values at process startup so a malformed deployment fails visibly rather than serving partially configured traffic.",[1015,1654,1656],{"id":1655},"make-the-image-observable-and-testable","Make the image observable and testable",[97,1658,1659,1660,1662],{},"Add OCI labels for source repository, revision, and creation metadata so operators can trace an artifact without relying on its tag. Expose a non-secret application version from the build through a small ",[119,1661,1396],{}," response or startup log. This should identify the Git revision used to create the image, while the deployment system records the final registry digest.",[97,1664,1665,1666,1669],{},"Test the final stage, not only ",[119,1667,1668],{},"npm run build"," on the host. Start it as the configured user, with the root filesystem read-only and only documented environment variables present. Exercise HTTP startup and SIGTERM shutdown. Scan the final image rather than the builder stage, but also review build dependencies because compromised tooling can affect produced output even when it is absent at runtime.",[101,1671,723],{"id":722},[97,1673,1674,1675,1678,1679,1682,1683,1686],{},"Containerize a Nuxt or Node service with the Dockerfile pattern above. Build twice after changing only one source file and compare cached steps. Run ",[119,1676,1677],{},"docker run --rm -p 3000:3000 --read-only --tmpfs \u002Ftmp \u003Cimage>",", verify the process user with ",[119,1680,1681],{},"docker exec",", and confirm no ",[119,1684,1685],{},".env"," or development dependency exists in the final filesystem.",[101,1688,734],{"id":733},[736,1690,1691,1698],{},[739,1692,1693],{},[742,1694,1697],{"href":1695,"rel":1696},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fbest-practices\u002F",[746],"Dockerfile best practices",[739,1699,1700],{},[742,1701,1704],{"href":1702,"rel":1703},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fdeployment",[746],"Nuxt deployment documentation",[756,1706,1707],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":1709},[1710,1711,1712,1713,1716,1717],{"id":1494,"depth":21,"text":1495},{"id":1593,"depth":21,"text":1594},{"id":1625,"depth":21,"text":1626},{"id":1639,"depth":21,"text":1640,"children":1714},[1715],{"id":1655,"depth":26,"text":1656},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A production Dockerfile should produce a small, deterministic runtime artifact, not reproduce a developer workstation. For Node and Nuxt, the key is…",{},"\u002Fkubernetes-for-web-applications\u002Fday-05",{"title":1482,"description":1718},"kubernetes-for-web-applications\u002Fday-05","ElafEBKS3-zwpUPGROkAhEHXkBVGejqfT1BVapUcIkI",{"id":1725,"title":1726,"body":1727,"day":231,"description":1955,"extension":769,"meta":1956,"navigation":147,"path":1957,"seo":1958,"stem":1959,"tag":774,"week":21,"weekName":49,"__hash__":1960},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-06.md","Running containers like an operator",{"type":89,"value":1728,"toc":1945},[1729,1733,1736,1740,1746,1753,1762,1769,1773,1776,1783,1787,1798,1801,1884,1888,1902,1905,1909,1912,1915,1917,1924,1926,1942],[92,1730,1732],{"id":1731},"day-6-running-containers-like-an-operator","Day 6 — Running containers like an operator",[97,1734,1735],{},"Building an image is only half the contract; operating it means understanding exactly how the process starts, stops, emits evidence, and uses host resources. Practicing these mechanics locally makes later Kubernetes failures recognizable rather than opaque.",[101,1737,1739],{"id":1738},"lifecycle-exit-status-and-signals","Lifecycle, exit status, and signals",[97,1741,1742,1743,1745],{},"A container runs while its configured main process runs. Exit code ",[119,1744,977],{}," normally means deliberate completion; nonzero codes indicate failure by convention. If PID 1 crashes, the container stops regardless of whether background child processes remain useful. A restart policy may start a new instance, but it does not repair the underlying cause.",[97,1747,1748,1749,1752],{},"Use the exec form of ",[119,1750,1751],{},"CMD"," so Node directly receives signals:",[112,1754,1756],{"className":1505,"code":1755,"language":1507,"meta":117,"style":117},"CMD [\"node\", \"server.mjs\"]\n",[119,1757,1758],{"__ignoreMap":117},[122,1759,1760],{"class":124,"line":12},[122,1761,1755],{},[97,1763,1764,1765,1768],{},"Shell form introduces ",[119,1766,1767],{},"\u002Fbin\u002Fsh -c",", which can interfere with signal delivery. On SIGTERM, stop accepting requests, allow in-flight work to complete within a deadline, close database clients, and exit. Test this behavior; orchestrators eventually send SIGKILL when the grace period expires.",[101,1770,1772],{"id":1771},"logs-are-an-external-data-stream","Logs are an external data stream",[97,1774,1775],{},"Applications should write structured events to stdout and errors to stderr. The container runtime captures those streams; a logging agent can later ship them centrally. Files written inside the container are harder to collect and disappear with the writable layer.",[97,1777,1778,1779,1782],{},"A useful JSON log includes timestamp, severity, service, request or trace ID, route, duration, and a stable error code. Do not log credentials, cookies, authorization headers, or complete request bodies. ",[119,1780,1781],{},"docker logs --since 10m --follow \u003Ccontainer>"," is the local equivalent of following a workload’s current evidence, but remember that a restarted container may have previous-instance logs elsewhere.",[101,1784,1786],{"id":1785},"ports-mounts-and-runtime-configuration","Ports, mounts, and runtime configuration",[97,1788,1789,1790,1793,1794,1797],{},"Publishing ",[119,1791,1792],{},"-p 8080:3000"," maps host port 8080 to the container’s port 3000. ",[119,1795,1796],{},"EXPOSE 3000"," is documentation; it does not publish anything. Bind mounts map a specific host path and are useful for development. Named volumes are managed by Docker and suit persistent local data. Neither should be used to inject an entire host home directory into a production container.",[97,1799,1800],{},"Environment variables are convenient configuration, but they are visible through process and runtime inspection. Use them for ordinary settings and use secret-specific mechanisms for credentials. Validate and normalize them:",[112,1802,1804],{"className":805,"code":1803,"language":807,"meta":117,"style":117},"const port = Number.parseInt(process.env.PORT ?? '3000', 10);\nif (!Number.isInteger(port) || port \u003C 1) throw new Error('Invalid PORT');\n",[119,1805,1806,1836],{"__ignoreMap":117},[122,1807,1808,1810,1812,1814,1817,1820,1822,1824,1826,1829,1831,1834],{"class":124,"line":12},[122,1809,170],{"class":127},[122,1811,833],{"class":162},[122,1813,176],{"class":127},[122,1815,1816],{"class":131}," Number.",[122,1818,1819],{"class":182},"parseInt",[122,1821,841],{"class":131},[122,1823,844],{"class":162},[122,1825,847],{"class":127},[122,1827,1828],{"class":138}," '3000'",[122,1830,199],{"class":131},[122,1832,1833],{"class":162},"10",[122,1835,307],{"class":131},[122,1837,1838,1841,1843,1846,1849,1852,1855,1858,1861,1864,1866,1868,1871,1874,1877,1879,1882],{"class":124,"line":21},[122,1839,1840],{"class":127},"if",[122,1842,192],{"class":131},[122,1844,1845],{"class":127},"!",[122,1847,1848],{"class":131},"Number.",[122,1850,1851],{"class":182},"isInteger",[122,1853,1854],{"class":131},"(port) ",[122,1856,1857],{"class":127},"||",[122,1859,1860],{"class":131}," port ",[122,1862,1863],{"class":127},"\u003C",[122,1865,406],{"class":162},[122,1867,205],{"class":131},[122,1869,1870],{"class":127},"throw",[122,1872,1873],{"class":127}," new",[122,1875,1876],{"class":182}," Error",[122,1878,186],{"class":131},[122,1880,1881],{"class":138},"'Invalid PORT'",[122,1883,307],{"class":131},[101,1885,1887],{"id":1886},"debug-from-outside-inward","Debug from outside inward",[97,1889,1890,1891,1894,1895,1898,1899,1901],{},"Start with ",[119,1892,1893],{},"docker ps -a"," for state and exit code, then inspect logs and ",[119,1896,1897],{},"docker inspect",". Confirm the command, environment, mounts, health, and port bindings. Use ",[119,1900,1681],{}," only if the container is running. For an image that exits immediately, override the entrypoint to a shell or start a separate diagnostic container from the same image.",[97,1903,1904],{},"Avoid “fixing” a running container by installing packages or editing files. That creates unrecorded state that disappears on replacement. Reproduce the failure, update source or image construction, rebuild, and redeploy.",[1015,1906,1908],{"id":1907},"restart-policy-is-not-resilience","Restart policy is not resilience",[97,1910,1911],{},"A restart can recover from a transient process crash, but repeated restarts consume resources and may make a dependency outage worse. Establish what exit codes mean, whether startup performs migrations, and whether retries are bounded. A worker that loses a job when terminated needs queue acknowledgement semantics; an HTTP API needs upstream timeouts and idempotent retry behavior. Container lifecycle controls cannot invent those guarantees.",[97,1913,1914],{},"Measure shutdown under load. Docker sends SIGTERM and waits before forcing termination, which approximates—but does not fully reproduce—an orchestrated rollout. Track in-flight requests, rejected connections, unfinished background work, and exit duration. The result informs Kubernetes readiness changes and termination grace periods rather than leaving them at arbitrary defaults.",[101,1916,723],{"id":722},[97,1918,1919,1920,1923],{},"Run your Day 5 image with a deliberately invalid environment variable and inspect its exit status and logs. Run it correctly, send ",[119,1921,1922],{},"docker stop",", and time graceful shutdown. Test port publishing and a read-only root filesystem. Finally, start it with a missing runtime file and write a five-step diagnosis based only on inspectable evidence.",[101,1925,734],{"id":733},[736,1927,1928,1935],{},[739,1929,1930],{},[742,1931,1934],{"href":1932,"rel":1933},"https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Fcontainers\u002Fstart-containers-automatically\u002F",[746],"Docker: Start containers automatically",[739,1936,1937],{},[742,1938,1941],{"href":1939,"rel":1940},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Fhttp.html#serverclosecallback",[746],"Node.js HTTP server close documentation",[756,1943,1944],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":117,"searchDepth":21,"depth":21,"links":1946},[1947,1948,1949,1950,1953,1954],{"id":1738,"depth":21,"text":1739},{"id":1771,"depth":21,"text":1772},{"id":1785,"depth":21,"text":1786},{"id":1886,"depth":21,"text":1887,"children":1951},[1952],{"id":1907,"depth":26,"text":1908},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Building an image is only half the contract; operating it means understanding exactly how the process starts, stops, emits evidence, and uses host…",{},"\u002Fkubernetes-for-web-applications\u002Fday-06",{"title":1726,"description":1955},"kubernetes-for-web-applications\u002Fday-06","6YVpvS3JW8hBXYbd6JBByj_QG4bTAqhk_2d_wLxF1Ks",{"id":1962,"title":1963,"body":1964,"day":264,"description":2307,"extension":769,"meta":2308,"navigation":147,"path":2309,"seo":2310,"stem":2311,"tag":774,"week":21,"weekName":49,"__hash__":2312},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-07.md","Multi-service applications with Compose",{"type":89,"value":1965,"toc":2297},[1966,1970,1973,1977,1988,2209,2212,2216,2222,2225,2229,2240,2243,2247,2250,2253,2257,2260,2263,2265,2276,2278,2294],[92,1967,1969],{"id":1968},"day-7-multi-service-applications-with-compose","Day 7 — Multi-service applications with Compose",[97,1971,1972],{},"Compose is a compact way to model a local web topology: application processes, data dependencies, networks, storage, and configuration in one reproducible file. Its real value is not imitating Kubernetes syntax; it is making service boundaries and startup assumptions explicit.",[101,1974,1976],{"id":1975},"services-discover-each-other-by-name","Services discover each other by name",[97,1978,1979,1980,1983,1984,1987],{},"Compose creates a default network and DNS records for service names. An API connects to ",[119,1981,1982],{},"postgres:5432",", not ",[119,1985,1986],{},"localhost:5432",", because localhost inside the API container refers to itself. Only publish ports that the host needs; service-to-service traffic can stay on the internal network.",[112,1989,1991],{"className":469,"code":1990,"language":471,"meta":117,"style":117},"services:\n  api:\n    build: .\n    environment:\n      DATABASE_URL: postgres:\u002F\u002Fapp:dev@db:5432\u002Fapp\n    ports: [\"3000:3000\"]\n    depends_on:\n      db:\n        condition: service_healthy\n  db:\n    image: postgres:17\n    environment:\n      POSTGRES_USER: app\n      POSTGRES_PASSWORD: dev\n      POSTGRES_DB: app\n    volumes: [\"db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\"]\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U app\"]\n      interval: 5s\n      timeout: 3s\n      retries: 10\nvolumes:\n  db-data: {}\n",[119,1992,1993,2000,2007,2017,2024,2034,2048,2055,2062,2072,2079,2089,2095,2105,2115,2124,2136,2143,2160,2170,2181,2192,2200],{"__ignoreMap":117},[122,1994,1995,1998],{"class":124,"line":12},[122,1996,1997],{"class":478},"services",[122,1999,503],{"class":131},[122,2001,2002,2005],{"class":124,"line":21},[122,2003,2004],{"class":478},"  api",[122,2006,503],{"class":131},[122,2008,2009,2012,2014],{"class":124,"line":26},[122,2010,2011],{"class":478},"    build",[122,2013,482],{"class":131},[122,2015,2016],{"class":162},".\n",[122,2018,2019,2022],{"class":124,"line":31},[122,2020,2021],{"class":478},"    environment",[122,2023,503],{"class":131},[122,2025,2026,2029,2031],{"class":124,"line":58},[122,2027,2028],{"class":478},"      DATABASE_URL",[122,2030,482],{"class":131},[122,2032,2033],{"class":138},"postgres:\u002F\u002Fapp:dev@db:5432\u002Fapp\n",[122,2035,2036,2039,2042,2045],{"class":124,"line":231},[122,2037,2038],{"class":478},"    ports",[122,2040,2041],{"class":131},": [",[122,2043,2044],{"class":138},"\"3000:3000\"",[122,2046,2047],{"class":131},"]\n",[122,2049,2050,2053],{"class":124,"line":264},[122,2051,2052],{"class":478},"    depends_on",[122,2054,503],{"class":131},[122,2056,2057,2060],{"class":124,"line":272},[122,2058,2059],{"class":478},"      db",[122,2061,503],{"class":131},[122,2063,2064,2067,2069],{"class":124,"line":278},[122,2065,2066],{"class":478},"        condition",[122,2068,482],{"class":131},[122,2070,2071],{"class":138},"service_healthy\n",[122,2073,2074,2077],{"class":124,"line":285},[122,2075,2076],{"class":478},"  db",[122,2078,503],{"class":131},[122,2080,2081,2084,2086],{"class":124,"line":310},[122,2082,2083],{"class":478},"    image",[122,2085,482],{"class":131},[122,2087,2088],{"class":138},"postgres:17\n",[122,2090,2091,2093],{"class":124,"line":316},[122,2092,2021],{"class":478},[122,2094,503],{"class":131},[122,2096,2097,2100,2102],{"class":124,"line":321},[122,2098,2099],{"class":478},"      POSTGRES_USER",[122,2101,482],{"class":131},[122,2103,2104],{"class":138},"app\n",[122,2106,2107,2110,2112],{"class":124,"line":337},[122,2108,2109],{"class":478},"      POSTGRES_PASSWORD",[122,2111,482],{"class":131},[122,2113,2114],{"class":138},"dev\n",[122,2116,2117,2120,2122],{"class":124,"line":342},[122,2118,2119],{"class":478},"      POSTGRES_DB",[122,2121,482],{"class":131},[122,2123,2104],{"class":138},[122,2125,2126,2129,2131,2134],{"class":124,"line":363},[122,2127,2128],{"class":478},"    volumes",[122,2130,2041],{"class":131},[122,2132,2133],{"class":138},"\"db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\"",[122,2135,2047],{"class":131},[122,2137,2138,2141],{"class":124,"line":376},[122,2139,2140],{"class":478},"    healthcheck",[122,2142,503],{"class":131},[122,2144,2145,2148,2150,2153,2155,2158],{"class":124,"line":417},[122,2146,2147],{"class":478},"      test",[122,2149,2041],{"class":131},[122,2151,2152],{"class":138},"\"CMD-SHELL\"",[122,2154,199],{"class":131},[122,2156,2157],{"class":138},"\"pg_isready -U app\"",[122,2159,2047],{"class":131},[122,2161,2162,2165,2167],{"class":124,"line":450},[122,2163,2164],{"class":478},"      interval",[122,2166,482],{"class":131},[122,2168,2169],{"class":138},"5s\n",[122,2171,2173,2176,2178],{"class":124,"line":2172},20,[122,2174,2175],{"class":478},"      timeout",[122,2177,482],{"class":131},[122,2179,2180],{"class":138},"3s\n",[122,2182,2184,2187,2189],{"class":124,"line":2183},21,[122,2185,2186],{"class":478},"      retries",[122,2188,482],{"class":131},[122,2190,2191],{"class":162},"10\n",[122,2193,2195,2198],{"class":124,"line":2194},22,[122,2196,2197],{"class":478},"volumes",[122,2199,503],{"class":131},[122,2201,2203,2206],{"class":124,"line":2202},23,[122,2204,2205],{"class":478},"  db-data",[122,2207,2208],{"class":131},": {}\n",[97,2210,2211],{},"The password is acceptable only as disposable local configuration. Do not commit real credentials.",[101,2213,2215],{"id":2214},"startup-order-is-not-readiness","Startup order is not readiness",[97,2217,2218,2221],{},[119,2219,2220],{},"depends_on"," can order startup and, with a health condition, wait for a dependency’s health check. It does not make the application resilient to database restarts after startup. The API still needs bounded retries with backoff, connection timeouts, and clear failures.",[97,2223,2224],{},"Design as though dependencies can disappear at any time. Database migrations should be an explicit job or command rather than a race performed independently by every API replica. Cache unavailability should have an intentional policy: fail requests, degrade a feature, or fall back—never wait forever.",[101,2226,2228],{"id":2227},"volumes-preserve-only-the-state-you-choose","Volumes preserve only the state you choose",[97,2230,2231,2232,2235,2236,2239],{},"The named volume in the example outlives the database container. ",[119,2233,2234],{},"docker compose down"," preserves it; ",[119,2237,2238],{},"docker compose down --volumes"," removes it. This is useful for local development but does not provide backup, replication, encryption policy, or recovery testing.",[97,2241,2242],{},"Bind mounts are helpful for live source editing but reduce parity with the immutable production image. Maintain a deliberate development override rather than turning the base topology into a collection of host-specific assumptions. Periodically run the exact built image without source mounts.",[101,2244,2246],{"id":2245},"compose-exposes-orchestration-requirements","Compose exposes orchestration requirements",[97,2248,2249],{},"Compose can restart containers and coordinate a single-machine topology, but it does not provide a distributed scheduler, multi-node rescheduling, rolling Deployments, cluster Services, or Kubernetes-style policy controls. That limitation is useful: it reveals what an orchestrator adds.",[97,2251,2252],{},"Translate concepts, not YAML. A Compose service may become a Deployment and Service; a one-off migration becomes a Job; configuration becomes ConfigMaps and Secrets; a named volume may become a PersistentVolumeClaim or, preferably for production databases, a managed service. The application’s network and failure contracts should remain the same.",[1015,2254,2256],{"id":2255},"keep-local-parity-purposeful","Keep local parity purposeful",[97,2258,2259],{},"Parity does not mean running every production infrastructure component on a laptop. It means preserving the behaviors that affect application correctness: immutable images, service-name discovery, runtime configuration, schema migrations, health checks, and dependency failure. A lightweight local Postgres container can represent a managed database’s SQL contract, but it cannot validate cloud identity, network latency, backup, or failover.",[97,2261,2262],{},"Use pinned image versions and committed initialization scripts so teammates get the same topology. Add profiles or override files for optional tools rather than starting an enormous stack by default. In CI, launch the Compose project with a unique project name, wait on health, run integration tests, and always collect logs before teardown. This turns the file into an executable application contract rather than only a developer convenience.",[101,2264,723],{"id":722},[97,2266,2267,2268,2271,2272,2275],{},"Run the example with a small Node API that executes ",[119,2269,2270],{},"SELECT now()",". Verify the API resolves ",[119,2273,2274],{},"db",", then restart only the database and observe how the connection pool recovers. Remove host port publication from the database and confirm the API can still connect. Destroy and recreate containers once with, and once without, deleting the named volume.",[101,2277,734],{"id":733},[736,2279,2280,2287],{},[739,2281,2282],{},[742,2283,2286],{"href":2284,"rel":2285},"https:\u002F\u002Fdocs.docker.com\u002Fcompose\u002Fhow-tos\u002Fnetworking\u002F",[746],"Docker Compose networking",[739,2288,2289],{},[742,2290,2293],{"href":2291,"rel":2292},"https:\u002F\u002Fcompose-spec.io\u002F",[746],"Compose Specification",[756,2295,2296],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":2298},[2299,2300,2301,2302,2305,2306],{"id":1975,"depth":21,"text":1976},{"id":2214,"depth":21,"text":2215},{"id":2227,"depth":21,"text":2228},{"id":2245,"depth":21,"text":2246,"children":2303},[2304],{"id":2255,"depth":26,"text":2256},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Compose is a compact way to model a local web topology: application processes, data dependencies, networks, storage, and configuration in one…",{},"\u002Fkubernetes-for-web-applications\u002Fday-07",{"title":1963,"description":2307},"kubernetes-for-web-applications\u002Fday-07","7ui9rHCj6IuTfJ209FF0tMf4zhCH6xGDYJBkaUOcW04",{"id":2314,"title":2315,"body":2316,"day":272,"description":2408,"extension":769,"meta":2409,"navigation":147,"path":2410,"seo":2411,"stem":2412,"tag":774,"week":21,"weekName":49,"__hash__":2413},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-08.md","Why Kubernetes exists",{"type":89,"value":2317,"toc":2398},[2318,2322,2325,2329,2332,2335,2339,2342,2345,2349,2352,2355,2359,2362,2365,2369,2372,2375,2377,2380,2382],[92,2319,2321],{"id":2320},"day-8-why-kubernetes-exists","Day 8 — Why Kubernetes exists",[97,2323,2324],{},"Kubernetes is valuable when many application instances must be placed, replaced, connected, and updated across a pool of machines. Its central idea is not containers or YAML; it is a control loop that continuously drives observed state toward declared desired state.",[101,2326,2328],{"id":2327},"desired-state-and-reconciliation","Desired state and reconciliation",[97,2330,2331],{},"You submit resources to the Kubernetes API: for example, a Deployment declaring three replicas of an image. Controllers observe that declaration and current cluster state, then create or remove subordinate resources until they converge. If a Pod disappears, the Deployment’s controller causes a replacement to be created without a human restart command.",[97,2333,2334],{},"Reconciliation is asynchronous and level-based. The API accepting a Deployment does not mean the application is ready. Status conditions, events, and child resources show progress. This model rewards declarative changes: state what should be true, then let controllers repeatedly handle ordinary drift.",[101,2336,2338],{"id":2337},"scheduling-and-self-healing-have-boundaries","Scheduling and self-healing have boundaries",[97,2340,2341],{},"The scheduler chooses a node for each unscheduled Pod using resource requests, constraints, affinities, and available capacity. The kubelet on that node asks the container runtime to start containers and continually reports status. Failed containers may restart; failed Pods may be replaced; failed nodes eventually cause workloads to be scheduled elsewhere.",[97,2343,2344],{},"Kubernetes restores declared process topology, not business correctness. It cannot determine that an endpoint returns subtly incorrect prices, recover data never backed up, or make a stateful service safe to replicate. Probes and metrics must express useful health, and the architecture must tolerate replacement.",[101,2346,2348],{"id":2347},"mapping-a-web-stack-to-resources","Mapping a web stack to resources",[97,2350,2351],{},"A stateless Node API typically becomes a Deployment plus a Service. A public Nuxt server may use another Deployment and Service, with an Ingress or Gateway routing external traffic. Configuration enters through ConfigMaps and Secrets. Batch tasks become Jobs or CronJobs. Persistent storage uses claims, though managed databases and object stores are often preferable.",[97,2353,2354],{},"This mapping keeps application concerns visible. Deployments own replaceable processes; Services provide stable discovery; configuration remains external; data has explicit durability. Do not place every container from a Compose file into one Pod. Containers in a Pod share lifecycle and network identity and should be tightly coupled helpers, not independently scalable services.",[101,2356,2358],{"id":2357},"kubernetes-has-a-break-even-point","Kubernetes has a break-even point",[97,2360,2361],{},"Kubernetes earns its complexity when a team needs consistent deployment across multiple services, automated rollouts, policy, workload portability, or an internal platform across a fleet. It is often overkill for one small service with modest availability needs, especially when a managed application platform supplies builds, TLS, scaling, logs, and rollback.",[97,2363,2364],{},"Account for the operating system around Kubernetes: ingress, DNS, certificates, secrets, observability, image governance, upgrades, and cost allocation. Managed control planes remove some cluster administration but not application-platform ownership. Adopt Kubernetes for explicit capabilities, not as a proxy for architectural maturity.",[1015,2366,2368],{"id":2367},"controllers-favor-replaceable-declarative-applications","Controllers favor replaceable, declarative applications",[97,2370,2371],{},"The reconciliation model works best when replacement is ordinary. An API instance should obtain identity from configuration, register no irreplaceable local state, and become useful through a readiness signal. Background jobs should make side effects idempotent because controllers and queues can retry work. Migrations need explicit coordination because multiple replicas may overlap during rollout.",[97,2373,2374],{},"This model also changes intervention. Editing a live Pod is ineffective because the controller recreates it from the template. The durable fix updates the owning resource or its source repository. During incidents, temporary actions can still be necessary, but operators must understand whether reconciliation will undo them and how desired state will be corrected afterward. Kubernetes rewards systems whose intended behavior can be stated, observed, and safely repeated.",[101,2376,723],{"id":722},[97,2378,2379],{},"Take one real web application and list its processes, public routes, internal dependencies, configuration, secrets, background work, and durable data. Map each to a Kubernetes resource or external managed service. Then write three concrete capabilities Kubernetes would add and three ongoing costs; if the benefits are vague, compare a managed container platform before committing.",[101,2381,734],{"id":733},[736,2383,2384,2391],{},[739,2385,2386],{},[742,2387,2390],{"href":2388,"rel":2389},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Foverview\u002F",[746],"Kubernetes concepts overview",[739,2392,2393],{},[742,2394,2397],{"href":2395,"rel":2396},"https:\u002F\u002Fgithub.com\u002Fkubernetes\u002Fcommunity\u002Fblob\u002Fmaster\u002Fcontributors\u002Fdevel\u002Fsig-architecture\u002Fapi-conventions.md",[746],"Kubernetes API conventions",{"title":117,"searchDepth":21,"depth":21,"links":2399},[2400,2401,2402,2403,2406,2407],{"id":2327,"depth":21,"text":2328},{"id":2337,"depth":21,"text":2338},{"id":2347,"depth":21,"text":2348},{"id":2357,"depth":21,"text":2358,"children":2404},[2405],{"id":2367,"depth":26,"text":2368},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Kubernetes is valuable when many application instances must be placed, replaced, connected, and updated across a pool of machines. Its central idea is…",{},"\u002Fkubernetes-for-web-applications\u002Fday-08",{"title":2315,"description":2408},"kubernetes-for-web-applications\u002Fday-08","teOVX0D_ftG3d2dC21edS-cyqlUmSQ66x7ncq1PEDQE",{"id":2415,"title":2416,"body":2417,"day":278,"description":2635,"extension":769,"meta":2636,"navigation":147,"path":2637,"seo":2638,"stem":2639,"tag":774,"week":21,"weekName":49,"__hash__":2640},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-09.md","Cluster architecture without mystique",{"type":89,"value":2418,"toc":2625},[2419,2423,2430,2434,2440,2443,2447,2452,2455,2459,2462,2465,2469,2472,2543,2557,2560,2564,2579,2592,2594,2604,2606,2622],[92,2420,2422],{"id":2421},"day-9-cluster-architecture-without-mystique","Day 9 — Cluster architecture without mystique",[97,2424,2425,2426,2429],{},"A cluster is a distributed control system plus worker machines. Application engineers do not need to operate every component, but knowing who owns each decision makes ",[119,2427,2428],{},"kubectl"," output and failure symptoms much easier to interpret.",[101,2431,2433],{"id":2432},"the-api-server-is-the-front-door","The API server is the front door",[97,2435,2436,2437,2439],{},"Clients, controllers, schedulers, and kubelets communicate through the Kubernetes API server. It authenticates and authorizes requests, runs admission logic, validates resources, and persists accepted state. ",[119,2438,2428],{}," is simply one API client; YAML is a serialization format for API objects, not the platform itself.",[97,2441,2442],{},"etcd is the strongly consistent key-value store behind API state. Application teams should not connect to it directly. On self-managed clusters it requires careful backup and recovery; managed providers usually own those mechanics. A successful write to the API records intent, while resource status later records what the system observed.",[101,2444,2446],{"id":2445},"controllers-and-scheduler-make-decisions","Controllers and scheduler make decisions",[97,2448,2449,2450,916],{},"The controller manager runs control loops such as Deployment, ReplicaSet, Node, and Job controllers. A Deployment controller produces a ReplicaSet; that ReplicaSet causes Pods to exist. Ownership references let Kubernetes garbage-collect dependent objects and explain the hierarchy visible through ",[119,2451,2428],{},[97,2453,2454],{},"The scheduler watches for Pods with no assigned node and selects a feasible placement. It considers resource requests, node selectors, taints and tolerations, affinity, topology, and other constraints. It does not start the container. If a Pod is Pending, scheduling events usually explain whether the issue is capacity or incompatible constraints.",[101,2456,2458],{"id":2457},"nodes-execute-the-declared-workload","Nodes execute the declared workload",[97,2460,2461],{},"Each node runs a kubelet that reconciles assigned Pod specifications with local reality. A container runtime such as containerd pulls images and creates containers. A Container Network Interface implementation configures Pod networking; a storage interface may attach and mount volumes.",[97,2463,2464],{},"This division localizes incidents. An image pull error involves registry access, credentials, or the runtime. A Pod scheduled but unable to start may involve mounts or container configuration. A Ready Pod unreachable through a Service points toward selectors, endpoints, ports, or networking rather than the scheduler.",[101,2466,2468],{"id":2467},"read-state-as-resources-and-events","Read state as resources and events",[97,2470,2471],{},"Start broad, then follow ownership and status:",[112,2473,2477],{"className":2474,"code":2475,"language":2476,"meta":117,"style":117},"language-bash shiki shiki-themes github-dark","kubectl get nodes\nkubectl get deployments,replicasets,pods -A\nkubectl get pod api-abc -o yaml\nkubectl describe pod api-abc\nkubectl get events --sort-by=.metadata.creationTimestamp\n","bash",[119,2478,2479,2489,2501,2519,2531],{"__ignoreMap":117},[122,2480,2481,2483,2486],{"class":124,"line":12},[122,2482,2428],{"class":182},[122,2484,2485],{"class":138}," get",[122,2487,2488],{"class":138}," nodes\n",[122,2490,2491,2493,2495,2498],{"class":124,"line":21},[122,2492,2428],{"class":182},[122,2494,2485],{"class":138},[122,2496,2497],{"class":138}," deployments,replicasets,pods",[122,2499,2500],{"class":162}," -A\n",[122,2502,2503,2505,2507,2510,2513,2516],{"class":124,"line":26},[122,2504,2428],{"class":182},[122,2506,2485],{"class":138},[122,2508,2509],{"class":138}," pod",[122,2511,2512],{"class":138}," api-abc",[122,2514,2515],{"class":162}," -o",[122,2517,2518],{"class":138}," yaml\n",[122,2520,2521,2523,2526,2528],{"class":124,"line":31},[122,2522,2428],{"class":182},[122,2524,2525],{"class":138}," describe",[122,2527,2509],{"class":138},[122,2529,2530],{"class":138}," api-abc\n",[122,2532,2533,2535,2537,2540],{"class":124,"line":58},[122,2534,2428],{"class":182},[122,2536,2485],{"class":138},[122,2538,2539],{"class":138}," events",[122,2541,2542],{"class":162}," --sort-by=.metadata.creationTimestamp\n",[97,2544,2545,2548,2549,2552,2553,2556],{},[119,2546,2547],{},"get"," provides a summary, ",[119,2550,2551],{},"-o yaml"," exposes specification and status, and ",[119,2554,2555],{},"describe"," combines useful fields with related events. Events are time-limited diagnostic records, not a durable audit log. Conditions are better machine-readable indicators of current progress.",[97,2558,2559],{},"Use namespaces to narrow routine queries and labels to select application resources. Avoid editing controller-owned Pods directly: a replacement will be created from the Deployment template and discard the change. Update the owning resource instead.",[1015,2561,2563],{"id":2562},"status-should-guide-the-next-question","Status should guide the next question",[97,2565,2566,2567,2570,2571,2574,2575,2578],{},"Kubernetes resources typically separate ",[119,2568,2569],{},"spec",", the submitted intent, from ",[119,2572,2573],{},"status",", the controller’s observation. Metadata includes generation counters, while conditions often record an ",[119,2576,2577],{},"observedGeneration",". If a controller has not observed the latest generation, its status may describe an older configuration. This distinction matters in automation that otherwise declares success too early.",[97,2580,2581,2582,199,2585,1044,2588,2591],{},"Use API discovery rather than relying on memory: ",[119,2583,2584],{},"kubectl api-resources",[119,2586,2587],{},"kubectl explain deployment.spec.strategy",[119,2589,2590],{},"kubectl get --raw"," expose what the current cluster supports. Managed providers and installed custom resources differ. Read-only discovery is safer than pasting manifests from another version and helps you recognize whether a field is native Kubernetes, provider-specific configuration, or a custom controller’s API.",[101,2593,723],{"id":722},[97,2595,2596,2597,1306,2600,2603],{},"Use a local cluster such as kind, minikube, or Docker Desktop. Run the commands above and locate the API server and node components appropriate to that distribution. Create a simple Deployment, then trace Deployment → ReplicaSet → Pod using ",[119,2598,2599],{},"kubectl get ... -o yaml",[119,2601,2602],{},"ownerReferences",". Delete the Pod and watch reconciliation create a replacement.",[101,2605,734],{"id":733},[736,2607,2608,2615],{},[739,2609,2610],{},[742,2611,2614],{"href":2612,"rel":2613},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Farchitecture\u002F",[746],"Kubernetes cluster architecture",[739,2616,2617],{},[742,2618,2621],{"href":2619,"rel":2620},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Freference\u002Fkubectl\u002F",[746],"kubectl command reference",[756,2623,2624],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":2626},[2627,2628,2629,2630,2633,2634],{"id":2432,"depth":21,"text":2433},{"id":2445,"depth":21,"text":2446},{"id":2457,"depth":21,"text":2458},{"id":2467,"depth":21,"text":2468,"children":2631},[2632],{"id":2562,"depth":26,"text":2563},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A cluster is a distributed control system plus worker machines. Application engineers do not need to operate every component, but knowing who owns each…",{},"\u002Fkubernetes-for-web-applications\u002Fday-09",{"title":2416,"description":2635},"kubernetes-for-web-applications\u002Fday-09","aCBxXwbuPFaRfFJHW2mQ9b9a5TVHWgsanEqZFpLgvW4",{"id":2642,"title":2643,"body":2644,"day":285,"description":2951,"extension":769,"meta":2952,"navigation":147,"path":2953,"seo":2954,"stem":2955,"tag":774,"week":21,"weekName":49,"__hash__":2956},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-10.md","Your first workload: Pods and Deployments",{"type":89,"value":2645,"toc":2941},[2646,2650,2653,2657,2660,2663,2666,2670,2673,2846,2849,2853,2860,2867,2871,2886,2893,2897,2904,2907,2909,2920,2922,2938],[92,2647,2649],{"id":2648},"day-10-your-first-workload-pods-and-deployments","Day 10 — Your first workload: Pods and Deployments",[97,2651,2652],{},"Pods are replaceable execution envelopes; Deployments are the durable declaration most web services should own. The practical distinction prevents a common mistake: caring for an individual Pod when the correct operational target is the controller that can replace it.",[101,2654,2656],{"id":2655},"pods-define-co-located-containers","Pods define co-located containers",[97,2658,2659],{},"Containers in one Pod share a network namespace, IP address, and mounted volumes. They can communicate over localhost and are scheduled together. Use multiple containers only when their lifecycles are tightly coupled—for example, a proxy or telemetry helper—not merely because two services talk to each other.",[97,2661,2662],{},"Treat one container as the boundary for one primary service process. This is not a rule that a container may contain only one Unix process: Node can create worker threads or child processes, and an init process may reap children. The operational rule is to avoid bundling independently deployable services under a process manager in one container. Separate containers give each service an explicit image, resource policy, health status, logs, and restart behavior; separate Pods additionally allow independent placement and scaling.",[97,2664,2665],{},"Pod identity and writable files are ephemeral. A replacement usually receives a new name and IP. Applications should not depend on either. Stable discovery comes from Services, configuration from external resources, and durable data from external storage.",[101,2667,2669],{"id":2668},"deployments-manage-stateless-replicas","Deployments manage stateless replicas",[97,2671,2672],{},"A Deployment declares a Pod template and replica count. It creates ReplicaSets that maintain Pods and support rolling updates. Labels tie these objects together, so selector correctness is critical. A minimal API workload is:",[112,2674,2676],{"className":469,"code":2675,"language":471,"meta":117,"style":117},"apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: web-api\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: web-api\n  template:\n    metadata:\n      labels:\n        app: web-api\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io\u002Fexample\u002Fweb-api@sha256:REPLACE_ME\n          ports:\n            - name: http\n              containerPort: 3000\n",[119,2677,2678,2687,2696,2702,2711,2717,2727,2734,2741,2750,2757,2764,2771,2780,2787,2794,2807,2817,2824,2836],{"__ignoreMap":117},[122,2679,2680,2682,2684],{"class":124,"line":12},[122,2681,479],{"class":478},[122,2683,482],{"class":131},[122,2685,2686],{"class":138},"apps\u002Fv1\n",[122,2688,2689,2691,2693],{"class":124,"line":21},[122,2690,490],{"class":478},[122,2692,482],{"class":131},[122,2694,2695],{"class":138},"Deployment\n",[122,2697,2698,2700],{"class":124,"line":26},[122,2699,500],{"class":478},[122,2701,503],{"class":131},[122,2703,2704,2706,2708],{"class":124,"line":31},[122,2705,508],{"class":478},[122,2707,482],{"class":131},[122,2709,2710],{"class":138},"web-api\n",[122,2712,2713,2715],{"class":124,"line":58},[122,2714,2569],{"class":478},[122,2716,503],{"class":131},[122,2718,2719,2722,2724],{"class":124,"line":231},[122,2720,2721],{"class":478},"  replicas",[122,2723,482],{"class":131},[122,2725,2726],{"class":162},"2\n",[122,2728,2729,2732],{"class":124,"line":264},[122,2730,2731],{"class":478},"  selector",[122,2733,503],{"class":131},[122,2735,2736,2739],{"class":124,"line":272},[122,2737,2738],{"class":478},"    matchLabels",[122,2740,503],{"class":131},[122,2742,2743,2746,2748],{"class":124,"line":278},[122,2744,2745],{"class":478},"      app",[122,2747,482],{"class":131},[122,2749,2710],{"class":138},[122,2751,2752,2755],{"class":124,"line":285},[122,2753,2754],{"class":478},"  template",[122,2756,503],{"class":131},[122,2758,2759,2762],{"class":124,"line":310},[122,2760,2761],{"class":478},"    metadata",[122,2763,503],{"class":131},[122,2765,2766,2769],{"class":124,"line":316},[122,2767,2768],{"class":478},"      labels",[122,2770,503],{"class":131},[122,2772,2773,2776,2778],{"class":124,"line":321},[122,2774,2775],{"class":478},"        app",[122,2777,482],{"class":131},[122,2779,2710],{"class":138},[122,2781,2782,2785],{"class":124,"line":337},[122,2783,2784],{"class":478},"    spec",[122,2786,503],{"class":131},[122,2788,2789,2792],{"class":124,"line":342},[122,2790,2791],{"class":478},"      containers",[122,2793,503],{"class":131},[122,2795,2796,2799,2802,2804],{"class":124,"line":363},[122,2797,2798],{"class":131},"        - ",[122,2800,2801],{"class":478},"name",[122,2803,482],{"class":131},[122,2805,2806],{"class":138},"api\n",[122,2808,2809,2812,2814],{"class":124,"line":376},[122,2810,2811],{"class":478},"          image",[122,2813,482],{"class":131},[122,2815,2816],{"class":138},"ghcr.io\u002Fexample\u002Fweb-api@sha256:REPLACE_ME\n",[122,2818,2819,2822],{"class":124,"line":417},[122,2820,2821],{"class":478},"          ports",[122,2823,503],{"class":131},[122,2825,2826,2829,2831,2833],{"class":124,"line":450},[122,2827,2828],{"class":131},"            - ",[122,2830,2801],{"class":478},[122,2832,482],{"class":131},[122,2834,2835],{"class":138},"http\n",[122,2837,2838,2841,2843],{"class":124,"line":2172},[122,2839,2840],{"class":478},"              containerPort",[122,2842,482],{"class":131},[122,2844,2845],{"class":162},"3000\n",[97,2847,2848],{},"The selector must match the Pod-template labels and is effectively immutable. Add descriptive labels such as application, component, and version, but keep selectors stable across releases.",[101,2850,2852],{"id":2851},"pod-phases-are-not-application-readiness","Pod phases are not application readiness",[97,2854,2855,2856,2859],{},"Common Pod phases include Pending, Running, Succeeded, and Failed. ",[119,2857,2858],{},"Running"," means the Pod is bound to a node and its required containers have been created; it does not prove the API can serve a request. Container status exposes waiting, running, and terminated state, along with restart count and reasons.",[97,2861,2862,2863,2866],{},"A Deployment’s ",[119,2864,2865],{},"Available"," condition incorporates readiness and availability timing. Later you will define probes; until then, Kubernetes considers a started container ready quickly. Always inspect both the controller rollout and individual Pod status when validating a release.",[101,2868,2870],{"id":2869},"work-through-the-controller","Work through the controller",[97,2872,2873,2874,2877,2878,2881,2882,2885],{},"Apply desired state with ",[119,2875,2876],{},"kubectl apply -f deployment.yaml",", then use ",[119,2879,2880],{},"kubectl rollout status deployment\u002Fweb-api",". View selected Pods with ",[119,2883,2884],{},"kubectl get pods -l app=web-api -o wide",". Logs and details operate on Pods, but configuration changes belong in the Deployment template.",[97,2887,2888,2889,2892],{},"Deleting one Pod is a useful reconciliation test, not a deployment method. Scaling should update the Deployment: ",[119,2890,2891],{},"kubectl scale deployment\u002Fweb-api --replicas=3",". For repeatable environments, commit the declarative replica value or let an autoscaler own it rather than leaving an unexplained imperative change.",[1015,2894,2896],{"id":2895},"the-pod-template-is-the-release-boundary","The Pod template is the release boundary",[97,2898,2899,2900,2903],{},"Any meaningful change to ",[119,2901,2902],{},"spec.template","—image, environment, labels, probes, or annotations—creates a new Deployment revision and ReplicaSet. Changing only Deployment metadata does not roll Pods. Configuration referenced indirectly by name may also change without altering the template, which is why content-hashed configuration names or checksum annotations are common rollout triggers.",[97,2905,2906],{},"Avoid setting both an HPA and a delivery manifest to fight over replica count. Decide which controller owns each field. The same principle applies to GitOps tools and imperative commands: multiple writers cause surprising reconciliation. Server-side apply can track field managers, but clear organizational ownership is still required. A reliable workload has one authoritative path for release configuration and observable controllers for runtime state.",[101,2908,723],{"id":722},[97,2910,2911,2912,2915,2916,2919],{},"Build and push a tiny Node API image to a registry your local cluster can access, replace the digest, and apply the manifest. Watch Pods with ",[119,2913,2914],{},"kubectl get pods -w",", inspect logs, and use ",[119,2917,2918],{},"kubectl port-forward deployment\u002Fweb-api 3000:3000"," to call it. Delete a Pod, scale to three, and explain which controller performed each recovery action.",[101,2921,734],{"id":733},[736,2923,2924,2931],{},[739,2925,2926],{},[742,2927,2930],{"href":2928,"rel":2929},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fpods\u002F",[746],"Kubernetes: Pods",[739,2932,2933],{},[742,2934,2937],{"href":2935,"rel":2936},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fcontrollers\u002Fdeployment\u002F",[746],"Kubernetes: Deployments",[756,2939,2940],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":2942},[2943,2944,2945,2946,2949,2950],{"id":2655,"depth":21,"text":2656},{"id":2668,"depth":21,"text":2669},{"id":2851,"depth":21,"text":2852},{"id":2869,"depth":21,"text":2870,"children":2947},[2948],{"id":2895,"depth":26,"text":2896},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Pods are replaceable execution envelopes; Deployments are the durable declaration most web services should own. The practical distinction prevents a…",{},"\u002Fkubernetes-for-web-applications\u002Fday-10",{"title":2643,"description":2951},"kubernetes-for-web-applications\u002Fday-10","Ql4qq_qf8iasB5txZLMnfPptAT1b-sVYzMdpeLLAbRc",{"id":2958,"title":2959,"body":2960,"day":310,"description":3311,"extension":769,"meta":3312,"navigation":147,"path":3313,"seo":3314,"stem":3315,"tag":774,"week":26,"weekName":52,"__hash__":3316},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-11.md","Services and internal application networking",{"type":89,"value":2961,"toc":3301},[2962,2966,2969,2973,2976,3070,3081,3085,3100,3103,3213,3216,3220,3223,3239,3243,3246,3253,3257,3260,3263,3265,3280,3282,3298],[92,2963,2965],{"id":2964},"day-11-services-and-internal-application-networking","Day 11 — Services and internal application networking",[97,2967,2968],{},"Pods are disposable and their IP addresses change, so callers need a stable abstraction that represents a logical backend rather than an instance. Kubernetes Services provide that stable name and virtual endpoint, allowing frontend, API, and worker components to evolve independently.",[101,2970,2972],{"id":2971},"a-service-selects-a-changing-backend-set","A Service selects a changing backend set",[97,2974,2975],{},"A ClusterIP Service receives a stable virtual IP and DNS name inside the cluster. Its selector matches Pod labels, and Kubernetes maintains EndpointSlices containing the currently eligible Pod addresses. Clients connect to the Service; cluster networking forwards each connection to one backend.",[112,2977,2979],{"className":469,"code":2978,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: Service\nmetadata:\n  name: api\nspec:\n  selector:\n    app: web-api\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n",[119,2980,2981,2989,2998,3004,3012,3018,3024,3033,3040,3051,3061],{"__ignoreMap":117},[122,2982,2983,2985,2987],{"class":124,"line":12},[122,2984,479],{"class":478},[122,2986,482],{"class":131},[122,2988,485],{"class":138},[122,2990,2991,2993,2995],{"class":124,"line":21},[122,2992,490],{"class":478},[122,2994,482],{"class":131},[122,2996,2997],{"class":138},"Service\n",[122,2999,3000,3002],{"class":124,"line":26},[122,3001,500],{"class":478},[122,3003,503],{"class":131},[122,3005,3006,3008,3010],{"class":124,"line":31},[122,3007,508],{"class":478},[122,3009,482],{"class":131},[122,3011,2806],{"class":138},[122,3013,3014,3016],{"class":124,"line":58},[122,3015,2569],{"class":478},[122,3017,503],{"class":131},[122,3019,3020,3022],{"class":124,"line":231},[122,3021,2731],{"class":478},[122,3023,503],{"class":131},[122,3025,3026,3029,3031],{"class":124,"line":264},[122,3027,3028],{"class":478},"    app",[122,3030,482],{"class":131},[122,3032,2710],{"class":138},[122,3034,3035,3038],{"class":124,"line":272},[122,3036,3037],{"class":478},"  ports",[122,3039,503],{"class":131},[122,3041,3042,3045,3047,3049],{"class":124,"line":278},[122,3043,3044],{"class":131},"    - ",[122,3046,2801],{"class":478},[122,3048,482],{"class":131},[122,3050,2835],{"class":138},[122,3052,3053,3056,3058],{"class":124,"line":285},[122,3054,3055],{"class":478},"      port",[122,3057,482],{"class":131},[122,3059,3060],{"class":162},"80\n",[122,3062,3063,3066,3068],{"class":124,"line":310},[122,3064,3065],{"class":478},"      targetPort",[122,3067,482],{"class":131},[122,3069,2835],{"class":138},[97,3071,3072,3073,3076,3077,3080],{},"Here ",[119,3074,3075],{},"port"," is the Service port and ",[119,3078,3079],{},"targetPort"," resolves the named container port, such as 3000. Naming ports prevents duplicated numbers and makes intent clearer. A Service selector must match the Deployment’s Pod labels, not the Deployment metadata alone.",[101,3082,3084],{"id":3083},"dns-is-the-normal-discovery-mechanism","DNS is the normal discovery mechanism",[97,3086,3087,3088,3091,3092,3095,3096,3099],{},"Within the same namespace, a frontend can call ",[119,3089,3090],{},"http:\u002F\u002Fapi",". The complete name is ",[119,3093,3094],{},"api.\u003Cnamespace>.svc.cluster.local",", though code should rarely hard-code the cluster domain. A Service in another namespace can be reached as ",[119,3097,3098],{},"api.backend"," or by its fully qualified name.",[97,3101,3102],{},"Service DNS resolves infrastructure location, not application configuration. Keep the base URL configurable so the same image works locally and in each cluster. For server-side Node code:",[112,3104,3106],{"className":805,"code":3105,"language":807,"meta":117,"style":117},"const apiBase = process.env.API_BASE_URL ?? 'http:\u002F\u002Fapi';\nconst response = await fetch(`${apiBase}\u002Fv1\u002Fproducts`, {\n  signal: AbortSignal.timeout(2000)\n});\nif (!response.ok) throw new Error(`API returned ${response.status}`);\n",[119,3107,3108,3130,3159,3175,3179],{"__ignoreMap":117},[122,3109,3110,3112,3115,3117,3120,3123,3125,3128],{"class":124,"line":12},[122,3111,170],{"class":127},[122,3113,3114],{"class":162}," apiBase",[122,3116,176],{"class":127},[122,3118,3119],{"class":131}," process.env.",[122,3121,3122],{"class":162},"API_BASE_URL",[122,3124,847],{"class":127},[122,3126,3127],{"class":138}," 'http:\u002F\u002Fapi'",[122,3129,142],{"class":131},[122,3131,3132,3134,3137,3139,3142,3145,3147,3150,3153,3156],{"class":124,"line":21},[122,3133,170],{"class":127},[122,3135,3136],{"class":162}," response",[122,3138,176],{"class":127},[122,3140,3141],{"class":127}," await",[122,3143,3144],{"class":182}," fetch",[122,3146,186],{"class":131},[122,3148,3149],{"class":138},"`${",[122,3151,3152],{"class":131},"apiBase",[122,3154,3155],{"class":138},"}\u002Fv1\u002Fproducts`",[122,3157,3158],{"class":131},", {\n",[122,3160,3161,3164,3167,3169,3172],{"class":124,"line":26},[122,3162,3163],{"class":131},"  signal: AbortSignal.",[122,3165,3166],{"class":182},"timeout",[122,3168,186],{"class":131},[122,3170,3171],{"class":162},"2000",[122,3173,3174],{"class":131},")\n",[122,3176,3177],{"class":124,"line":31},[122,3178,313],{"class":131},[122,3180,3181,3183,3185,3187,3190,3192,3194,3196,3198,3201,3204,3206,3208,3211],{"class":124,"line":58},[122,3182,1840],{"class":127},[122,3184,192],{"class":131},[122,3186,1845],{"class":127},[122,3188,3189],{"class":131},"response.ok) ",[122,3191,1870],{"class":127},[122,3193,1873],{"class":127},[122,3195,1876],{"class":182},[122,3197,186],{"class":131},[122,3199,3200],{"class":138},"`API returned ${",[122,3202,3203],{"class":131},"response",[122,3205,916],{"class":138},[122,3207,2573],{"class":131},[122,3209,3210],{"class":138},"}`",[122,3212,307],{"class":131},[97,3214,3215],{},"Browser JavaScript cannot normally resolve cluster-internal names. A Nuxt server may call the internal Service during server rendering, while browser requests must use a public route or same-origin proxy.",[101,3217,3219],{"id":3218},"readiness-controls-membership","Readiness controls membership",[97,3221,3222],{},"EndpointSlices normally include only Pods considered ready. When a readiness probe fails, Kubernetes removes that Pod from ordinary Service routing without necessarily restarting it. This is the key link between health checks and safe traffic management.",[97,3224,3225,3226,199,3229,1044,3232,3235,3236,3238],{},"A Service with no endpoints usually indicates a selector mismatch or no ready Pods. Diagnose with ",[119,3227,3228],{},"kubectl get service api -o yaml",[119,3230,3231],{},"kubectl get endpointslice -l kubernetes.io\u002Fservice-name=api",[119,3233,3234],{},"kubectl get pods --show-labels",". If endpoints exist, test DNS, ports, NetworkPolicies, and application binding. The app must listen on ",[119,3237,1239],{},", not only loopback.",[101,3240,3242],{"id":3241},"service-types-solve-different-exposure-problems","Service types solve different exposure problems",[97,3244,3245],{},"ClusterIP is the default for internal dependencies. NodePort exposes a port on every node and is usually an implementation building block or development convenience. LoadBalancer asks an integration to provision an external load balancer. ExternalName returns a DNS alias and provides no proxying or health checking.",[97,3247,3248,3249,3252],{},"For web applications, prefer ClusterIP behind Ingress or Gateway so HTTP routing, certificates, and a shared load balancer stay centralized. Do not make an internal database publicly reachable merely because ",[119,3250,3251],{},"type: LoadBalancer"," is easy to add.",[1015,3254,3256],{"id":3255},"connections-are-balanced-not-individual-requests","Connections are balanced, not individual requests",[97,3258,3259],{},"Service routing normally chooses a backend when a connection is established. HTTP keep-alive, HTTP\u002F2 multiplexing, WebSockets, and database pools can therefore keep traffic on one Pod longer than a simple request-by-request model suggests. A small number of long-lived connections may distribute unevenly even when the Service is functioning correctly.",[97,3261,3262],{},"Do not solve this immediately with client affinity, which can worsen hotspots and complicate replacement. First measure connection patterns and configure reasonable keep-alive, maximum connection age, and drain behavior at the client or proxy. If the application relies on sticky in-memory sessions, externalize the session instead. A stable Service endpoint removes Pod identity from discovery; the application should preserve that independence at the protocol layer too.",[101,3264,723],{"id":722},[97,3266,3267,3268,3271,3272,3275,3276,3279],{},"Deploy two API replicas and create the Service above. From a temporary ",[119,3269,3270],{},"node:22"," Pod, resolve ",[119,3273,3274],{},"api"," with ",[119,3277,3278],{},"getent hosts api"," if available and fetch it repeatedly. Compare the Service address with EndpointSlice addresses. Break the selector, observe the empty backend set, then repair it. Finally, mark one Pod unready and watch it disappear from eligible endpoints.",[101,3281,734],{"id":733},[736,3283,3284,3291],{},[739,3285,3286],{},[742,3287,3290],{"href":3288,"rel":3289},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fservice\u002F",[746],"Kubernetes: Services",[739,3292,3293],{},[742,3294,3297],{"href":3295,"rel":3296},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fdns-pod-service\u002F",[746],"Kubernetes: DNS for Services and Pods",[756,3299,3300],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":117,"searchDepth":21,"depth":21,"links":3302},[3303,3304,3305,3306,3309,3310],{"id":2971,"depth":21,"text":2972},{"id":3083,"depth":21,"text":3084},{"id":3218,"depth":21,"text":3219},{"id":3241,"depth":21,"text":3242,"children":3307},[3308],{"id":3255,"depth":26,"text":3256},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Pods are disposable and their IP addresses change, so callers need a stable abstraction that represents a logical backend rather than an instance.…",{},"\u002Fkubernetes-for-web-applications\u002Fday-11",{"title":2959,"description":3311},"kubernetes-for-web-applications\u002Fday-11","rBE8SeVaSqhe7E3ishzaHpE3cbTDzxpZu7sA3830JiM",{"id":3318,"title":3319,"body":3320,"day":316,"description":3642,"extension":769,"meta":3643,"navigation":147,"path":3644,"seo":3645,"stem":3646,"tag":774,"week":26,"weekName":52,"__hash__":3647},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-12.md","Public traffic: Ingress and Gateway concepts",{"type":89,"value":3321,"toc":3632},[3322,3326,3329,3333,3336,3516,3523,3527,3530,3541,3545,3560,3563,3567,3577,3580,3584,3591,3594,3596,3612,3614,3630],[92,3323,3325],{"id":3324},"day-12-public-traffic-ingress-and-gateway-concepts","Day 12 — Public traffic: Ingress and Gateway concepts",[97,3327,3328],{},"Public HTTP traffic needs more than a Service: it needs an implementation that accepts connections, terminates TLS, and routes hostnames or paths to internal backends. Kubernetes separates route configuration from the data-plane controller that actually handles packets.",[101,3330,3332],{"id":3331},"ingress-requires-a-controller","Ingress requires a controller",[97,3334,3335],{},"An Ingress is an API object containing HTTP host and path rules. It does nothing unless an Ingress controller watches it and configures a proxy or cloud load balancer. Installing a cluster does not guarantee a controller exists; local distributions vary.",[112,3337,3339],{"className":469,"code":3338,"language":471,"meta":117,"style":117},"apiVersion: networking.k8s.io\u002Fv1\nkind: Ingress\nmetadata:\n  name: web\nspec:\n  ingressClassName: nginx\n  tls:\n    - hosts: [app.example.test]\n      secretName: app-tls\n  rules:\n    - host: app.example.test\n      http:\n        paths:\n          - path: \u002Fapi\n            pathType: Prefix\n            backend:\n              service:\n                name: api\n                port:\n                  number: 80\n",[119,3340,3341,3350,3359,3365,3374,3380,3390,3397,3411,3421,3428,3440,3447,3454,3467,3477,3484,3491,3500,3507],{"__ignoreMap":117},[122,3342,3343,3345,3347],{"class":124,"line":12},[122,3344,479],{"class":478},[122,3346,482],{"class":131},[122,3348,3349],{"class":138},"networking.k8s.io\u002Fv1\n",[122,3351,3352,3354,3356],{"class":124,"line":21},[122,3353,490],{"class":478},[122,3355,482],{"class":131},[122,3357,3358],{"class":138},"Ingress\n",[122,3360,3361,3363],{"class":124,"line":26},[122,3362,500],{"class":478},[122,3364,503],{"class":131},[122,3366,3367,3369,3371],{"class":124,"line":31},[122,3368,508],{"class":478},[122,3370,482],{"class":131},[122,3372,3373],{"class":138},"web\n",[122,3375,3376,3378],{"class":124,"line":58},[122,3377,2569],{"class":478},[122,3379,503],{"class":131},[122,3381,3382,3385,3387],{"class":124,"line":231},[122,3383,3384],{"class":478},"  ingressClassName",[122,3386,482],{"class":131},[122,3388,3389],{"class":138},"nginx\n",[122,3391,3392,3395],{"class":124,"line":264},[122,3393,3394],{"class":478},"  tls",[122,3396,503],{"class":131},[122,3398,3399,3401,3404,3406,3409],{"class":124,"line":272},[122,3400,3044],{"class":131},[122,3402,3403],{"class":478},"hosts",[122,3405,2041],{"class":131},[122,3407,3408],{"class":138},"app.example.test",[122,3410,2047],{"class":131},[122,3412,3413,3416,3418],{"class":124,"line":278},[122,3414,3415],{"class":478},"      secretName",[122,3417,482],{"class":131},[122,3419,3420],{"class":138},"app-tls\n",[122,3422,3423,3426],{"class":124,"line":285},[122,3424,3425],{"class":478},"  rules",[122,3427,503],{"class":131},[122,3429,3430,3432,3435,3437],{"class":124,"line":310},[122,3431,3044],{"class":131},[122,3433,3434],{"class":478},"host",[122,3436,482],{"class":131},[122,3438,3439],{"class":138},"app.example.test\n",[122,3441,3442,3445],{"class":124,"line":316},[122,3443,3444],{"class":478},"      http",[122,3446,503],{"class":131},[122,3448,3449,3452],{"class":124,"line":321},[122,3450,3451],{"class":478},"        paths",[122,3453,503],{"class":131},[122,3455,3456,3459,3462,3464],{"class":124,"line":337},[122,3457,3458],{"class":131},"          - ",[122,3460,3461],{"class":478},"path",[122,3463,482],{"class":131},[122,3465,3466],{"class":138},"\u002Fapi\n",[122,3468,3469,3472,3474],{"class":124,"line":342},[122,3470,3471],{"class":478},"            pathType",[122,3473,482],{"class":131},[122,3475,3476],{"class":138},"Prefix\n",[122,3478,3479,3482],{"class":124,"line":363},[122,3480,3481],{"class":478},"            backend",[122,3483,503],{"class":131},[122,3485,3486,3489],{"class":124,"line":376},[122,3487,3488],{"class":478},"              service",[122,3490,503],{"class":131},[122,3492,3493,3496,3498],{"class":124,"line":417},[122,3494,3495],{"class":478},"                name",[122,3497,482],{"class":131},[122,3499,2806],{"class":138},[122,3501,3502,3505],{"class":124,"line":450},[122,3503,3504],{"class":478},"                port",[122,3506,503],{"class":131},[122,3508,3509,3512,3514],{"class":124,"line":2172},[122,3510,3511],{"class":478},"                  number",[122,3513,482],{"class":131},[122,3515,3060],{"class":162},[97,3517,3518,3519,3522],{},"The controller’s external address receives traffic; the rule sends ",[119,3520,3521],{},"\u002Fapi"," to the ClusterIP Service. Controller-specific annotations can add behavior, but too many make manifests nonportable and difficult to review.",[101,3524,3526],{"id":3525},"tls-terminates-at-an-explicit-boundary","TLS terminates at an explicit boundary",[97,3528,3529],{},"The Ingress controller commonly terminates TLS using a certificate stored in a Secret. A certificate controller such as cert-manager can automate issuance and renewal. Traffic from the controller to the Service may remain plain HTTP inside the cluster or use TLS again, depending on threat model and platform support.",[97,3531,3532,3533,3536,3537,3540],{},"Decide where the original scheme and client address are trusted. Reverse proxies normally add ",[119,3534,3535],{},"Forwarded"," or ",[119,3538,3539],{},"X-Forwarded-*"," headers. Configure Node’s proxy trust only for known proxy hops; blindly trusting headers lets direct clients spoof protocol or IP. Redirect HTTP to HTTPS at the edge and ensure secure cookies are generated correctly behind the proxy.",[101,3542,3544],{"id":3543},"gateway-api-offers-clearer-ownership","Gateway API offers clearer ownership",[97,3546,3547,3548,3551,3552,3555,3556,3559],{},"Gateway API separates infrastructure from application routing. A ",[119,3549,3550],{},"GatewayClass"," identifies an implementation, a ",[119,3553,3554],{},"Gateway"," describes listeners owned by a platform team, and an ",[119,3557,3558],{},"HTTPRoute"," describes application routes that attach with explicit permission. This supports shared gateways and cross-namespace delegation more cleanly than a large collection of controller annotations.",[97,3561,3562],{},"Ingress remains widely deployed and sufficient for basic host\u002Fpath routing. Gateway API is the stronger default when your chosen provider supports the required features and the organization needs role separation or richer routing. The concrete capabilities still depend on the installed controller; API objects alone do not create a load balancer.",[101,3564,3566],{"id":3565},"routing-must-preserve-application-semantics","Routing must preserve application semantics",[97,3568,3569,3570,3572,3573,3576],{},"Clarify whether ",[119,3571,3521],{}," is preserved or stripped before reaching Node. Different controllers and rewrite settings behave differently. Prefer applications that can operate under a configured base path or use separate hosts such as ",[119,3574,3575],{},"api.example.com"," when that simplifies cookies, generated URLs, and observability.",[97,3578,3579],{},"Set request and upstream timeouts deliberately. WebSockets, streaming responses, large uploads, and server-sent events may need controller configuration. Health checks for the proxy backend should use readiness behavior, while user-facing error handling should distinguish edge failures from application responses.",[1015,3581,3583],{"id":3582},"treat-routing-configuration-as-an-api-contract","Treat routing configuration as an API contract",[97,3585,3586,3587,3590],{},"Hostnames, paths, redirects, header handling, body-size limits, and timeouts affect clients and should be reviewed like application interfaces. Test exact and prefix matching, trailing slashes, encoded paths, and query strings. A broad ",[119,3588,3589],{},"\u002F"," route can unintentionally capture traffic intended for another component, while an incorrect rewrite may bypass application authorization assumptions.",[97,3592,3593],{},"Expose a clear edge error identity so operators can distinguish a proxy-generated 502 or 504 from a Node response. Include ingress metrics and access logs in the request investigation path, with privacy controls. During migrations between controllers, run conformance and application-level tests because annotation names and subtle routing behavior rarely transfer one-for-one.",[101,3595,723],{"id":722},[97,3597,3598,3599,3601,3602,3604,3605,3607,3608,3611],{},"Install or enable one local Ingress controller. Map ",[119,3600,3408],{}," to its address in your hosts file, route ",[119,3603,3589],{}," to Nuxt and ",[119,3606,3521],{}," to Node, and verify both with ",[119,3609,3610],{},"curl -v",". Add a locally trusted or self-signed TLS certificate, inspect the handshake, and confirm the application sees the intended host, scheme, and path.",[101,3613,734],{"id":733},[736,3615,3616,3623],{},[739,3617,3618],{},[742,3619,3622],{"href":3620,"rel":3621},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fingress\u002F",[746],"Kubernetes: Ingress",[739,3624,3625],{},[742,3626,3629],{"href":3627,"rel":3628},"https:\u002F\u002Fgateway-api.sigs.k8s.io\u002F",[746],"Gateway API documentation",[756,3631,2940],{},{"title":117,"searchDepth":21,"depth":21,"links":3633},[3634,3635,3636,3637,3640,3641],{"id":3331,"depth":21,"text":3332},{"id":3525,"depth":21,"text":3526},{"id":3543,"depth":21,"text":3544},{"id":3565,"depth":21,"text":3566,"children":3638},[3639],{"id":3582,"depth":26,"text":3583},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Public HTTP traffic needs more than a Service: it needs an implementation that accepts connections, terminates TLS, and routes hostnames or paths to…",{},"\u002Fkubernetes-for-web-applications\u002Fday-12",{"title":3319,"description":3642},"kubernetes-for-web-applications\u002Fday-12","5icKLapTLy-7uAbrRFGi5Romc8G8eHX4SjWGpAhMysM",{"id":3649,"title":3650,"body":3651,"day":321,"description":3856,"extension":769,"meta":3857,"navigation":147,"path":3858,"seo":3859,"stem":3860,"tag":774,"week":26,"weekName":52,"__hash__":3861},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-13.md","Configuration, secrets, and the twelve-factor boundary",{"type":89,"value":3652,"toc":3846},[3653,3657,3660,3664,3667,3670,3674,3680,3780,3783,3786,3790,3797,3800,3804,3807,3810,3814,3817,3820,3822,3825,3827,3843],[92,3654,3656],{"id":3655},"day-13-configuration-secrets-and-the-twelve-factor-boundary","Day 13 — Configuration, secrets, and the twelve-factor boundary",[97,3658,3659],{},"One image should move through environments while configuration changes around it. Kubernetes provides ConfigMaps and Secrets as delivery mechanisms, but application teams still own validation, exposure risk, and a workable rotation strategy.",[101,3661,3663],{"id":3662},"separate-build-output-from-deploy-time-configuration","Separate build output from deploy-time configuration",[97,3665,3666],{},"Configuration includes service URLs, feature flags, log levels, and tuning values that differ by environment. Secrets include credentials, signing keys, and tokens whose disclosure has security impact. Neither belongs in the image, source repository, or browser bundle.",[97,3668,3669],{},"Nuxt makes this boundary especially important: values exposed through public runtime configuration can reach the browser. Server-only credentials must remain in server runtime configuration. Treat every value shipped to client JavaScript as public, regardless of its environment-variable name.",[101,3671,3673],{"id":3672},"environment-variables-are-simple-but-static","Environment variables are simple but static",[97,3675,3676,3677,582],{},"ConfigMaps and Secrets can populate individual environment variables or all keys with ",[119,3678,3679],{},"envFrom",[112,3681,3683],{"className":469,"code":3682,"language":471,"meta":117,"style":117},"env:\n  - name: DATABASE_URL\n    valueFrom:\n      secretKeyRef:\n        name: api-database\n        key: url\n  - name: LOG_LEVEL\n    valueFrom:\n      configMapKeyRef:\n        name: api-config\n        key: logLevel\n",[119,3684,3685,3692,3704,3711,3718,3728,3738,3749,3755,3762,3771],{"__ignoreMap":117},[122,3686,3687,3690],{"class":124,"line":12},[122,3688,3689],{"class":478},"env",[122,3691,503],{"class":131},[122,3693,3694,3697,3699,3701],{"class":124,"line":21},[122,3695,3696],{"class":131},"  - ",[122,3698,2801],{"class":478},[122,3700,482],{"class":131},[122,3702,3703],{"class":138},"DATABASE_URL\n",[122,3705,3706,3709],{"class":124,"line":26},[122,3707,3708],{"class":478},"    valueFrom",[122,3710,503],{"class":131},[122,3712,3713,3716],{"class":124,"line":31},[122,3714,3715],{"class":478},"      secretKeyRef",[122,3717,503],{"class":131},[122,3719,3720,3723,3725],{"class":124,"line":58},[122,3721,3722],{"class":478},"        name",[122,3724,482],{"class":131},[122,3726,3727],{"class":138},"api-database\n",[122,3729,3730,3733,3735],{"class":124,"line":231},[122,3731,3732],{"class":478},"        key",[122,3734,482],{"class":131},[122,3736,3737],{"class":138},"url\n",[122,3739,3740,3742,3744,3746],{"class":124,"line":264},[122,3741,3696],{"class":131},[122,3743,2801],{"class":478},[122,3745,482],{"class":131},[122,3747,3748],{"class":138},"LOG_LEVEL\n",[122,3750,3751,3753],{"class":124,"line":272},[122,3752,3708],{"class":478},[122,3754,503],{"class":131},[122,3756,3757,3760],{"class":124,"line":278},[122,3758,3759],{"class":478},"      configMapKeyRef",[122,3761,503],{"class":131},[122,3763,3764,3766,3768],{"class":124,"line":285},[122,3765,3722],{"class":478},[122,3767,482],{"class":131},[122,3769,3770],{"class":138},"api-config\n",[122,3772,3773,3775,3777],{"class":124,"line":310},[122,3774,3732],{"class":478},[122,3776,482],{"class":131},[122,3778,3779],{"class":138},"logLevel\n",[97,3781,3782],{},"Environment values are captured when the container starts. Updating the source object does not change existing process environments, so a rollout is required. Also, environment variables may appear in diagnostic output and child processes. Never dump the complete environment into logs.",[97,3784,3785],{},"Validate configuration once at startup with a schema. A Node service should fail with a precise message for a missing variable, invalid URL, or unsupported enum. Avoid silently defaulting security-relevant values such as cookie signing secrets.",[101,3787,3789],{"id":3788},"mounted-files-support-updates-with-caveats","Mounted files support updates with caveats",[97,3791,3792,3793,3796],{},"ConfigMaps and Secrets can be mounted as files. Kubelet updates projected volume contents eventually, which can support certificate or policy reloads. Mounting a single key with ",[119,3794,3795],{},"subPath"," prevents these automatic updates. The application must also watch or periodically reread the file; a process that loads it only at startup still needs restarting.",[97,3798,3799],{},"Use immutable configuration names containing a content hash when predictable rollout and rollback matter. Update the Pod template to reference the new name, triggering a Deployment rollout. This avoids invisible drift and ensures old revisions keep their original configuration identity.",[101,3801,3803],{"id":3802},"a-kubernetes-secret-is-not-a-vault","A Kubernetes Secret is not a vault",[97,3805,3806],{},"Secret data in YAML is base64 encoded, not encrypted by that fact. Anyone who can read the Secret through the API can recover it. Clusters should encrypt Secret data at rest, and RBAC should restrict access by namespace and service account. Avoid broad list\u002Fwatch permission because it exposes every matching value.",[97,3808,3809],{},"For mature environments, an external secret manager plus CSI driver or synchronizing operator can provide centralized rotation and cloud identity. The delivered credential still exists in process memory or a mounted file, so least privilege and short lifetimes remain necessary. Rotation must be tested through both credential overlap and connection-pool renewal.",[1015,3811,3813],{"id":3812},"configuration-changes-are-releases","Configuration changes are releases",[97,3815,3816],{},"A log-level change may be low risk, but a feature flag, upstream URL, or pool size can alter user behavior as much as code. Give configuration an owner, schema, review path, and revision identity. Include the non-secret configuration version in deployment metadata and telemetry so incidents can correlate behavior with a change.",[97,3818,3819],{},"Decide whether each setting is read once, dynamically reloaded, or requires a rollout. Dynamic reload adds code paths that need validation and rollback; restarting through a Deployment may be simpler and more observable. Avoid configuration that changes independently on every request unless the external control plane has defined availability, caching, and failure semantics.",[101,3821,723],{"id":722},[97,3823,3824],{},"Create a ConfigMap for log level and a Secret for a disposable API token using command-line input, not a committed manifest. Inject both into a Node Deployment and validate them at startup. Change each value and observe that environment-based Pods do not update automatically; perform a rollout and verify the new configuration without printing the secret.",[101,3826,734],{"id":733},[736,3828,3829,3836],{},[739,3830,3831],{},[742,3832,3835],{"href":3833,"rel":3834},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fconfigure-pod-container\u002Fconfigure-pod-configmap\u002F",[746],"Kubernetes: Configure a Pod with ConfigMaps",[739,3837,3838],{},[742,3839,3842],{"href":3840,"rel":3841},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fconfiguration\u002Fsecret\u002F",[746],"Kubernetes: Secrets",[756,3844,3845],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":3847},[3848,3849,3850,3851,3854,3855],{"id":3662,"depth":21,"text":3663},{"id":3672,"depth":21,"text":3673},{"id":3788,"depth":21,"text":3789},{"id":3802,"depth":21,"text":3803,"children":3852},[3853],{"id":3812,"depth":26,"text":3813},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"One image should move through environments while configuration changes around it. Kubernetes provides ConfigMaps and Secrets as delivery mechanisms,…",{},"\u002Fkubernetes-for-web-applications\u002Fday-13",{"title":3650,"description":3856},"kubernetes-for-web-applications\u002Fday-13","6MZw6gv1LdrnKddZBU4I5DeM9xruCA49IGhCnmykLb8",{"id":3863,"title":3864,"body":3865,"day":337,"description":4061,"extension":769,"meta":4062,"navigation":147,"path":4063,"seo":4064,"stem":4065,"tag":774,"week":26,"weekName":52,"__hash__":4066},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-14.md","Persistent data and stateful dependencies",{"type":89,"value":3866,"toc":4051},[3867,3871,3874,3878,3885,3888,3977,3983,3987,3990,3993,3997,4000,4003,4007,4010,4013,4017,4020,4023,4025,4031,4033,4049],[92,3868,3870],{"id":3869},"day-14-persistent-data-and-stateful-dependencies","Day 14 — Persistent data and stateful dependencies",[97,3872,3873],{},"Containers and Pods are replaceable; business data is not. Kubernetes can attach durable storage, but a volume only solves byte persistence—it does not provide database correctness, replication, backup, or operational ownership.",[101,3875,3877],{"id":3876},"volumes-have-different-lifetimes","Volumes have different lifetimes",[97,3879,3880,3881,3884],{},"A Pod volume such as ",[119,3882,3883],{},"emptyDir"," lives for the Pod lifetime and can be shared by its containers. It survives a container restart but disappears when the Pod is replaced. It is appropriate for caches, temporary transforms, or shared scratch data, subject to node capacity.",[97,3886,3887],{},"A PersistentVolume represents storage available to the cluster. A PersistentVolumeClaim requests capacity and access characteristics, and a StorageClass describes dynamic provisioning behavior. Applications normally create claims; a CSI driver and infrastructure provider handle the underlying disk or filesystem.",[112,3889,3891],{"className":469,"code":3890,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: uploads\nspec:\n  accessModes: [ReadWriteOnce]\n  resources:\n    requests:\n      storage: 10Gi\n  storageClassName: standard\n",[119,3892,3893,3901,3910,3916,3925,3931,3943,3950,3957,3967],{"__ignoreMap":117},[122,3894,3895,3897,3899],{"class":124,"line":12},[122,3896,479],{"class":478},[122,3898,482],{"class":131},[122,3900,485],{"class":138},[122,3902,3903,3905,3907],{"class":124,"line":21},[122,3904,490],{"class":478},[122,3906,482],{"class":131},[122,3908,3909],{"class":138},"PersistentVolumeClaim\n",[122,3911,3912,3914],{"class":124,"line":26},[122,3913,500],{"class":478},[122,3915,503],{"class":131},[122,3917,3918,3920,3922],{"class":124,"line":31},[122,3919,508],{"class":478},[122,3921,482],{"class":131},[122,3923,3924],{"class":138},"uploads\n",[122,3926,3927,3929],{"class":124,"line":58},[122,3928,2569],{"class":478},[122,3930,503],{"class":131},[122,3932,3933,3936,3938,3941],{"class":124,"line":231},[122,3934,3935],{"class":478},"  accessModes",[122,3937,2041],{"class":131},[122,3939,3940],{"class":138},"ReadWriteOnce",[122,3942,2047],{"class":131},[122,3944,3945,3948],{"class":124,"line":264},[122,3946,3947],{"class":478},"  resources",[122,3949,503],{"class":131},[122,3951,3952,3955],{"class":124,"line":272},[122,3953,3954],{"class":478},"    requests",[122,3956,503],{"class":131},[122,3958,3959,3962,3964],{"class":124,"line":278},[122,3960,3961],{"class":478},"      storage",[122,3963,482],{"class":131},[122,3965,3966],{"class":138},"10Gi\n",[122,3968,3969,3972,3974],{"class":124,"line":285},[122,3970,3971],{"class":478},"  storageClassName",[122,3973,482],{"class":131},[122,3975,3976],{"class":138},"standard\n",[97,3978,3979,3980,3982],{},"Access modes describe supported attachment semantics, not application-level locking. ",[119,3981,3940],{}," commonly permits write mounting from one node, which can constrain replica placement.",[101,3984,3986],{"id":3985},"stateful-workloads-carry-identity-and-ordering","Stateful workloads carry identity and ordering",[97,3988,3989],{},"A Deployment treats replicas as interchangeable. Databases often need stable identity, ordered rollout, persistent volume association, replication membership, and controlled failover. StatefulSets provide stable Pod names and per-replica claims, but they do not configure PostgreSQL replication, prevent split brain, or test restores.",[97,3991,3992],{},"Operators can encode database-specific lifecycle knowledge, yet they create another software system to understand and upgrade. Before running a database in-cluster, identify who owns patches, backups, point-in-time recovery, failover drills, capacity, and after-hours incidents.",[101,3994,3996],{"id":3995},"managed-data-services-are-often-the-application-choice","Managed data services are often the application choice",[97,3998,3999],{},"A managed database usually provides automated backups, monitored replication, maintenance workflows, and a support boundary. It may cost more per unit and introduce provider coupling, but it lets a small product team concentrate on schema and query behavior rather than storage orchestration.",[97,4001,4002],{},"Connectivity still needs engineering. Use private networking where possible, enforce TLS, obtain credentials through workload identity or secret delivery, configure connection timeouts, and size pools across all replicas. Ten Pods each opening 50 connections can exhaust a modest database. A pooling proxy may help, but it also requires capacity and observability.",[101,4004,4006],{"id":4005},"data-lifecycle-must-be-explicit","Data lifecycle must be explicit",[97,4008,4009],{},"Uploaded files rarely belong on a Node container filesystem. Object storage offers durable, scalable storage and direct delivery patterns. If a shared filesystem is genuinely required, understand latency, concurrency, and zone availability. For any persistent system, define retention, deletion, encryption, backup frequency, recovery objectives, and restore tests.",[97,4011,4012],{},"PVC deletion and underlying-volume deletion are separate decisions governed by reclaim policy and application workflows. Test what happens when a namespace, claim, or StatefulSet is removed. A backup that has never been restored is an assumption, not a recovery capability.",[1015,4014,4016],{"id":4015},"availability-and-durability-are-different-goals","Availability and durability are different goals",[97,4018,4019],{},"Replication can keep a service available after one instance fails, but it may faithfully replicate accidental deletion or corruption. Backups preserve recoverable history, but a backup stored in the same failure domain or protected by the same compromised credentials may not survive the incident. Define recovery point and recovery time objectives from product impact, then design both replication and backup around them.",[97,4021,4022],{},"Applications participate in recovery. Schema migrations need compatible restore procedures, object references must remain consistent, and caches should rebuild safely. Practice restoring into an isolated environment, validate application queries and counts, and record the time and manual decisions. This evidence is more valuable than a dashboard showing that scheduled backups completed.",[101,4024,723],{"id":722},[97,4026,4027,4028,4030],{},"Create an ",[119,4029,3883],{}," mount in a two-container Pod and prove both containers share it; then delete the Pod and observe data loss. Create a PVC on your local cluster, mount it into a replacement Pod, and verify persistence. Finally, produce a short decision record comparing managed PostgreSQL with in-cluster PostgreSQL for ownership, recovery, cost, and latency.",[101,4032,734],{"id":733},[736,4034,4035,4042],{},[739,4036,4037],{},[742,4038,4041],{"href":4039,"rel":4040},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fstorage\u002Fpersistent-volumes\u002F",[746],"Kubernetes: Persistent Volumes",[739,4043,4044],{},[742,4045,4048],{"href":4046,"rel":4047},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fcontrollers\u002Fstatefulset\u002F",[746],"Kubernetes: StatefulSets",[756,4050,3845],{},{"title":117,"searchDepth":21,"depth":21,"links":4052},[4053,4054,4055,4056,4059,4060],{"id":3876,"depth":21,"text":3877},{"id":3985,"depth":21,"text":3986},{"id":3995,"depth":21,"text":3996},{"id":4005,"depth":21,"text":4006,"children":4057},[4058],{"id":4015,"depth":26,"text":4016},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Containers and Pods are replaceable; business data is not. Kubernetes can attach durable storage, but a volume only solves byte persistence—it does not…",{},"\u002Fkubernetes-for-web-applications\u002Fday-14",{"title":3864,"description":4061},"kubernetes-for-web-applications\u002Fday-14","M_PdsKFv6AwudvpLl-0yHOjZaCUw9Ry86Q_COd71vjE",{"id":4068,"title":4069,"body":4070,"day":342,"description":4265,"extension":769,"meta":4266,"navigation":147,"path":4267,"seo":4268,"stem":4269,"tag":774,"week":26,"weekName":52,"__hash__":4270},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-15.md","Resource requests, limits, and predictable capacity",{"type":89,"value":4071,"toc":4255},[4072,4076,4079,4083,4086,4089,4093,4100,4110,4173,4176,4180,4195,4202,4206,4209,4216,4220,4223,4226,4228,4235,4237,4253],[92,4073,4075],{"id":4074},"day-15-resource-requests-limits-and-predictable-capacity","Day 15 — Resource requests, limits, and predictable capacity",[97,4077,4078],{},"Resource settings translate application behavior into scheduling and isolation decisions. Good initial values are measured hypotheses: enough room for normal Node traffic, explicit protection against runaway use, and metrics that support adjustment after deployment.",[101,4080,4082],{"id":4081},"requests-place-pods-and-reserve-capacity","Requests place Pods and reserve capacity",[97,4084,4085],{},"CPU and memory requests tell the scheduler how much node capacity a Pod needs. A Pod is placed only where all requests fit, even if current usage is low. Requests also influence CPU sharing during contention and form the utilization denominator for common Horizontal Pod Autoscaler configurations.",[97,4087,4088],{},"Understated requests overpack nodes and increase contention or eviction risk. Overstated requests waste allocatable capacity and can leave Pods Pending. Measure representative steady-state and peak behavior, then leave margin for traffic variance, runtime overhead, and sidecars.",[101,4090,4092],{"id":4091},"limits-enforce-different-behavior","Limits enforce different behavior",[97,4094,4095,4096,4099],{},"CPU is measured in cores; ",[119,4097,4098],{},"500m"," means half a core. When a container exceeds its CPU limit, the kernel throttles it rather than killing it. Throttling can increase event-loop lag and request latency even while average CPU dashboards look acceptable.",[97,4101,4102,4103,1306,4106,4109],{},"Memory is measured in bytes with suffixes such as ",[119,4104,4105],{},"Mi",[119,4107,4108],{},"Gi",". Exceeding a memory cgroup limit can terminate the process with an OOM kill. Node’s JavaScript heap is only part of resident memory: native buffers, code, thread stacks, and libraries also consume space. Leave headroom between any V8 heap cap and the container memory limit.",[112,4111,4113],{"className":469,"code":4112,"language":471,"meta":117,"style":117},"resources:\n  requests:\n    cpu: 250m\n    memory: 256Mi\n  limits:\n    cpu: \"1\"\n    memory: 512Mi\n",[119,4114,4115,4121,4128,4138,4148,4155,4164],{"__ignoreMap":117},[122,4116,4117,4119],{"class":124,"line":12},[122,4118,733],{"class":478},[122,4120,503],{"class":131},[122,4122,4123,4126],{"class":124,"line":21},[122,4124,4125],{"class":478},"  requests",[122,4127,503],{"class":131},[122,4129,4130,4133,4135],{"class":124,"line":26},[122,4131,4132],{"class":478},"    cpu",[122,4134,482],{"class":131},[122,4136,4137],{"class":138},"250m\n",[122,4139,4140,4143,4145],{"class":124,"line":31},[122,4141,4142],{"class":478},"    memory",[122,4144,482],{"class":131},[122,4146,4147],{"class":138},"256Mi\n",[122,4149,4150,4153],{"class":124,"line":58},[122,4151,4152],{"class":478},"  limits",[122,4154,503],{"class":131},[122,4156,4157,4159,4161],{"class":124,"line":231},[122,4158,4132],{"class":478},[122,4160,482],{"class":131},[122,4162,4163],{"class":138},"\"1\"\n",[122,4165,4166,4168,4170],{"class":124,"line":264},[122,4167,4142],{"class":478},[122,4169,482],{"class":131},[122,4171,4172],{"class":138},"512Mi\n",[97,4174,4175],{},"These are plausible starting numbers, not universal recommendations.",[101,4177,4179],{"id":4178},"qos-and-node-pressure-affect-eviction","QoS and node pressure affect eviction",[97,4181,4182,4183,4186,4187,4190,4191,4194],{},"Kubernetes assigns a Pod a QoS class based on requests and limits. ",[119,4184,4185],{},"Guaranteed"," requires matching CPU and memory request\u002Flimit for every container; ",[119,4188,4189],{},"Burstable"," covers most partially specified workloads; ",[119,4192,4193],{},"BestEffort"," has none. Under node resource pressure, QoS and usage relative to requests influence eviction priority, alongside other factors.",[97,4196,4197,4198,4201],{},"An evicted Pod or OOM-killed container is not the same as an application exception. Inspect Pod status, container ",[119,4199,4200],{},"lastState",", node conditions, and events. Repeatedly increasing limits without finding a leak or unbounded queue merely delays the incident.",[101,4203,4205],{"id":4204},"capacity-planning-begins-with-workload-behavior","Capacity planning begins with workload behavior",[97,4207,4208],{},"Load-test the production image with realistic response sizes, downstream latency, and concurrency. Track request rate, p95\u002Fp99 latency, error rate, CPU, resident memory, heap, garbage collection, and event-loop lag. Determine a safe per-Pod throughput before saturation, then choose replicas and requests that provide failure and rollout headroom.",[97,4210,4211,4212,4215],{},"Remember scheduling arithmetic. During a rolling update, ",[119,4213,4214],{},"maxSurge"," may create extra Pods; during node maintenance, replicas must fit elsewhere. A cluster that runs at near-total requested capacity cannot self-heal smoothly. Resource policy should be paired with namespace quotas and default ranges so omissions fail early or receive intentional defaults.",[1015,4217,4219],{"id":4218},"measure-node-specific-saturation","Measure Node-specific saturation",[97,4221,4222],{},"CPU alone misses important Node bottlenecks. Track event-loop delay, active handles, garbage-collection pauses, heap utilization, external memory, connection-pool wait time, and queue depth. A service can have spare CPU while every request waits on a pool, or high CPU because JSON serialization blocks the event loop. Scaling based on the wrong signal may multiply load on the constrained dependency.",[97,4224,4225],{},"Separate memory leak from legitimate cache growth. Observe resident memory after repeated garbage collection and across a sustained traffic plateau. If memory rises without stabilizing, capture bounded heap evidence in a safe environment. Resource limits contain blast radius, while profiling and application fixes address the cause.",[101,4227,723],{"id":722},[97,4229,4230,4231,4234],{},"Deploy a Node endpoint with the example resources and generate controlled load using a tool such as autocannon. Watch ",[119,4232,4233],{},"kubectl top pods",", latency, restart count, and events. Exercise one CPU-heavy and one memory-heavy route in a disposable environment, observe throttling or OOM behavior, then document safer request, limit, concurrency, and replica values based on evidence.",[101,4236,734],{"id":733},[736,4238,4239,4246],{},[739,4240,4241],{},[742,4242,4245],{"href":4243,"rel":4244},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fconfiguration\u002Fmanage-resources-containers\u002F",[746],"Kubernetes: Resource management for Pods and containers",[739,4247,4248],{},[742,4249,4252],{"href":4250,"rel":4251},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fpods\u002Fpod-qos\u002F",[746],"Kubernetes: Pod quality of service classes",[756,4254,3845],{},{"title":117,"searchDepth":21,"depth":21,"links":4256},[4257,4258,4259,4260,4263,4264],{"id":4081,"depth":21,"text":4082},{"id":4091,"depth":21,"text":4092},{"id":4178,"depth":21,"text":4179},{"id":4204,"depth":21,"text":4205,"children":4261},[4262],{"id":4218,"depth":26,"text":4219},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Resource settings translate application behavior into scheduling and isolation decisions. Good initial values are measured hypotheses: enough room for…",{},"\u002Fkubernetes-for-web-applications\u002Fday-15",{"title":4069,"description":4265},"kubernetes-for-web-applications\u002Fday-15","tlIRS-OPWdy6IU9a-rUVsOpX96rmqDAlHmCvv5Mey_w",{"id":4272,"title":4273,"body":4274,"day":363,"description":4650,"extension":769,"meta":4651,"navigation":147,"path":4652,"seo":4653,"stem":4654,"tag":774,"week":31,"weekName":55,"__hash__":4655},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-16.md","Health checks that support real rollouts",{"type":89,"value":4275,"toc":4640},[4276,4280,4283,4287,4290,4293,4297,4300,4307,4311,4582,4585,4589,4602,4605,4609,4612,4615,4617,4620,4622,4638],[92,4277,4279],{"id":4278},"day-16-health-checks-that-support-real-rollouts","Day 16 — Health checks that support real rollouts",[97,4281,4282],{},"Probes are control inputs, not decorative endpoints. A good health design delays traffic until an instance can serve it, stops traffic during degradation or shutdown, and restarts only processes that cannot recover without intervention.",[101,4284,4286],{"id":4285},"startup-readiness-and-liveness-answer-different-questions","Startup, readiness, and liveness answer different questions",[97,4288,4289],{},"A startup probe asks whether slow initialization has completed. Until it succeeds, Kubernetes does not run liveness or readiness checks, preventing premature restarts. A readiness probe asks whether the Pod should receive Service traffic now. A liveness probe asks whether restarting the container is likely to restore progress.",[97,4291,4292],{},"Do not make all three call one comprehensive endpoint. A temporary database outage should often make an API unready while allowing it to remain alive and reconnect. If liveness depends on the database, every replica may restart simultaneously during a shared dependency incident, adding load and obscuring evidence.",[101,4294,4296],{"id":4295},"readiness-is-traffic-control","Readiness is traffic control",[97,4298,4299],{},"Readiness should reflect the ability to serve the class of requests routed to that Pod. It may check that startup is complete, required clients are initialized, and the instance is not draining. Keep it fast, bounded, and inexpensive. Deep queries against every dependency can amplify outages because probes run frequently across every replica.",[97,4301,4302,4303,4306],{},"During SIGTERM, set readiness false before closing listeners, allowing endpoint propagation to reduce new traffic. Continue in-flight work within ",[119,4304,4305],{},"terminationGracePeriodSeconds",". The exact sequence depends on the ingress data plane, so test it under real rollout traffic rather than assuming zero failed requests.",[101,4308,4310],{"id":4309},"design-explicit-node-endpoints","Design explicit Node endpoints",[112,4312,4314],{"className":805,"code":4313,"language":807,"meta":117,"style":117},"import express from 'express';\n\nconst app = express();\nlet ready = false;\napp.get('\u002Fhealth\u002Flive', (_req, res) => res.sendStatus(204));\napp.get('\u002Fhealth\u002Fready', (_req, res) =>\n  ready ? res.sendStatus(204) : res.status(503).json({ status: 'not-ready' })\n);\n\nconst server = app.listen(3000, '0.0.0.0', async () => {\n  await initializeRequiredClients();\n  ready = true;\n});\n\nprocess.on('SIGTERM', () => {\n  ready = false;\n  server.close(() => process.exit(0));\n});\n",[119,4315,4316,4330,4334,4348,4360,4399,4423,4464,4468,4472,4504,4514,4524,4528,4532,4548,4558,4578],{"__ignoreMap":117},[122,4317,4318,4320,4323,4325,4328],{"class":124,"line":12},[122,4319,128],{"class":127},[122,4321,4322],{"class":131}," express ",[122,4324,135],{"class":127},[122,4326,4327],{"class":138}," 'express'",[122,4329,142],{"class":131},[122,4331,4332],{"class":124,"line":21},[122,4333,148],{"emptyLinePlaceholder":147},[122,4335,4336,4338,4341,4343,4346],{"class":124,"line":26},[122,4337,170],{"class":127},[122,4339,4340],{"class":162}," app",[122,4342,176],{"class":127},[122,4344,4345],{"class":182}," express",[122,4347,261],{"class":131},[122,4349,4350,4352,4354,4356,4358],{"class":124,"line":31},[122,4351,153],{"class":127},[122,4353,156],{"class":131},[122,4355,159],{"class":127},[122,4357,371],{"class":162},[122,4359,142],{"class":131},[122,4361,4362,4365,4367,4369,4372,4375,4378,4380,4382,4384,4386,4389,4392,4394,4397],{"class":124,"line":58},[122,4363,4364],{"class":131},"app.",[122,4366,2547],{"class":182},[122,4368,186],{"class":131},[122,4370,4371],{"class":138},"'\u002Fhealth\u002Flive'",[122,4373,4374],{"class":131},", (",[122,4376,4377],{"class":195},"_req",[122,4379,199],{"class":131},[122,4381,202],{"class":195},[122,4383,205],{"class":131},[122,4385,208],{"class":127},[122,4387,4388],{"class":131}," res.",[122,4390,4391],{"class":182},"sendStatus",[122,4393,186],{"class":131},[122,4395,4396],{"class":162},"204",[122,4398,414],{"class":131},[122,4400,4401,4403,4405,4407,4410,4412,4414,4416,4418,4420],{"class":124,"line":231},[122,4402,4364],{"class":131},[122,4404,2547],{"class":182},[122,4406,186],{"class":131},[122,4408,4409],{"class":138},"'\u002Fhealth\u002Fready'",[122,4411,4374],{"class":131},[122,4413,4377],{"class":195},[122,4415,199],{"class":131},[122,4417,202],{"class":195},[122,4419,205],{"class":131},[122,4421,4422],{"class":127},"=>\n",[122,4424,4425,4427,4429,4431,4433,4435,4437,4439,4441,4443,4445,4447,4450,4452,4455,4458,4461],{"class":124,"line":264},[122,4426,366],{"class":131},[122,4428,243],{"class":127},[122,4430,4388],{"class":131},[122,4432,4391],{"class":182},[122,4434,186],{"class":131},[122,4436,4396],{"class":162},[122,4438,205],{"class":131},[122,4440,582],{"class":127},[122,4442,4388],{"class":131},[122,4444,2573],{"class":182},[122,4446,186],{"class":131},[122,4448,4449],{"class":162},"503",[122,4451,255],{"class":131},[122,4453,4454],{"class":182},"json",[122,4456,4457],{"class":131},"({ status: ",[122,4459,4460],{"class":138},"'not-ready'",[122,4462,4463],{"class":131}," })\n",[122,4465,4466],{"class":124,"line":272},[122,4467,307],{"class":131},[122,4469,4470],{"class":124,"line":278},[122,4471,148],{"emptyLinePlaceholder":147},[122,4473,4474,4476,4478,4480,4483,4485,4487,4489,4491,4493,4495,4497,4500,4502],{"class":124,"line":285},[122,4475,170],{"class":127},[122,4477,173],{"class":162},[122,4479,176],{"class":127},[122,4481,4482],{"class":131}," app.",[122,4484,327],{"class":182},[122,4486,186],{"class":131},[122,4488,332],{"class":162},[122,4490,199],{"class":131},[122,4492,942],{"class":138},[122,4494,199],{"class":131},[122,4496,189],{"class":127},[122,4498,4499],{"class":131}," () ",[122,4501,208],{"class":127},[122,4503,211],{"class":131},[122,4505,4506,4509,4512],{"class":124,"line":310},[122,4507,4508],{"class":127},"  await",[122,4510,4511],{"class":182}," initializeRequiredClients",[122,4513,261],{"class":131},[122,4515,4516,4518,4520,4522],{"class":124,"line":316},[122,4517,366],{"class":131},[122,4519,159],{"class":127},[122,4521,163],{"class":162},[122,4523,142],{"class":131},[122,4525,4526],{"class":124,"line":321},[122,4527,313],{"class":131},[122,4529,4530],{"class":124,"line":337},[122,4531,148],{"emptyLinePlaceholder":147},[122,4533,4534,4536,4538,4540,4542,4544,4546],{"class":124,"line":342},[122,4535,345],{"class":131},[122,4537,348],{"class":182},[122,4539,186],{"class":131},[122,4541,955],{"class":138},[122,4543,356],{"class":131},[122,4545,208],{"class":127},[122,4547,211],{"class":131},[122,4549,4550,4552,4554,4556],{"class":124,"line":363},[122,4551,366],{"class":131},[122,4553,159],{"class":127},[122,4555,371],{"class":162},[122,4557,142],{"class":131},[122,4559,4560,4562,4564,4566,4568,4570,4572,4574,4576],{"class":124,"line":376},[122,4561,379],{"class":131},[122,4563,382],{"class":182},[122,4565,423],{"class":131},[122,4567,208],{"class":127},[122,4569,395],{"class":131},[122,4571,398],{"class":182},[122,4573,186],{"class":131},[122,4575,977],{"class":162},[122,4577,414],{"class":131},[122,4579,4580],{"class":124,"line":417},[122,4581,313],{"class":131},[97,4583,4584],{},"Do not return stack traces, dependency URLs, or secret-bearing errors from unauthenticated health routes. Detailed diagnostics belong in telemetry.",[101,4586,4588],{"id":4587},"tune-probes-from-measured-timing","Tune probes from measured timing",[97,4590,4591,4592,199,4595,1044,4598,4601],{},"Configure ",[119,4593,4594],{},"timeoutSeconds",[119,4596,4597],{},"periodSeconds",[119,4599,4600],{},"failureThreshold"," according to startup and recovery measurements. Liveness should allow enough time to avoid restart loops during short event-loop stalls. Readiness can react faster, but overly aggressive settings cause endpoint flapping.",[97,4603,4604],{},"Prefer HTTP probes for HTTP applications, TCP only when connection acceptance is meaningful, and exec probes when no protocol endpoint exists. Exec probes create processes and can add overhead. Probe the application port directly rather than routing through public ingress; edge health is a separate concern.",[1015,4606,4608],{"id":4607},"probe-failures-need-observability","Probe failures need observability",[97,4610,4611],{},"Kubelet events show that a probe failed, but the application should expose enough internal telemetry to explain why. Count readiness state transitions, record a bounded reason code internally, and graph ready replicas beside errors and dependency health. Do not log every successful probe; frequent access logs add noise and cost.",[97,4613,4614],{},"Protect health endpoints from ordinary middleware surprises. They should not require user authentication, remote feature-flag calls, or rate-limit capacity intended for users. At the same time, keep them narrow and avoid publishing detailed diagnostics through public ingress. A separate authenticated diagnostic endpoint may help operators, but it should not become a probe dependency.",[101,4616,723],{"id":722},[97,4618,4619],{},"Add separate live and ready endpoints to your Node service and configure all three probe types with a deliberately slow startup. Watch Pod conditions and EndpointSlices. Simulate a recoverable downstream outage and confirm the container is not restarted. Then deploy under continuous traffic, send SIGTERM to a Pod, and count any failed requests while tuning shutdown and readiness timing.",[101,4621,734],{"id":733},[736,4623,4624,4631],{},[739,4625,4626],{},[742,4627,4630],{"href":4628,"rel":4629},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fconfigure-pod-container\u002Fconfigure-liveness-readiness-startup-probes\u002F",[746],"Kubernetes: Configure liveness, readiness, and startup probes",[739,4632,4633],{},[742,4634,4637],{"href":4635,"rel":4636},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fpods\u002Fpod-lifecycle\u002F",[746],"Kubernetes: Pod lifecycle and termination",[756,4639,1073],{},{"title":117,"searchDepth":21,"depth":21,"links":4641},[4642,4643,4644,4645,4648,4649],{"id":4285,"depth":21,"text":4286},{"id":4295,"depth":21,"text":4296},{"id":4309,"depth":21,"text":4310},{"id":4587,"depth":21,"text":4588,"children":4646},[4647],{"id":4607,"depth":26,"text":4608},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Probes are control inputs, not decorative endpoints. A good health design delays traffic until an instance can serve it, stops traffic during…",{},"\u002Fkubernetes-for-web-applications\u002Fday-16",{"title":4273,"description":4650},"kubernetes-for-web-applications\u002Fday-16","hU6ZcLJnN6_BDKReTFum1m5HWP9fr11aUi0AULczGuw",{"id":4657,"title":4658,"body":4659,"day":376,"description":4875,"extension":769,"meta":4876,"navigation":147,"path":4877,"seo":4878,"stem":4879,"tag":774,"week":31,"weekName":55,"__hash__":4880},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-17.md","Deployments, rollouts, and safe rollback",{"type":89,"value":4660,"toc":4864},[4661,4665,4668,4672,4688,4766,4769,4773,4783,4786,4790,4796,4799,4803,4810,4813,4817,4820,4829,4833,4836,4839,4841,4844,4846,4862],[92,4662,4664],{"id":4663},"day-17-deployments-rollouts-and-safe-rollback","Day 17 — Deployments, rollouts, and safe rollback",[97,4666,4667],{},"A Deployment can replace replicas gradually, but safety depends on readiness, capacity, and compatibility across versions. A successful rollout is not merely “new Pods started”; it is a controlled transition that preserves user-visible behavior and remains reversible.",[101,4669,4671],{"id":4670},"rolling-updates-balance-availability-and-capacity","Rolling updates balance availability and capacity",[97,4673,4674,4677,4678,4680,4681,1306,4684,4687],{},[119,4675,4676],{},"maxUnavailable"," limits how many desired replicas may be unavailable during an update. ",[119,4679,4214],{}," limits temporary replicas above the desired count. For a four-replica API, ",[119,4682,4683],{},"maxUnavailable: 0",[119,4685,4686],{},"maxSurge: 1"," preserve ready capacity but require room for a fifth Pod.",[112,4689,4691],{"className":469,"code":4690,"language":471,"meta":117,"style":117},"strategy:\n  type: RollingUpdate\n  rollingUpdate:\n    maxUnavailable: 0\n    maxSurge: 1\nminReadySeconds: 10\nprogressDeadlineSeconds: 600\nrevisionHistoryLimit: 5\n",[119,4692,4693,4700,4710,4717,4727,4737,4746,4756],{"__ignoreMap":117},[122,4694,4695,4698],{"class":124,"line":12},[122,4696,4697],{"class":478},"strategy",[122,4699,503],{"class":131},[122,4701,4702,4705,4707],{"class":124,"line":21},[122,4703,4704],{"class":478},"  type",[122,4706,482],{"class":131},[122,4708,4709],{"class":138},"RollingUpdate\n",[122,4711,4712,4715],{"class":124,"line":26},[122,4713,4714],{"class":478},"  rollingUpdate",[122,4716,503],{"class":131},[122,4718,4719,4722,4724],{"class":124,"line":31},[122,4720,4721],{"class":478},"    maxUnavailable",[122,4723,482],{"class":131},[122,4725,4726],{"class":162},"0\n",[122,4728,4729,4732,4734],{"class":124,"line":58},[122,4730,4731],{"class":478},"    maxSurge",[122,4733,482],{"class":131},[122,4735,4736],{"class":162},"1\n",[122,4738,4739,4742,4744],{"class":124,"line":231},[122,4740,4741],{"class":478},"minReadySeconds",[122,4743,482],{"class":131},[122,4745,2191],{"class":162},[122,4747,4748,4751,4753],{"class":124,"line":264},[122,4749,4750],{"class":478},"progressDeadlineSeconds",[122,4752,482],{"class":131},[122,4754,4755],{"class":162},"600\n",[122,4757,4758,4761,4763],{"class":124,"line":272},[122,4759,4760],{"class":478},"revisionHistoryLimit",[122,4762,482],{"class":131},[122,4764,4765],{"class":162},"5\n",[97,4767,4768],{},"Percentages round differently for surge and unavailable counts, so small replica sets deserve explicit review. PodDisruptionBudgets do not control Deployment rollouts; they constrain certain voluntary disruptions such as node drains.",[101,4770,4772],{"id":4771},"observe-the-controller-pods-and-service-signals","Observe the controller, Pods, and service signals",[97,4774,1608,4775,4778,4779,4782],{},[119,4776,4777],{},"kubectl rollout status deployment\u002Fapi"," and inspect ",[119,4780,4781],{},"kubectl describe deployment\u002Fapi",". Watch new ReplicaSet Pods become ready and old ones terminate. A progress deadline marks a stalled rollout as failed but does not automatically roll it back.",[97,4784,4785],{},"Platform status is necessary but insufficient. Watch request errors, latency, saturation, and business indicators during release. A syntactically valid container can return incorrect responses. Record the image digest and configuration revision so the observed regression maps to an exact artifact.",[101,4787,4789],{"id":4788},"rollback-has-limits","Rollback has limits",[97,4791,4792,4795],{},[119,4793,4794],{},"kubectl rollout undo deployment\u002Fapi"," restores an earlier Pod template revision if retained. It does not reverse database migrations, external API changes, deleted data, or mutated shared caches. A reliable rollback plan therefore begins in application design.",[97,4797,4798],{},"Use backward-compatible “expand and contract” database changes. First add nullable columns or new tables that old code tolerates. Deploy code that can work across both schemas and backfill safely. Only after all old code is gone should a later release remove obsolete structures. Run migrations as an explicit, observable Job rather than having every replica race at startup.",[101,4800,4802],{"id":4801},"release-behavior-should-be-automated-and-bounded","Release behavior should be automated and bounded",[97,4804,4805,4806,4809],{},"Set image by immutable digest in version-controlled configuration, wait for rollout completion, and fail the pipeline on deadline or health regression. Avoid ",[119,4807,4808],{},"kubectl apply"," from a developer laptop as the primary production path. The same release action should produce an auditable change and predictable status.",[97,4811,4812],{},"For higher-risk changes, progressive delivery controllers can send a fraction of traffic to a canary and evaluate metrics before continuing. They add value only when health signals and abort thresholds are trustworthy. A canary of one Pod behind ordinary random Service routing is not controlled exposure.",[1015,4814,4816],{"id":4815},"revisions-capture-pod-templates-not-complete-releases","Revisions capture Pod templates, not complete releases",[97,4818,4819],{},"A Deployment creates a new revision when its Pod template changes; scaling replicas alone does not create one. Revision history can recover an earlier image, command, environment reference, or annotation embedded in that template, but it is not a snapshot of mutable ConfigMap or Secret contents. If rollback must restore configuration, use versioned configuration objects and change their names in the Pod template.",[97,4821,1608,4822,4825,4826,4828],{},[119,4823,4824],{},"kubectl rollout history deployment\u002Fapi"," to correlate revisions with change records, but keep the authoritative release identity in source control and deployment telemetry. You can pause a Deployment while making several template edits and resume it to roll them out together; do not leave production paused without an alert or runbook, because updates will be accepted without progressing. Before relying on undo, verify that the required revision remains within ",[119,4827,4760],{}," and that its external dependencies are still compatible.",[1015,4830,4832],{"id":4831},"termination-behavior-determines-rollout-quality","Termination behavior determines rollout quality",[97,4834,4835],{},"During a rolling update, old Pods receive termination while new Pods join endpoints. If the application continues accepting work after removal starts, or exits before proxies update, users may see resets. Combine readiness-based draining, Node’s SIGTERM handler, a suitable grace period, and proxy connection-drain settings. Test long requests and WebSockets separately.",[97,4837,4838],{},"Capacity also shapes safety. With no surge room and a slow-starting replacement, rollout may reduce effective capacity enough to breach latency targets. Conversely, surge Pods can exhaust database connections. A release plan should account for temporary replica count, downstream pools, and node capacity, not only the steady state. Verify these assumptions under representative load.",[101,4840,723],{"id":722},[97,4842,4843],{},"Run three replicas under continuous requests. Deploy a version whose readiness succeeds after 20 seconds and watch surge behavior. Then deploy a version that never becomes ready, observe the progress deadline, and perform a rollback. Finally, write an expand-and-contract sequence for one real schema change and identify the last point where application rollback remains safe.",[101,4845,734],{"id":733},[736,4847,4848,4855],{},[739,4849,4850],{},[742,4851,4854],{"href":4852,"rel":4853},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fcontrollers\u002Fdeployment\u002F#updating-a-deployment",[746],"Kubernetes: Updating a Deployment",[739,4856,4857],{},[742,4858,4861],{"href":4859,"rel":4860},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fworkloads\u002Fcontrollers\u002Fdeployment\u002F#rolling-back-a-deployment",[746],"Kubernetes: Roll back a Deployment",[756,4863,2940],{},{"title":117,"searchDepth":21,"depth":21,"links":4865},[4866,4867,4868,4869,4873,4874],{"id":4670,"depth":21,"text":4671},{"id":4771,"depth":21,"text":4772},{"id":4788,"depth":21,"text":4789},{"id":4801,"depth":21,"text":4802,"children":4870},[4871,4872],{"id":4815,"depth":26,"text":4816},{"id":4831,"depth":26,"text":4832},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A Deployment can replace replicas gradually, but safety depends on readiness, capacity, and compatibility across versions. A successful rollout is not…",{},"\u002Fkubernetes-for-web-applications\u002Fday-17",{"title":4658,"description":4875},"kubernetes-for-web-applications\u002Fday-17","nOepqCvkGyMK_Vw9baDuSHc3LBq5l8EmmzXOaclIBPk",{"id":4882,"title":4883,"body":4884,"day":417,"description":5141,"extension":769,"meta":5142,"navigation":147,"path":5143,"seo":5144,"stem":5145,"tag":774,"week":31,"weekName":55,"__hash__":5146},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-18.md","Autoscaling and resilience",{"type":89,"value":4885,"toc":5131},[4886,4890,4893,4897,4900,5059,5062,5066,5069,5072,5076,5079,5082,5086,5089,5092,5096,5099,5102,5104,5111,5113,5129],[92,4887,4889],{"id":4888},"day-18-autoscaling-and-resilience","Day 18 — Autoscaling and resilience",[97,4891,4892],{},"Autoscaling is a feedback loop that adds replicas when observed demand exceeds a target. It works only when the service is horizontally replicable, resource requests and metrics are credible, and scale-up arrives before the workload has already failed.",[101,4894,4896],{"id":4895},"hpa-turns-metrics-into-desired-replicas","HPA turns metrics into desired replicas",[97,4898,4899],{},"The Horizontal Pod Autoscaler periodically compares a metric with a target and adjusts a scalable resource such as a Deployment. For CPU utilization, the target is a percentage of requested CPU—not a percentage of the CPU limit or node. Missing or unrealistic requests therefore make the signal unusable.",[112,4901,4903],{"className":469,"code":4902,"language":471,"meta":117,"style":117},"apiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: api\nspec:\n  scaleTargetRef:\n    apiVersion: apps\u002Fv1\n    kind: Deployment\n    name: api\n  minReplicas: 3\n  maxReplicas: 20\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 65\n",[119,4904,4905,4914,4923,4929,4937,4943,4950,4959,4968,4977,4987,4997,5004,5016,5023,5032,5039,5049],{"__ignoreMap":117},[122,4906,4907,4909,4911],{"class":124,"line":12},[122,4908,479],{"class":478},[122,4910,482],{"class":131},[122,4912,4913],{"class":138},"autoscaling\u002Fv2\n",[122,4915,4916,4918,4920],{"class":124,"line":21},[122,4917,490],{"class":478},[122,4919,482],{"class":131},[122,4921,4922],{"class":138},"HorizontalPodAutoscaler\n",[122,4924,4925,4927],{"class":124,"line":26},[122,4926,500],{"class":478},[122,4928,503],{"class":131},[122,4930,4931,4933,4935],{"class":124,"line":31},[122,4932,508],{"class":478},[122,4934,482],{"class":131},[122,4936,2806],{"class":138},[122,4938,4939,4941],{"class":124,"line":58},[122,4940,2569],{"class":478},[122,4942,503],{"class":131},[122,4944,4945,4948],{"class":124,"line":231},[122,4946,4947],{"class":478},"  scaleTargetRef",[122,4949,503],{"class":131},[122,4951,4952,4955,4957],{"class":124,"line":264},[122,4953,4954],{"class":478},"    apiVersion",[122,4956,482],{"class":131},[122,4958,2686],{"class":138},[122,4960,4961,4964,4966],{"class":124,"line":272},[122,4962,4963],{"class":478},"    kind",[122,4965,482],{"class":131},[122,4967,2695],{"class":138},[122,4969,4970,4973,4975],{"class":124,"line":278},[122,4971,4972],{"class":478},"    name",[122,4974,482],{"class":131},[122,4976,2806],{"class":138},[122,4978,4979,4982,4984],{"class":124,"line":285},[122,4980,4981],{"class":478},"  minReplicas",[122,4983,482],{"class":131},[122,4985,4986],{"class":162},"3\n",[122,4988,4989,4992,4994],{"class":124,"line":310},[122,4990,4991],{"class":478},"  maxReplicas",[122,4993,482],{"class":131},[122,4995,4996],{"class":162},"20\n",[122,4998,4999,5002],{"class":124,"line":316},[122,5000,5001],{"class":478},"  metrics",[122,5003,503],{"class":131},[122,5005,5006,5008,5011,5013],{"class":124,"line":321},[122,5007,3044],{"class":131},[122,5009,5010],{"class":478},"type",[122,5012,482],{"class":131},[122,5014,5015],{"class":138},"Resource\n",[122,5017,5018,5021],{"class":124,"line":337},[122,5019,5020],{"class":478},"      resource",[122,5022,503],{"class":131},[122,5024,5025,5027,5029],{"class":124,"line":342},[122,5026,3722],{"class":478},[122,5028,482],{"class":131},[122,5030,5031],{"class":138},"cpu\n",[122,5033,5034,5037],{"class":124,"line":363},[122,5035,5036],{"class":478},"        target",[122,5038,503],{"class":131},[122,5040,5041,5044,5046],{"class":124,"line":376},[122,5042,5043],{"class":478},"          type",[122,5045,482],{"class":131},[122,5047,5048],{"class":138},"Utilization\n",[122,5050,5051,5054,5056],{"class":124,"line":417},[122,5052,5053],{"class":478},"          averageUtilization",[122,5055,482],{"class":131},[122,5057,5058],{"class":162},"65\n",[97,5060,5061],{},"Resource metrics normally come from Metrics Server. Custom request-rate or queue-depth metrics require an adapter or external metrics integration.",[101,5063,5065],{"id":5064},"scaling-requires-stateless-instances","Scaling requires stateless instances",[97,5067,5068],{},"Any replica should be able to handle any request. Store sessions in signed cookies or a shared, appropriately secured store rather than process memory. Put uploaded assets in object storage. Coordinate scheduled work so adding replicas does not duplicate jobs. Use idempotency keys when clients or workers may retry side effects.",[97,5070,5071],{},"Connection pools multiply with replicas, so cap per-Pod pool size and compare the maximum replica count with downstream capacity. Autoscaling an API from 3 to 20 Pods can overwhelm a database even while protecting API CPU. Concurrency limits and backpressure should reject excess work predictably instead of allowing unbounded queues.",[101,5073,5075],{"id":5074},"graceful-shutdown-is-part-of-elasticity","Graceful shutdown is part of elasticity",[97,5077,5078],{},"Scale-down terminates Pods just like a rollout. On SIGTERM, stop readiness, cease accepting work, finish or safely abandon in-flight operations, and close clients before the grace deadline. Workers should make jobs visible again or extend leases according to queue semantics.",[97,5080,5081],{},"HPA behavior rules can stabilize scale-down and limit how rapidly replicas change. This avoids oscillation when traffic is bursty. Keep a nonzero minimum replica count when cold startup or node provisioning is slower than acceptable request latency.",[101,5083,5085],{"id":5084},"autoscaling-is-not-performance-engineering","Autoscaling is not performance engineering",[97,5087,5088],{},"HPA reacts after metrics change. It cannot repair a slow query, memory leak, serialized critical section, or downstream quota. Establish a safe per-Pod operating point with load tests, optimize bottlenecks, and then use scaling to stay within that region.",[97,5090,5091],{},"Cluster capacity must also follow. HPA can create Pending Pods if nodes are full; a cluster autoscaler may add nodes, but VM provisioning and image pulling add delay. Monitor desired versus available replicas, scheduling latency, throttling, and downstream saturation—not only HPA’s current replica number.",[1015,5093,5095],{"id":5094},"match-the-metric-to-the-work","Match the metric to the work",[97,5097,5098],{},"CPU is useful for CPU-bound, proportionally scaling request handlers. Request concurrency or arrival rate may represent I\u002FO-heavy APIs better, provided each replica has similar capacity. Queue depth or age is often the right worker signal because it captures outstanding work. The metric pipeline must be timely and available during stress; a delayed signal creates delayed capacity.",[97,5100,5101],{},"Define a stabilization and failure strategy. If custom metrics disappear, understand how the HPA behaves and alert on the loss. Keep a minimum capacity that meets baseline availability. Scheduled pre-scaling can complement reactive scaling for known events, but it should remain a measured operational decision rather than masking slow startup or poor performance.",[101,5103,723],{"id":722},[97,5105,5106,5107,5110],{},"Install Metrics Server if your local cluster needs it, set realistic requests, and apply the HPA. Generate sustained CPU-producing HTTP load and watch ",[119,5108,5109],{},"kubectl get hpa -w"," alongside Pods. Stop load and observe stabilized scale-down. Repeat with an artificially slow downstream and explain why adding replicas helps, hurts, or has no effect.",[101,5112,734],{"id":733},[736,5114,5115,5122],{},[739,5116,5117],{},[742,5118,5121],{"href":5119,"rel":5120},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Frun-application\u002Fhorizontal-pod-autoscale\u002F",[746],"Kubernetes: Horizontal Pod Autoscaling",[739,5123,5124],{},[742,5125,5128],{"href":5126,"rel":5127},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Freference\u002Fkubernetes-api\u002Fworkload-resources\u002Fhorizontal-pod-autoscaler-v2\u002F",[746],"Kubernetes autoscaling API reference",[756,5130,2940],{},{"title":117,"searchDepth":21,"depth":21,"links":5132},[5133,5134,5135,5136,5139,5140],{"id":4895,"depth":21,"text":4896},{"id":5064,"depth":21,"text":5065},{"id":5074,"depth":21,"text":5075},{"id":5084,"depth":21,"text":5085,"children":5137},[5138],{"id":5094,"depth":26,"text":5095},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Autoscaling is a feedback loop that adds replicas when observed demand exceeds a target. It works only when the service is horizontally replicable,…",{},"\u002Fkubernetes-for-web-applications\u002Fday-18",{"title":4883,"description":5141},"kubernetes-for-web-applications\u002Fday-18","_TweQhe9MhseYl4sM41qeuNkKQ4lq7AmDEab1zqzh90",{"id":5148,"title":5149,"body":5150,"day":450,"description":5357,"extension":769,"meta":5358,"navigation":147,"path":5359,"seo":5360,"stem":5361,"tag":774,"week":31,"weekName":55,"__hash__":5362},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-19.md","Debugging production workloads",{"type":89,"value":5151,"toc":5347},[5152,5156,5159,5163,5166,5169,5256,5259,5263,5273,5287,5291,5294,5301,5305,5308,5311,5315,5318,5321,5323,5326,5328,5344],[92,5153,5155],{"id":5154},"day-19-debugging-production-workloads","Day 19 — Debugging production workloads",[97,5157,5158],{},"Production debugging should narrow the failing layer without introducing new state. Kubernetes provides enough evidence to distinguish scheduling, image, process, health, networking, and application failures if you follow a consistent sequence.",[101,5160,5162],{"id":5161},"start-with-scope-and-recent-change","Start with scope and recent change",[97,5164,5165],{},"Confirm the user-visible symptom, affected routes, environment, start time, and blast radius. Check recent deployments, configuration updates, dependency incidents, certificate changes, and node events. Preserve exact timestamps and image digests; “the current version” changes during an incident.",[97,5167,5168],{},"Then inspect controller and Pods:",[112,5170,5172],{"className":2474,"code":5171,"language":2476,"meta":117,"style":117},"kubectl get deployment,pods -n shop -l app=api -o wide\nkubectl rollout status deployment\u002Fapi -n shop\nkubectl describe pod \u003Cpod> -n shop\nkubectl get events -n shop --sort-by=.metadata.creationTimestamp\n",[119,5173,5174,5200,5218,5242],{"__ignoreMap":117},[122,5175,5176,5178,5180,5183,5186,5189,5192,5195,5197],{"class":124,"line":12},[122,5177,2428],{"class":182},[122,5179,2485],{"class":138},[122,5181,5182],{"class":138}," deployment,pods",[122,5184,5185],{"class":162}," -n",[122,5187,5188],{"class":138}," shop",[122,5190,5191],{"class":162}," -l",[122,5193,5194],{"class":138}," app=api",[122,5196,2515],{"class":162},[122,5198,5199],{"class":138}," wide\n",[122,5201,5202,5204,5207,5210,5213,5215],{"class":124,"line":21},[122,5203,2428],{"class":182},[122,5205,5206],{"class":138}," rollout",[122,5208,5209],{"class":138}," status",[122,5211,5212],{"class":138}," deployment\u002Fapi",[122,5214,5185],{"class":162},[122,5216,5217],{"class":138}," shop\n",[122,5219,5220,5222,5224,5226,5229,5232,5235,5238,5240],{"class":124,"line":26},[122,5221,2428],{"class":182},[122,5223,2525],{"class":138},[122,5225,2509],{"class":138},[122,5227,5228],{"class":127}," \u003C",[122,5230,5231],{"class":138},"po",[122,5233,5234],{"class":131},"d",[122,5236,5237],{"class":127},">",[122,5239,5185],{"class":162},[122,5241,5217],{"class":138},[122,5243,5244,5246,5248,5250,5252,5254],{"class":124,"line":31},[122,5245,2428],{"class":182},[122,5247,2485],{"class":138},[122,5249,2539],{"class":138},[122,5251,5185],{"class":162},[122,5253,5188],{"class":138},[122,5255,2542],{"class":162},[97,5257,5258],{},"Events help explain recent decisions but expire and may be aggregated. Use centralized logs and metrics for durable timelines.",[101,5260,5262],{"id":5261},"recognize-common-status-patterns","Recognize common status patterns",[97,5264,5265,5268,5269,5272],{},[119,5266,5267],{},"Pending"," often means insufficient requested resources, an unsatisfied node constraint, an unbound PVC, or an untolerated taint. Read scheduling events. ",[119,5270,5271],{},"ImagePullBackOff"," points toward a wrong image reference, missing registry credentials, authorization, rate limits, or node-to-registry connectivity.",[97,5274,5275,5278,5279,5282,5283,5286],{},[119,5276,5277],{},"CrashLoopBackOff"," means the container repeatedly exits and restart attempts are delayed. Inspect current and previous logs with ",[119,5280,5281],{},"kubectl logs \u003Cpod> -c api --previous",", container exit code, reason, command, and configuration. Exit code 137 commonly accompanies SIGKILL, including memory enforcement, but validate ",[119,5284,5285],{},"lastState.terminated.reason"," and metrics rather than assuming.",[101,5288,5290],{"id":5289},"follow-request-flow-layer-by-layer","Follow request flow layer by layer",[97,5292,5293],{},"For connectivity incidents, verify Pod readiness and application binding, then EndpointSlices, Service ports and selectors, in-cluster DNS, NetworkPolicies, ingress routes, and external DNS\u002Fload balancer state. Test from an origin similar to the failed caller. A successful laptop request through public ingress does not prove an internal worker can reach the Service.",[97,5295,5296,5297,5300],{},"Use a temporary diagnostic Pod with only approved tools. Minimal production images may intentionally lack a shell or curl. ",[119,5298,5299],{},"kubectl exec"," is useful when the process is running, but avoid editing files or installing packages. Ephemeral containers can attach a debugging image to an existing Pod’s namespaces where the cluster supports them; they still require appropriate authorization and may not expose every filesystem view.",[101,5302,5304],{"id":5303},"make-application-evidence-correlatable","Make application evidence correlatable",[97,5306,5307],{},"Search logs by request or trace ID, compare failing and healthy replicas, and align application timestamps with deployment events. Obtain thread, heap, or CPU profiles only when safe and with bounded duration; diagnostics can increase load and contain sensitive data.",[97,5309,5310],{},"Keep a hypothesis log: observation, possible explanation, next discriminating test, and result. Change one variable at a time. Once service is restored, save the minimal evidence needed for follow-up and remove temporary access or debug resources.",[1015,5312,5314],{"id":5313},"distinguish-mitigation-from-repair","Distinguish mitigation from repair",[97,5316,5317],{},"Scaling replicas, rolling back, disabling a feature, or raising a limit may restore service without proving root cause. Label the action as mitigation, record its exact time, and watch whether symptoms change. This prevents the incident narrative from turning correlation into certainty.",[97,5319,5320],{},"After stabilization, reproduce under controlled conditions where possible and fix desired state, code, or platform configuration through the normal path. Add a regression test or observable guardrail that would catch recurrence. A useful follow-up explains why existing controls failed, not only which engineer ran which command. Review whether access, documentation, or telemetry slowed diagnosis and address the smallest systemic gap.",[101,5322,723],{"id":722},[97,5324,5325],{},"Create three failures in a disposable namespace: an invalid image tag, a missing required environment variable, and a request that exceeds a small memory limit. Diagnose each without modifying the running container. Then break a Service selector and use a written flow from ingress to endpoint to find it. Record the exact command that first proved each root cause.",[101,5327,734],{"id":733},[736,5329,5330,5337],{},[739,5331,5332],{},[742,5333,5336],{"href":5334,"rel":5335},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fdebug\u002Fdebug-application\u002Fdebug-running-pod\u002F",[746],"Kubernetes: Debug running Pods",[739,5338,5339],{},[742,5340,5343],{"href":5341,"rel":5342},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fdebug\u002Fdebug-application\u002Fdebug-pods\u002F",[746],"Kubernetes: Debug Pods",[756,5345,5346],{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":117,"searchDepth":21,"depth":21,"links":5348},[5349,5350,5351,5352,5355,5356],{"id":5161,"depth":21,"text":5162},{"id":5261,"depth":21,"text":5262},{"id":5289,"depth":21,"text":5290},{"id":5303,"depth":21,"text":5304,"children":5353},[5354],{"id":5313,"depth":26,"text":5314},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Production debugging should narrow the failing layer without introducing new state. Kubernetes provides enough evidence to distinguish scheduling,…",{},"\u002Fkubernetes-for-web-applications\u002Fday-19",{"title":5149,"description":5357},"kubernetes-for-web-applications\u002Fday-19","UyO8_RZwweO58210DU8a43JjlpRyssSBJ3W4AusIwdY",{"id":5364,"title":5365,"body":5366,"day":2172,"description":5462,"extension":769,"meta":5463,"navigation":147,"path":5464,"seo":5465,"stem":5466,"tag":774,"week":31,"weekName":55,"__hash__":5467},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-20.md","Observability for application developers",{"type":89,"value":5367,"toc":5452},[5368,5372,5375,5379,5382,5385,5389,5396,5399,5403,5406,5409,5413,5416,5419,5423,5426,5429,5431,5434,5436],[92,5369,5371],{"id":5370},"day-20-observability-for-application-developers","Day 20 — Observability for application developers",[97,5373,5374],{},"Observability is the ability to explain system behavior from emitted evidence, especially when the failure was not predicted. For a Node web service, a small coherent set of metrics, structured logs, and traces is more useful than hundreds of unowned signals.",[101,5376,5378],{"id":5377},"metrics-quantify-trends-and-thresholds","Metrics quantify trends and thresholds",[97,5380,5381],{},"Metrics are numeric time series suited to rates, distributions, capacity, and alerting. Start with request count by route template, status class, and method; request-duration histograms; in-flight work; event-loop lag; CPU; resident memory; and dependency latency\u002Ferrors. Never label metrics with request IDs, raw URLs, user IDs, or other unbounded values because high cardinality can overwhelm the metrics system.",[97,5383,5384],{},"Prometheus commonly scrapes application and platform metrics; Grafana queries and presents them. Kubernetes does not install a complete monitoring stack by default. Define who owns scrape configuration, retention, dashboard changes, and alerts.",[101,5386,5388],{"id":5387},"logs-explain-discrete-events","Logs explain discrete events",[97,5390,5391,5392,5395],{},"Emit structured JSON to stdout with timestamp, severity, service, environment, release digest, trace ID, and stable event name. Log unexpected failures with stack information internally, but return sanitized error responses. Use route templates such as ",[119,5393,5394],{},"\u002Fusers\u002F:id",", not sensitive concrete paths.",[97,5397,5398],{},"Avoid duplicate logging at every layer. One request-completion event can capture status and duration; specific domain events capture meaningful decisions. Sampling may be appropriate for high-volume success logs, while errors and security-relevant events require deliberate retention. Central aggregation is essential because Pods disappear.",[101,5400,5402],{"id":5401},"traces-connect-distributed-work","Traces connect distributed work",[97,5404,5405],{},"A trace represents a request or job as spans across services and dependencies. OpenTelemetry provides vendor-neutral APIs, SDKs, semantic conventions, and exporters for traces, metrics, and logs. Instrument inbound HTTP, outbound requests, database calls, and queue publish\u002Fconsume boundaries, while avoiding secret-bearing attributes.",[97,5407,5408],{},"Propagate W3C Trace Context headers through trusted HTTP boundaries and message metadata. Include the active trace ID in logs so an operator can move from an aggregate latency spike to a representative trace and then to relevant application events. Sampling decisions should preserve enough errors and slow requests to diagnose them without collecting every operation indefinitely.",[101,5410,5412],{"id":5411},"slos-turn-telemetry-into-priorities","SLOs turn telemetry into priorities",[97,5414,5415],{},"An SLO states a measured reliability objective, such as 99.9% of eligible API requests succeeding over 28 days, with an explicit indicator and exclusions. The error budget is the allowed unreliability. Alerts based on rapid error-budget consumption generally align better with user impact than alerts for every transient CPU spike.",[97,5417,5418],{},"A practical dashboard for one Node API should show traffic, error rate, p50\u002Fp95\u002Fp99 latency, saturation, replica\u002Freadiness state, rollout version, event-loop lag, memory, and top downstream failures. Every panel should support a decision. Link dashboards to a runbook and annotate releases so behavior changes have context.",[1015,5420,5422],{"id":5421},"instrumentation-has-reliability-and-cost-limits","Instrumentation has reliability and cost limits",[97,5424,5425],{},"Telemetry code runs in the request path. Export asynchronously, bound queues and memory, and decide whether to drop telemetry rather than block user requests when a collector is unavailable. Configure exporter timeouts and observe dropped spans or log batches. A monitoring outage should not normally become an application outage.",[97,5427,5428],{},"Control volume at creation. Use histogram buckets that match meaningful latency thresholds, aggregate route labels, and sample high-volume traces. Retention should reflect diagnostic and compliance needs, and access should reflect the sensitive content telemetry may contain. Review instrumentation during API changes so renamed routes and error codes do not silently break dashboards or SLO calculations.",[101,5430,723],{"id":722},[97,5432,5433],{},"Add a request ID and OpenTelemetry-compatible trace context to one Node request path. Expose a Prometheus histogram and counter using route templates, emit one structured completion log, and generate traffic with successes, errors, and a slow dependency. Build a single dashboard view, then write one availability SLI and an alert condition tied to user impact.",[101,5435,734],{"id":733},[736,5437,5438,5445],{},[739,5439,5440],{},[742,5441,5444],{"href":5442,"rel":5443},"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Flanguages\u002Fjs\u002F",[746],"OpenTelemetry JavaScript documentation",[739,5446,5447],{},[742,5448,5451],{"href":5449,"rel":5450},"https:\u002F\u002Fprometheus.io\u002Fdocs\u002Fpractices\u002Finstrumentation\u002F",[746],"Prometheus instrumentation practices",{"title":117,"searchDepth":21,"depth":21,"links":5453},[5454,5455,5456,5457,5460,5461],{"id":5377,"depth":21,"text":5378},{"id":5387,"depth":21,"text":5388},{"id":5401,"depth":21,"text":5402},{"id":5411,"depth":21,"text":5412,"children":5458},[5459],{"id":5421,"depth":26,"text":5422},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Observability is the ability to explain system behavior from emitted evidence, especially when the failure was not predicted. For a Node web service, a…",{},"\u002Fkubernetes-for-web-applications\u002Fday-20",{"title":5365,"description":5462},"kubernetes-for-web-applications\u002Fday-20","KgovE08WWXukce9KJ4fomGJD-vh41j1tRd9kZCtOLHk",{"id":5469,"title":5470,"body":5471,"day":2183,"description":6226,"extension":769,"meta":6227,"navigation":147,"path":6228,"seo":6229,"stem":6230,"tag":774,"week":58,"weekName":59,"__hash__":6231},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-21.md","Packaging manifests with Helm and Kustomize",{"type":89,"value":5472,"toc":6218},[5473,5477,5480,5484,5487,5662,5675,5679,5694,5772,5855,5870,5886,5890,5896,5953,6031,6152,6162,6166,6169,6172,6174,6197,6199,6215],[92,5474,5476],{"id":5475},"day-21-packaging-manifests-with-helm-and-kustomize","Day 21 — Packaging manifests with Helm and Kustomize",[97,5478,5479],{},"For a senior engineer, packaging is less about avoiding repeated YAML and more about preserving understandable change boundaries. Kubernetes configuration becomes production code: it needs reviewable defaults, explicit environment differences, deterministic rendering, and an escape hatch when abstractions leak.",[101,5481,5483],{"id":5482},"_1-raw-yaml-establishes-the-contract","1. Raw YAML establishes the contract",[97,5485,5486],{},"Start with plain manifests because they expose the Kubernetes API without another language in between. A Deployment and Service can be applied directly, diffed easily, and understood with standard tooling. The cost appears when several environments or services repeat the same structures and drift independently.",[112,5488,5490],{"className":469,"code":5489,"language":471,"meta":117,"style":117},"apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: web-api\nspec:\n  replicas: 2\n  selector:\n    matchLabels: { app: web-api }\n  template:\n    metadata:\n      labels: { app: web-api }\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io\u002Facme\u002Fweb-api@sha256:REPLACE_ME\n          ports:\n            - containerPort: 3000\n          env:\n            - name: NODE_ENV\n              value: production\n",[119,5491,5492,5500,5508,5514,5522,5528,5536,5542,5560,5566,5572,5586,5592,5598,5608,5617,5623,5634,5641,5652],{"__ignoreMap":117},[122,5493,5494,5496,5498],{"class":124,"line":12},[122,5495,479],{"class":478},[122,5497,482],{"class":131},[122,5499,2686],{"class":138},[122,5501,5502,5504,5506],{"class":124,"line":21},[122,5503,490],{"class":478},[122,5505,482],{"class":131},[122,5507,2695],{"class":138},[122,5509,5510,5512],{"class":124,"line":26},[122,5511,500],{"class":478},[122,5513,503],{"class":131},[122,5515,5516,5518,5520],{"class":124,"line":31},[122,5517,508],{"class":478},[122,5519,482],{"class":131},[122,5521,2710],{"class":138},[122,5523,5524,5526],{"class":124,"line":58},[122,5525,2569],{"class":478},[122,5527,503],{"class":131},[122,5529,5530,5532,5534],{"class":124,"line":231},[122,5531,2721],{"class":478},[122,5533,482],{"class":131},[122,5535,2726],{"class":162},[122,5537,5538,5540],{"class":124,"line":264},[122,5539,2731],{"class":478},[122,5541,503],{"class":131},[122,5543,5544,5546,5549,5552,5554,5557],{"class":124,"line":272},[122,5545,2738],{"class":478},[122,5547,5548],{"class":131},": { ",[122,5550,5551],{"class":478},"app",[122,5553,482],{"class":131},[122,5555,5556],{"class":138},"web-api",[122,5558,5559],{"class":131}," }\n",[122,5561,5562,5564],{"class":124,"line":278},[122,5563,2754],{"class":478},[122,5565,503],{"class":131},[122,5567,5568,5570],{"class":124,"line":285},[122,5569,2761],{"class":478},[122,5571,503],{"class":131},[122,5573,5574,5576,5578,5580,5582,5584],{"class":124,"line":310},[122,5575,2768],{"class":478},[122,5577,5548],{"class":131},[122,5579,5551],{"class":478},[122,5581,482],{"class":131},[122,5583,5556],{"class":138},[122,5585,5559],{"class":131},[122,5587,5588,5590],{"class":124,"line":316},[122,5589,2784],{"class":478},[122,5591,503],{"class":131},[122,5593,5594,5596],{"class":124,"line":321},[122,5595,2791],{"class":478},[122,5597,503],{"class":131},[122,5599,5600,5602,5604,5606],{"class":124,"line":337},[122,5601,2798],{"class":131},[122,5603,2801],{"class":478},[122,5605,482],{"class":131},[122,5607,2806],{"class":138},[122,5609,5610,5612,5614],{"class":124,"line":342},[122,5611,2811],{"class":478},[122,5613,482],{"class":131},[122,5615,5616],{"class":138},"ghcr.io\u002Facme\u002Fweb-api@sha256:REPLACE_ME\n",[122,5618,5619,5621],{"class":124,"line":363},[122,5620,2821],{"class":478},[122,5622,503],{"class":131},[122,5624,5625,5627,5630,5632],{"class":124,"line":376},[122,5626,2828],{"class":131},[122,5628,5629],{"class":478},"containerPort",[122,5631,482],{"class":131},[122,5633,2845],{"class":162},[122,5635,5636,5639],{"class":124,"line":417},[122,5637,5638],{"class":478},"          env",[122,5640,503],{"class":131},[122,5642,5643,5645,5647,5649],{"class":124,"line":450},[122,5644,2828],{"class":131},[122,5646,2801],{"class":478},[122,5648,482],{"class":131},[122,5650,5651],{"class":138},"NODE_ENV\n",[122,5653,5654,5657,5659],{"class":124,"line":2172},[122,5655,5656],{"class":478},"              value",[122,5658,482],{"class":131},[122,5660,5661],{"class":138},"production\n",[97,5663,5664,5665,199,5668,1044,5671,5674],{},"Raw YAML is a good baseline for a small workload with few variants. Avoid copying whole directories for ",[119,5666,5667],{},"dev",[119,5669,5670],{},"staging",[119,5672,5673],{},"production","; copies hide whether a difference is intentional. Regardless of packaging tool, inspect the final objects before applying them. The rendered YAML—not a template or patch—is what the API server receives.",[101,5676,5678],{"id":5677},"_2-helm-packages-reusable-applications","2. Helm packages reusable applications",[97,5680,5681,5682,5685,5686,5689,5690,5693],{},"Helm is a chart format plus a Go-template rendering engine. A chart normally contains ",[119,5683,5684],{},"Chart.yaml",", default configuration in ",[119,5687,5688],{},"values.yaml",", and templates under ",[119,5691,5692],{},"templates\u002F",". Values represent supported configuration knobs; templates turn those values into Kubernetes objects.",[112,5695,5697],{"className":469,"code":5696,"language":471,"meta":117,"style":117},"# values.yaml\nreplicaCount: 2\nimage:\n  repository: ghcr.io\u002Facme\u002Fweb-api\n  digest: \"sha256:REPLACE_ME\"\nresources:\n  requests: { cpu: 100m, memory: 128Mi }\n",[119,5698,5699,5704,5713,5720,5730,5740,5746],{"__ignoreMap":117},[122,5700,5701],{"class":124,"line":12},[122,5702,5703],{"class":281},"# values.yaml\n",[122,5705,5706,5709,5711],{"class":124,"line":21},[122,5707,5708],{"class":478},"replicaCount",[122,5710,482],{"class":131},[122,5712,2726],{"class":162},[122,5714,5715,5718],{"class":124,"line":26},[122,5716,5717],{"class":478},"image",[122,5719,503],{"class":131},[122,5721,5722,5725,5727],{"class":124,"line":31},[122,5723,5724],{"class":478},"  repository",[122,5726,482],{"class":131},[122,5728,5729],{"class":138},"ghcr.io\u002Facme\u002Fweb-api\n",[122,5731,5732,5735,5737],{"class":124,"line":58},[122,5733,5734],{"class":478},"  digest",[122,5736,482],{"class":131},[122,5738,5739],{"class":138},"\"sha256:REPLACE_ME\"\n",[122,5741,5742,5744],{"class":124,"line":231},[122,5743,733],{"class":478},[122,5745,503],{"class":131},[122,5747,5748,5750,5752,5755,5757,5760,5762,5765,5767,5770],{"class":124,"line":264},[122,5749,4125],{"class":478},[122,5751,5548],{"class":131},[122,5753,5754],{"class":478},"cpu",[122,5756,482],{"class":131},[122,5758,5759],{"class":138},"100m",[122,5761,199],{"class":131},[122,5763,5764],{"class":478},"memory",[122,5766,482],{"class":131},[122,5768,5769],{"class":138},"128Mi",[122,5771,5559],{"class":131},[112,5773,5775],{"className":469,"code":5774,"language":471,"meta":117,"style":117},"# templates\u002Fdeployment.yaml (excerpt)\nspec:\n  replicas: {{ .Values.replicaCount }}\n  template:\n    spec:\n      containers:\n        - name: api\n          image: \"{{ .Values.image.repository }}@{{ .Values.image.digest }}\"\n          resources:\n            {{- toYaml .Values.resources | nindent 12 }}\n",[119,5776,5777,5782,5788,5801,5807,5813,5819,5829,5838,5845],{"__ignoreMap":117},[122,5778,5779],{"class":124,"line":12},[122,5780,5781],{"class":281},"# templates\u002Fdeployment.yaml (excerpt)\n",[122,5783,5784,5786],{"class":124,"line":21},[122,5785,2569],{"class":478},[122,5787,503],{"class":131},[122,5789,5790,5792,5795,5798],{"class":124,"line":26},[122,5791,2721],{"class":478},[122,5793,5794],{"class":131},": {{ ",[122,5796,5797],{"class":138},".Values.replicaCount",[122,5799,5800],{"class":131}," }}\n",[122,5802,5803,5805],{"class":124,"line":31},[122,5804,2754],{"class":478},[122,5806,503],{"class":131},[122,5808,5809,5811],{"class":124,"line":58},[122,5810,2784],{"class":478},[122,5812,503],{"class":131},[122,5814,5815,5817],{"class":124,"line":231},[122,5816,2791],{"class":478},[122,5818,503],{"class":131},[122,5820,5821,5823,5825,5827],{"class":124,"line":264},[122,5822,2798],{"class":131},[122,5824,2801],{"class":478},[122,5826,482],{"class":131},[122,5828,2806],{"class":138},[122,5830,5831,5833,5835],{"class":124,"line":272},[122,5832,2811],{"class":478},[122,5834,482],{"class":131},[122,5836,5837],{"class":138},"\"{{ .Values.image.repository }}@{{ .Values.image.digest }}\"\n",[122,5839,5840,5843],{"class":124,"line":278},[122,5841,5842],{"class":478},"          resources",[122,5844,503],{"class":131},[122,5846,5847,5850,5853],{"class":124,"line":285},[122,5848,5849],{"class":131},"            {{- ",[122,5851,5852],{"class":138},"toYaml .Values.resources | nindent 12",[122,5854,5800],{"class":131},[97,5856,5857,5858,5861,5862,5865,5866,5869],{},"Render before deployment with ",[119,5859,5860],{},"helm template web-api .\u002Fchart -f values-staging.yaml",". Validate the result, then install with ",[119,5863,5864],{},"helm upgrade --install web-api .\u002Fchart --namespace web --create-namespace",". ",[119,5867,5868],{},"upgrade --install"," is convenient, but Helm release state does not replace Git history or a deployment review.",[97,5871,5872,5873,199,5875,1044,5878,5881,5882,5885],{},"Keep the values interface narrow and meaningful. A chart exposing every field as a value becomes an undocumented second Kubernetes API. Prefer values such as ",[119,5874,5708],{},[119,5876,5877],{},"image.digest",[119,5879,5880],{},"ingress.host","; do not turn arbitrary template fragments into configuration. Use helpers for repeated labels and names, quote strings defensively, and fail early with Helm's ",[119,5883,5884],{},"required"," function for mandatory settings. Never place secret values in committed values files.",[101,5887,5889],{"id":5888},"_3-kustomize-patches-concrete-manifests","3. Kustomize patches concrete manifests",[97,5891,5892,5893,5895],{},"Kustomize uses ordinary YAML as a base and applies declarative transformations. It is built into ",[119,5894,2428],{},", has no template syntax, and works especially well when environments share nearly identical resources.",[112,5897,5899],{"className":469,"code":5898,"language":471,"meta":117,"style":117},"# base\u002Fkustomization.yaml\nresources:\n  - deployment.yaml\n  - service.yaml\nimages:\n  - name: ghcr.io\u002Facme\u002Fweb-api\n    newTag: \"1.8.0\"\n",[119,5900,5901,5906,5912,5919,5926,5933,5943],{"__ignoreMap":117},[122,5902,5903],{"class":124,"line":12},[122,5904,5905],{"class":281},"# base\u002Fkustomization.yaml\n",[122,5907,5908,5910],{"class":124,"line":21},[122,5909,733],{"class":478},[122,5911,503],{"class":131},[122,5913,5914,5916],{"class":124,"line":26},[122,5915,3696],{"class":131},[122,5917,5918],{"class":138},"deployment.yaml\n",[122,5920,5921,5923],{"class":124,"line":31},[122,5922,3696],{"class":131},[122,5924,5925],{"class":138},"service.yaml\n",[122,5927,5928,5931],{"class":124,"line":58},[122,5929,5930],{"class":478},"images",[122,5932,503],{"class":131},[122,5934,5935,5937,5939,5941],{"class":124,"line":231},[122,5936,3696],{"class":131},[122,5938,2801],{"class":478},[122,5940,482],{"class":131},[122,5942,5729],{"class":138},[122,5944,5945,5948,5950],{"class":124,"line":264},[122,5946,5947],{"class":478},"    newTag",[122,5949,482],{"class":131},[122,5951,5952],{"class":138},"\"1.8.0\"\n",[112,5954,5956],{"className":469,"code":5955,"language":471,"meta":117,"style":117},"# overlays\u002Fproduction\u002Fkustomization.yaml\nresources:\n  - ..\u002F..\u002Fbase\nnamePrefix: prod-\nreplicas:\n  - name: web-api\n    count: 4\npatches:\n  - path: resources.yaml\n",[119,5957,5958,5963,5969,5976,5986,5993,6003,6013,6020],{"__ignoreMap":117},[122,5959,5960],{"class":124,"line":12},[122,5961,5962],{"class":281},"# overlays\u002Fproduction\u002Fkustomization.yaml\n",[122,5964,5965,5967],{"class":124,"line":21},[122,5966,733],{"class":478},[122,5968,503],{"class":131},[122,5970,5971,5973],{"class":124,"line":26},[122,5972,3696],{"class":131},[122,5974,5975],{"class":138},"..\u002F..\u002Fbase\n",[122,5977,5978,5981,5983],{"class":124,"line":31},[122,5979,5980],{"class":478},"namePrefix",[122,5982,482],{"class":131},[122,5984,5985],{"class":138},"prod-\n",[122,5987,5988,5991],{"class":124,"line":58},[122,5989,5990],{"class":478},"replicas",[122,5992,503],{"class":131},[122,5994,5995,5997,5999,6001],{"class":124,"line":231},[122,5996,3696],{"class":131},[122,5998,2801],{"class":478},[122,6000,482],{"class":131},[122,6002,2710],{"class":138},[122,6004,6005,6008,6010],{"class":124,"line":264},[122,6006,6007],{"class":478},"    count",[122,6009,482],{"class":131},[122,6011,6012],{"class":162},"4\n",[122,6014,6015,6018],{"class":124,"line":272},[122,6016,6017],{"class":478},"patches",[122,6019,503],{"class":131},[122,6021,6022,6024,6026,6028],{"class":124,"line":278},[122,6023,3696],{"class":131},[122,6025,3461],{"class":478},[122,6027,482],{"class":131},[122,6029,6030],{"class":138},"resources.yaml\n",[112,6032,6034],{"className":469,"code":6033,"language":471,"meta":117,"style":117},"# overlays\u002Fproduction\u002Fresources.yaml\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: web-api\nspec:\n  template:\n    spec:\n      containers:\n        - name: api\n          resources:\n            requests: { cpu: 250m, memory: 256Mi }\n            limits: { memory: 512Mi }\n",[119,6035,6036,6041,6049,6057,6063,6071,6077,6083,6089,6095,6105,6111,6136],{"__ignoreMap":117},[122,6037,6038],{"class":124,"line":12},[122,6039,6040],{"class":281},"# overlays\u002Fproduction\u002Fresources.yaml\n",[122,6042,6043,6045,6047],{"class":124,"line":21},[122,6044,479],{"class":478},[122,6046,482],{"class":131},[122,6048,2686],{"class":138},[122,6050,6051,6053,6055],{"class":124,"line":26},[122,6052,490],{"class":478},[122,6054,482],{"class":131},[122,6056,2695],{"class":138},[122,6058,6059,6061],{"class":124,"line":31},[122,6060,500],{"class":478},[122,6062,503],{"class":131},[122,6064,6065,6067,6069],{"class":124,"line":58},[122,6066,508],{"class":478},[122,6068,482],{"class":131},[122,6070,2710],{"class":138},[122,6072,6073,6075],{"class":124,"line":231},[122,6074,2569],{"class":478},[122,6076,503],{"class":131},[122,6078,6079,6081],{"class":124,"line":264},[122,6080,2754],{"class":478},[122,6082,503],{"class":131},[122,6084,6085,6087],{"class":124,"line":272},[122,6086,2784],{"class":478},[122,6088,503],{"class":131},[122,6090,6091,6093],{"class":124,"line":278},[122,6092,2791],{"class":478},[122,6094,503],{"class":131},[122,6096,6097,6099,6101,6103],{"class":124,"line":285},[122,6098,2798],{"class":131},[122,6100,2801],{"class":478},[122,6102,482],{"class":131},[122,6104,2806],{"class":138},[122,6106,6107,6109],{"class":124,"line":310},[122,6108,5842],{"class":478},[122,6110,503],{"class":131},[122,6112,6113,6116,6118,6120,6122,6125,6127,6129,6131,6134],{"class":124,"line":316},[122,6114,6115],{"class":478},"            requests",[122,6117,5548],{"class":131},[122,6119,5754],{"class":478},[122,6121,482],{"class":131},[122,6123,6124],{"class":138},"250m",[122,6126,199],{"class":131},[122,6128,5764],{"class":478},[122,6130,482],{"class":131},[122,6132,6133],{"class":138},"256Mi",[122,6135,5559],{"class":131},[122,6137,6138,6141,6143,6145,6147,6150],{"class":124,"line":321},[122,6139,6140],{"class":478},"            limits",[122,6142,5548],{"class":131},[122,6144,5764],{"class":478},[122,6146,482],{"class":131},[122,6148,6149],{"class":138},"512Mi",[122,6151,5559],{"class":131},[97,6153,1289,6154,6157,6158,6161],{},[119,6155,6156],{},"kubectl kustomize overlays\u002Fproduction"," to inspect output and ",[119,6159,6160],{},"kubectl apply -k overlays\u002Fproduction"," to apply it. Prefer targeted patches over large replacements. If overlays accumulate dozens of patches, the base may represent too many applications or environments at once.",[101,6163,6165],{"id":6164},"_4-choose-based-on-ownership-and-variation","4. Choose based on ownership and variation",[97,6167,6168],{},"Use raw YAML when there is one deployment shape and repetition is minimal. Use Kustomize when your team owns concrete manifests and needs a few environment-specific patches. Use Helm when distributing a reusable application package, installing third-party software, or supporting a deliberate values-based interface. They can be combined—some GitOps repositories inflate Helm charts and then patch output—but composition adds debugging cost.",[97,6170,6171],{},"For a small product team, a strong default is Kustomize for first-party applications and Helm for external platform components such as ingress controllers or Prometheus. If multiple teams consume the same internal application chart, Helm may also be justified. Whichever approach you select, pin image digests, render in CI, validate schemas, and show reviewers the rendered diff.",[101,6173,723],{"id":722},[97,6175,6176,6177,6180,6181,6184,6185,6188,6189,6192,6193,6196],{},"Take an existing Node Deployment and Service. Put them in ",[119,6178,6179],{},"base\u002F",", then create ",[119,6182,6183],{},"overlays\u002Fdev"," with one replica and ",[119,6186,6187],{},"overlays\u002Fproduction"," with three replicas plus production resource requests. Run both ",[119,6190,6191],{},"kubectl kustomize"," commands and compare the rendered output. Then model the same three knobs in a tiny Helm chart and run ",[119,6194,6195],{},"helm template",". Write down which representation makes intentional differences easiest to review; keep only that approach.",[101,6198,734],{"id":733},[736,6200,6201,6208],{},[739,6202,6203],{},[742,6204,6207],{"href":6205,"rel":6206},"https:\u002F\u002Fhelm.sh\u002Fdocs\u002F",[746],"Helm documentation",[739,6209,6210],{},[742,6211,6214],{"href":6212,"rel":6213},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fmanage-kubernetes-objects\u002Fkustomization\u002F",[746],"Kubernetes: Declarative Management with Kustomize",[756,6216,6217],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":117,"searchDepth":21,"depth":21,"links":6219},[6220,6221,6222,6223,6224,6225],{"id":5482,"depth":21,"text":5483},{"id":5677,"depth":21,"text":5678},{"id":5888,"depth":21,"text":5889},{"id":6164,"depth":21,"text":6165},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"For a senior engineer, packaging is less about avoiding repeated YAML and more about preserving understandable change boundaries. Kubernetes…",{},"\u002Fkubernetes-for-web-applications\u002Fday-21",{"title":5470,"description":6226},"kubernetes-for-web-applications\u002Fday-21","2rO3RW0Fux7YyK58joL5YC4f6twSOAWXTKC5uaekzcA",{"id":6233,"title":6234,"body":6235,"day":2194,"description":6638,"extension":769,"meta":6639,"navigation":147,"path":6640,"seo":6641,"stem":6642,"tag":774,"week":58,"weekName":59,"__hash__":6643},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-22.md","CI\u002FCD into Kubernetes",{"type":89,"value":6236,"toc":6630},[6237,6241,6244,6248,6251,6254,6355,6358,6361,6365,6376,6379,6456,6459,6462,6466,6469,6478,6481,6542,6548,6552,6555,6586,6589,6591,6609,6611,6627],[92,6238,6240],{"id":6239},"day-22-cicd-into-kubernetes","Day 22 — CI\u002FCD into Kubernetes",[97,6242,6243],{},"A Kubernetes pipeline should create one verifiable artifact and move that exact artifact through environments. For a senior engineer, the hard part is not writing workflow YAML; it is establishing trust boundaries, immutable identity, useful quality gates, and a deployment result that can be observed and rolled back.",[101,6245,6247],{"id":6246},"_1-build-once-verify-before-publishing","1. Build once, verify before publishing",[97,6249,6250],{},"A practical pipeline has distinct stages: install dependencies, lint and test, build the application, construct the image, scan it, publish it, and update or apply deployment configuration. Failures should stop before credentials with greater privileges are introduced.",[97,6252,6253],{},"For Node, deterministic installation starts with a committed lockfile:",[112,6255,6257],{"className":469,"code":6256,"language":471,"meta":117,"style":117},"- uses: actions\u002Fcheckout@v4\n- uses: actions\u002Fsetup-node@v4\n  with:\n    node-version: 22\n    cache: npm\n- run: npm ci\n- run: npm run lint\n- run: npm test\n- run: npm run build\n",[119,6258,6259,6272,6283,6290,6300,6310,6322,6333,6344],{"__ignoreMap":117},[122,6260,6261,6264,6267,6269],{"class":124,"line":12},[122,6262,6263],{"class":131},"- ",[122,6265,6266],{"class":478},"uses",[122,6268,482],{"class":131},[122,6270,6271],{"class":138},"actions\u002Fcheckout@v4\n",[122,6273,6274,6276,6278,6280],{"class":124,"line":21},[122,6275,6263],{"class":131},[122,6277,6266],{"class":478},[122,6279,482],{"class":131},[122,6281,6282],{"class":138},"actions\u002Fsetup-node@v4\n",[122,6284,6285,6288],{"class":124,"line":26},[122,6286,6287],{"class":478},"  with",[122,6289,503],{"class":131},[122,6291,6292,6295,6297],{"class":124,"line":31},[122,6293,6294],{"class":478},"    node-version",[122,6296,482],{"class":131},[122,6298,6299],{"class":162},"22\n",[122,6301,6302,6305,6307],{"class":124,"line":58},[122,6303,6304],{"class":478},"    cache",[122,6306,482],{"class":131},[122,6308,6309],{"class":138},"npm\n",[122,6311,6312,6314,6317,6319],{"class":124,"line":231},[122,6313,6263],{"class":131},[122,6315,6316],{"class":478},"run",[122,6318,482],{"class":131},[122,6320,6321],{"class":138},"npm ci\n",[122,6323,6324,6326,6328,6330],{"class":124,"line":264},[122,6325,6263],{"class":131},[122,6327,6316],{"class":478},[122,6329,482],{"class":131},[122,6331,6332],{"class":138},"npm run lint\n",[122,6334,6335,6337,6339,6341],{"class":124,"line":272},[122,6336,6263],{"class":131},[122,6338,6316],{"class":478},[122,6340,482],{"class":131},[122,6342,6343],{"class":138},"npm test\n",[122,6345,6346,6348,6350,6352],{"class":124,"line":278},[122,6347,6263],{"class":131},[122,6349,6316],{"class":478},[122,6351,482],{"class":131},[122,6353,6354],{"class":138},"npm run build\n",[97,6356,6357],{},"A multi-stage Docker build should repeat only the compilation necessary to produce the runtime image. CI tests and image construction serve different purposes: tests give fast feedback; the Docker build proves the deployable artifact can be assembled. Add a vulnerability scanner such as Trivy, but define policy deliberately. Blocking every low-severity finding creates alert fatigue; blocking fixable critical vulnerabilities is a defensible initial gate.",[97,6359,6360],{},"Do not pass cloud keys or registry passwords to untrusted pull-request jobs. GitHub environments can require approval before production, while OpenID Connect (OIDC) lets workflows exchange short-lived identity tokens for cloud access instead of storing long-lived credentials.",[101,6362,6364],{"id":6363},"_2-treat-the-digest-as-the-release-identity","2. Treat the digest as the release identity",[97,6366,6367,6368,6371,6372,6375],{},"Tags are convenient aliases, not immutable identities. ",[119,6369,6370],{},"web-api:1.8.0"," can be overwritten; a digest such as ",[119,6373,6374],{},"web-api@sha256:abc..."," identifies exact image content. Publish useful tags for humans, but deploy and promote by digest.",[97,6377,6378],{},"With Docker Buildx, GitHub Actions can capture the pushed digest:",[112,6380,6382],{"className":469,"code":6381,"language":471,"meta":117,"style":117},"- id: build\n  uses: docker\u002Fbuild-push-action@v6\n  with:\n    context: .\n    push: true\n    tags: ghcr.io\u002Facme\u002Fweb-api:${{ github.sha }}\n\n- run: echo \"digest=${{ steps.build.outputs.digest }}\"\n",[119,6383,6384,6396,6406,6412,6421,6431,6441,6445],{"__ignoreMap":117},[122,6385,6386,6388,6391,6393],{"class":124,"line":12},[122,6387,6263],{"class":131},[122,6389,6390],{"class":478},"id",[122,6392,482],{"class":131},[122,6394,6395],{"class":138},"build\n",[122,6397,6398,6401,6403],{"class":124,"line":21},[122,6399,6400],{"class":478},"  uses",[122,6402,482],{"class":131},[122,6404,6405],{"class":138},"docker\u002Fbuild-push-action@v6\n",[122,6407,6408,6410],{"class":124,"line":26},[122,6409,6287],{"class":478},[122,6411,503],{"class":131},[122,6413,6414,6417,6419],{"class":124,"line":31},[122,6415,6416],{"class":478},"    context",[122,6418,482],{"class":131},[122,6420,2016],{"class":162},[122,6422,6423,6426,6428],{"class":124,"line":58},[122,6424,6425],{"class":478},"    push",[122,6427,482],{"class":131},[122,6429,6430],{"class":162},"true\n",[122,6432,6433,6436,6438],{"class":124,"line":231},[122,6434,6435],{"class":478},"    tags",[122,6437,482],{"class":131},[122,6439,6440],{"class":138},"ghcr.io\u002Facme\u002Fweb-api:${{ github.sha }}\n",[122,6442,6443],{"class":124,"line":264},[122,6444,148],{"emptyLinePlaceholder":147},[122,6446,6447,6449,6451,6453],{"class":124,"line":272},[122,6448,6263],{"class":131},[122,6450,6316],{"class":478},[122,6452,482],{"class":131},[122,6454,6455],{"class":138},"echo \"digest=${{ steps.build.outputs.digest }}\"\n",[97,6457,6458],{},"The production release should refer to that output, not rebuild from the production branch. Rebuilding can change dependencies, base images, timestamps, or toolchain output. Promotion means moving a known digest through deployment configuration: development observes it first, then staging, then production.",[97,6460,6461],{},"Also attach provenance where your platform permits it. Build attestations and signed images make it possible to answer: Which repository and workflow produced this image? Which commit was used? Was the build altered after publication? These controls become meaningful when admission policy or deployment review verifies them.",[101,6463,6465],{"id":6464},"_3-separate-artifact-ci-from-deployment-cd","3. Separate artifact CI from deployment CD",[97,6467,6468],{},"CI answers “is this commit releasable?” CD answers “should this known release be placed in this environment?” Keeping them logically separate reduces accidental privilege escalation and makes retries safer. A failed deployment should not require rebuilding the image.",[97,6470,6471,6472,3536,6475,6477],{},"Two common CD models are valid. Push-based delivery runs ",[119,6473,6474],{},"helm upgrade",[119,6476,4808],{}," from the workflow. It is simple but gives CI direct cluster credentials. Pull-based GitOps changes a configuration repository; a cluster-side reconciler performs deployment. The latter improves auditability and narrows cluster access, which Day 23 explores.",[97,6479,6480],{},"A push-based job might be:",[112,6482,6484],{"className":469,"code":6483,"language":471,"meta":117,"style":117},"- name: Deploy staging\n  run: |\n    helm upgrade --install web-api .\u002Fdeploy\u002Fchart \\\n      --namespace web-staging --create-namespace \\\n      --set image.repository=ghcr.io\u002Facme\u002Fweb-api \\\n      --set image.digest=${{ needs.image.outputs.digest }} \\\n      --wait --timeout 5m\n    kubectl rollout status deployment\u002Fweb-api \\\n      -n web-staging --timeout=120s\n",[119,6485,6486,6497,6507,6512,6517,6522,6527,6532,6537],{"__ignoreMap":117},[122,6487,6488,6490,6492,6494],{"class":124,"line":12},[122,6489,6263],{"class":131},[122,6491,2801],{"class":478},[122,6493,482],{"class":131},[122,6495,6496],{"class":138},"Deploy staging\n",[122,6498,6499,6502,6504],{"class":124,"line":21},[122,6500,6501],{"class":478},"  run",[122,6503,482],{"class":131},[122,6505,6506],{"class":127},"|\n",[122,6508,6509],{"class":124,"line":26},[122,6510,6511],{"class":138},"    helm upgrade --install web-api .\u002Fdeploy\u002Fchart \\\n",[122,6513,6514],{"class":124,"line":31},[122,6515,6516],{"class":138},"      --namespace web-staging --create-namespace \\\n",[122,6518,6519],{"class":124,"line":58},[122,6520,6521],{"class":138},"      --set image.repository=ghcr.io\u002Facme\u002Fweb-api \\\n",[122,6523,6524],{"class":124,"line":231},[122,6525,6526],{"class":138},"      --set image.digest=${{ needs.image.outputs.digest }} \\\n",[122,6528,6529],{"class":124,"line":264},[122,6530,6531],{"class":138},"      --wait --timeout 5m\n",[122,6533,6534],{"class":124,"line":272},[122,6535,6536],{"class":138},"    kubectl rollout status deployment\u002Fweb-api \\\n",[122,6538,6539],{"class":124,"line":278},[122,6540,6541],{"class":138},"      -n web-staging --timeout=120s\n",[97,6543,6544,6547],{},[119,6545,6546],{},"--wait"," is useful, but readiness only proves Kubernetes considers Pods ready. Follow it with a smoke test against the real route, checking a user-relevant path and a dependency-backed operation if safe.",[101,6549,6551],{"id":6550},"_4-design-failure-and-rollback-explicitly","4. Design failure and rollback explicitly",[97,6553,6554],{},"Deployments must expose enough information to diagnose failure: commit SHA, image digest, workflow URL, timestamps, and rollout events. Set workflow concurrency so two production deploys cannot race:",[112,6556,6558],{"className":469,"code":6557,"language":471,"meta":117,"style":117},"concurrency:\n  group: production\n  cancel-in-progress: false\n",[119,6559,6560,6567,6576],{"__ignoreMap":117},[122,6561,6562,6565],{"class":124,"line":12},[122,6563,6564],{"class":478},"concurrency",[122,6566,503],{"class":131},[122,6568,6569,6572,6574],{"class":124,"line":21},[122,6570,6571],{"class":478},"  group",[122,6573,482],{"class":131},[122,6575,5661],{"class":138},[122,6577,6578,6581,6583],{"class":124,"line":26},[122,6579,6580],{"class":478},"  cancel-in-progress",[122,6582,482],{"class":131},[122,6584,6585],{"class":162},"false\n",[97,6587,6588],{},"Automated rollback is attractive but can conceal a persistent defect. A safer first design stops on failed rollout, preserves logs and events, and offers an explicit rollback job to the previous digest. Database changes require expand-and-contract compatibility: deploy additive schema changes first, release code compatible with old and new shapes, migrate data, and remove obsolete fields later. Kubernetes rollback cannot reverse destructive data migration.",[101,6590,723],{"id":722},[97,6592,6593,6594,6596,6597,6600,6601,6604,6605,6608],{},"Create a GitHub Actions workflow for a small Node API that runs ",[119,6595,1604],{},", tests, builds an image, and pushes it on commits to ",[119,6598,6599],{},"main",". Capture the digest as a job output. Add a staging deployment job protected by a GitHub environment and use the digest in Helm or Kustomize configuration. Finish with ",[119,6602,6603],{},"kubectl rollout status"," and a ",[119,6606,6607],{},"curl --fail"," smoke test. On paper, document the exact command or config change needed to redeploy the previous digest.",[101,6610,734],{"id":733},[736,6612,6613,6620],{},[739,6614,6615],{},[742,6616,6619],{"href":6617,"rel":6618},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions",[746],"GitHub Actions documentation",[739,6621,6622],{},[742,6623,6626],{"href":6624,"rel":6625},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftutorials\u002Fkubernetes-basics\u002Fupdate\u002Fupdate-intro\u002F",[746],"Kubernetes: Performing a Rolling Update",[756,6628,6629],{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":117,"searchDepth":21,"depth":21,"links":6631},[6632,6633,6634,6635,6636,6637],{"id":6246,"depth":21,"text":6247},{"id":6363,"depth":21,"text":6364},{"id":6464,"depth":21,"text":6465},{"id":6550,"depth":21,"text":6551},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A Kubernetes pipeline should create one verifiable artifact and move that exact artifact through environments. For a senior engineer, the hard part is…",{},"\u002Fkubernetes-for-web-applications\u002Fday-22",{"title":6234,"description":6638},"kubernetes-for-web-applications\u002Fday-22","1Vs4gnEBDgp4Byve_w-cDKILfGpJndCEYjXNcr2XVqk",{"id":6645,"title":6646,"body":6647,"day":2202,"description":7012,"extension":769,"meta":7013,"navigation":147,"path":7014,"seo":7015,"stem":7016,"tag":774,"week":58,"weekName":59,"__hash__":7017},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-23.md","GitOps as an operating model",{"type":89,"value":6648,"toc":7004},[6649,6653,6656,6660,6663,6670,6748,6751,6758,6762,6775,6778,6928,6938,6941,6945,6948,6956,6959,6962,6966,6969,6972,6975,6977,6983,6985,7001],[92,6650,6652],{"id":6651},"day-23-gitops-as-an-operating-model","Day 23 — GitOps as an operating model",[97,6654,6655],{},"GitOps is not “CI with extra commits.” It changes the authority model: Git declares what should run, while a controller inside or near the cluster continuously reconciles actual state toward that declaration. For senior engineers, its value is a reviewable change history, bounded deployment credentials, drift detection, and a consistent recovery procedure.",[101,6657,6659],{"id":6658},"_1-git-stores-desired-state-reconciliation-closes-the-loop","1. Git stores desired state; reconciliation closes the loop",[97,6661,6662],{},"In a push pipeline, a workflow executes an imperative deployment and then exits. In GitOps, Argo CD or Flux repeatedly compares Git-rendered resources with live objects. If they differ, the controller reports drift and—when automated synchronization is enabled—can restore the declared state.",[97,6664,6665,6666,6669],{},"Suppose CI publishes ",[119,6667,6668],{},"ghcr.io\u002Facme\u002Fweb-api@sha256:abc123",". Promotion can be a pull request changing one field:",[112,6671,6673],{"className":469,"code":6672,"language":471,"meta":117,"style":117},"apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: web-api\nspec:\n  template:\n    spec:\n      containers:\n        - name: api\n          image: ghcr.io\u002Facme\u002Fweb-api@sha256:abc123\n",[119,6674,6675,6683,6691,6697,6705,6711,6717,6723,6729,6739],{"__ignoreMap":117},[122,6676,6677,6679,6681],{"class":124,"line":12},[122,6678,479],{"class":478},[122,6680,482],{"class":131},[122,6682,2686],{"class":138},[122,6684,6685,6687,6689],{"class":124,"line":21},[122,6686,490],{"class":478},[122,6688,482],{"class":131},[122,6690,2695],{"class":138},[122,6692,6693,6695],{"class":124,"line":26},[122,6694,500],{"class":478},[122,6696,503],{"class":131},[122,6698,6699,6701,6703],{"class":124,"line":31},[122,6700,508],{"class":478},[122,6702,482],{"class":131},[122,6704,2710],{"class":138},[122,6706,6707,6709],{"class":124,"line":58},[122,6708,2569],{"class":478},[122,6710,503],{"class":131},[122,6712,6713,6715],{"class":124,"line":231},[122,6714,2754],{"class":478},[122,6716,503],{"class":131},[122,6718,6719,6721],{"class":124,"line":264},[122,6720,2784],{"class":478},[122,6722,503],{"class":131},[122,6724,6725,6727],{"class":124,"line":272},[122,6726,2791],{"class":478},[122,6728,503],{"class":131},[122,6730,6731,6733,6735,6737],{"class":124,"line":278},[122,6732,2798],{"class":131},[122,6734,2801],{"class":478},[122,6736,482],{"class":131},[122,6738,2806],{"class":138},[122,6740,6741,6743,6745],{"class":124,"line":285},[122,6742,2811],{"class":478},[122,6744,482],{"class":131},[122,6746,6747],{"class":138},"ghcr.io\u002Facme\u002Fweb-api@sha256:abc123\n",[97,6749,6750],{},"That commit is now the deployment request, approval record, and rollback reference. Reverting it declares the previous desired state. This is stronger than relying on a workflow log, but only if uncontrolled actors cannot mutate Git or bypass the reconciler.",[97,6752,6753,6754,6757],{},"Reconciliation is continuous, so distinguish legitimate controller-generated changes from drift. An HPA changing ",[119,6755,6756],{},"spec.replicas",", for example, should not fight a GitOps controller. Omit fields owned by another controller or configure drift-ignore rules narrowly; broad ignores create blind spots.",[101,6759,6761],{"id":6760},"_2-argo-cd-and-flux-implement-similar-principles-differently","2. Argo CD and Flux implement similar principles differently",[97,6763,6764,6765,199,6768,1044,6771,6774],{},"Argo CD models an Application that points to a source repository\u002Fpath and a destination cluster\u002Fnamespace. It offers a strong UI, resource topology, sync health, hooks, and application grouping. Flux uses composable Kubernetes custom resources—such as ",[119,6766,6767],{},"GitRepository",[119,6769,6770],{},"Kustomization",[119,6772,6773],{},"HelmRelease","—and fits naturally into command-line and Kubernetes-native workflows.",[97,6776,6777],{},"A simplified Argo CD Application looks like this:",[112,6779,6781],{"className":469,"code":6780,"language":471,"meta":117,"style":117},"apiVersion: argoproj.io\u002Fv1alpha1\nkind: Application\nmetadata:\n  name: web-api-staging\n  namespace: argocd\nspec:\n  source:\n    repoURL: https:\u002F\u002Fgithub.com\u002Facme\u002Fplatform-config.git\n    targetRevision: main\n    path: apps\u002Fweb-api\u002Foverlays\u002Fstaging\n  destination:\n    server: https:\u002F\u002Fkubernetes.default.svc\n    namespace: web-staging\n  syncPolicy:\n    automated:\n      prune: true\n      selfHeal: true\n",[119,6782,6783,6792,6801,6807,6816,6826,6832,6839,6849,6859,6869,6876,6886,6896,6903,6910,6919],{"__ignoreMap":117},[122,6784,6785,6787,6789],{"class":124,"line":12},[122,6786,479],{"class":478},[122,6788,482],{"class":131},[122,6790,6791],{"class":138},"argoproj.io\u002Fv1alpha1\n",[122,6793,6794,6796,6798],{"class":124,"line":21},[122,6795,490],{"class":478},[122,6797,482],{"class":131},[122,6799,6800],{"class":138},"Application\n",[122,6802,6803,6805],{"class":124,"line":26},[122,6804,500],{"class":478},[122,6806,503],{"class":131},[122,6808,6809,6811,6813],{"class":124,"line":31},[122,6810,508],{"class":478},[122,6812,482],{"class":131},[122,6814,6815],{"class":138},"web-api-staging\n",[122,6817,6818,6821,6823],{"class":124,"line":58},[122,6819,6820],{"class":478},"  namespace",[122,6822,482],{"class":131},[122,6824,6825],{"class":138},"argocd\n",[122,6827,6828,6830],{"class":124,"line":231},[122,6829,2569],{"class":478},[122,6831,503],{"class":131},[122,6833,6834,6837],{"class":124,"line":264},[122,6835,6836],{"class":478},"  source",[122,6838,503],{"class":131},[122,6840,6841,6844,6846],{"class":124,"line":272},[122,6842,6843],{"class":478},"    repoURL",[122,6845,482],{"class":131},[122,6847,6848],{"class":138},"https:\u002F\u002Fgithub.com\u002Facme\u002Fplatform-config.git\n",[122,6850,6851,6854,6856],{"class":124,"line":278},[122,6852,6853],{"class":478},"    targetRevision",[122,6855,482],{"class":131},[122,6857,6858],{"class":138},"main\n",[122,6860,6861,6864,6866],{"class":124,"line":285},[122,6862,6863],{"class":478},"    path",[122,6865,482],{"class":131},[122,6867,6868],{"class":138},"apps\u002Fweb-api\u002Foverlays\u002Fstaging\n",[122,6870,6871,6874],{"class":124,"line":310},[122,6872,6873],{"class":478},"  destination",[122,6875,503],{"class":131},[122,6877,6878,6881,6883],{"class":124,"line":316},[122,6879,6880],{"class":478},"    server",[122,6882,482],{"class":131},[122,6884,6885],{"class":138},"https:\u002F\u002Fkubernetes.default.svc\n",[122,6887,6888,6891,6893],{"class":124,"line":321},[122,6889,6890],{"class":478},"    namespace",[122,6892,482],{"class":131},[122,6894,6895],{"class":138},"web-staging\n",[122,6897,6898,6901],{"class":124,"line":337},[122,6899,6900],{"class":478},"  syncPolicy",[122,6902,503],{"class":131},[122,6904,6905,6908],{"class":124,"line":342},[122,6906,6907],{"class":478},"    automated",[122,6909,503],{"class":131},[122,6911,6912,6915,6917],{"class":124,"line":363},[122,6913,6914],{"class":478},"      prune",[122,6916,482],{"class":131},[122,6918,6430],{"class":162},[122,6920,6921,6924,6926],{"class":124,"line":376},[122,6922,6923],{"class":478},"      selfHeal",[122,6925,482],{"class":131},[122,6927,6430],{"class":162},[97,6929,6930,6933,6934,6937],{},[119,6931,6932],{},"prune"," deletes resources removed from Git; ",[119,6935,6936],{},"selfHeal"," reverses live drift. Both are powerful and should first be enabled in a non-production environment. Protect the controller itself, constrain its RBAC, and define which repositories and target namespaces an application project may use.",[97,6939,6940],{},"Tool choice matters less than agreeing on ownership, repository structure, promotion rules, and emergency procedures. A poorly governed GitOps installation merely automates ambiguity.",[101,6942,6944],{"id":6943},"_3-separate-source-code-from-deployment-configuration-deliberately","3. Separate source code from deployment configuration deliberately",[97,6946,6947],{},"A common design uses an application repository for TypeScript, tests, Dockerfile, and CI, plus a configuration repository for environment manifests. CI builds the image, then opens a pull request against config. This lets application CI avoid cluster credentials and gives platform policy a separate review surface.",[112,6949,6954],{"className":6950,"code":6952,"language":6953,"meta":117},[6951],"language-text","web-api\u002F\n  src\u002F  test\u002F  Dockerfile  .github\u002Fworkflows\u002Fci.yml\n\nplatform-config\u002F\n  apps\u002Fweb-api\u002Fbase\u002F\n  apps\u002Fweb-api\u002Foverlays\u002Fstaging\u002F\n  apps\u002Fweb-api\u002Foverlays\u002Fproduction\u002F\n  clusters\u002Fproduction\u002F\n","text",[119,6955,6952],{"__ignoreMap":117},[97,6957,6958],{},"The split has costs: cross-repository changes are less atomic, discoverability can suffer, and automated version bumps may generate noise. For one small team, keeping deployment configuration beside application code can be simpler. The important separation is between artifact production and environment approval, not necessarily between repositories.",[97,6960,6961],{},"Avoid one enormous “everything” application. Choose boundaries that match ownership and blast radius: one app per deployable product component or coherent stack, with separate production and non-production instances. Shared cluster add-ons should generally be managed independently from product workloads.",[101,6963,6965],{"id":6964},"_4-auditability-requires-policy-and-an-escape-path","4. Auditability requires policy and an escape path",[97,6967,6968],{},"Git history is useful only when branches are protected, reviews are meaningful, commits identify actors, and secrets are absent. Store secret references or encrypted secret resources—not plaintext credentials. Controllers need read access to source repositories and write access to target resources, so scope both narrowly.",[97,6970,6971],{},"Define a break-glass process for incidents. An operator may need an emergency live patch, but reconciliation could immediately reverse it. A runbook should explain how to pause synchronization, authorize and record the manual change, back-port the fix into Git, and resume reconciliation. “Never use kubectl” is less useful than making exceptions explicit, temporary, and auditable.",[97,6973,6974],{},"GitOps also improves disaster recovery: reinstall the controller, restore access to repositories and secret systems, and reconcile. Test that claim. Stateful data, external identities, CRDs, and controller bootstrapping may not be recoverable from ordinary manifests alone.",[101,6976,723],{"id":722},[97,6978,6979,6980,6982],{},"On a local cluster, install Argo CD or Flux and point it at a test repository containing a Node Deployment and Service. Enable automated synchronization in a disposable namespace. First change the image or replica count in Git and observe reconciliation. Then manually edit a label with ",[119,6981,2428],{}," and see whether drift is reported or healed. Finally, revert the Git commit and confirm the previous state returns. Record which identity performed each operation.",[101,6984,734],{"id":733},[736,6986,6987,6994],{},[739,6988,6989],{},[742,6990,6993],{"href":6991,"rel":6992},"https:\u002F\u002Fopengitops.dev\u002F",[746],"OpenGitOps principles",[739,6995,6996],{},[742,6997,7000],{"href":6998,"rel":6999},"https:\u002F\u002Fargo-cd.readthedocs.io\u002F",[746],"Argo CD documentation",[756,7002,7003],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":117,"searchDepth":21,"depth":21,"links":7005},[7006,7007,7008,7009,7010,7011],{"id":6658,"depth":21,"text":6659},{"id":6760,"depth":21,"text":6761},{"id":6943,"depth":21,"text":6944},{"id":6964,"depth":21,"text":6965},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"GitOps is not “CI with extra commits.” It changes the authority model: Git declares what should run, while a controller inside or near the cluster…",{},"\u002Fkubernetes-for-web-applications\u002Fday-23",{"title":6646,"description":7012},"kubernetes-for-web-applications\u002Fday-23","6vSCN4_IMGUAgXAIgKbHCi9TNkneKXfoOsRUceS7E68",{"id":7019,"title":7020,"body":7021,"day":7302,"description":7584,"extension":769,"meta":7585,"navigation":147,"path":7586,"seo":7587,"stem":7588,"tag":774,"week":58,"weekName":59,"__hash__":7589},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-24.md","Kubernetes security for application teams",{"type":89,"value":7022,"toc":7576},[7023,7027,7030,7034,7037,7090,7093,7309,7316,7320,7323,7499,7502,7509,7513,7516,7519,7522,7526,7529,7532,7539,7541,7555,7557,7573],[92,7024,7026],{"id":7025},"day-24-kubernetes-security-for-application-teams","Day 24 — Kubernetes security for application teams",[97,7028,7029],{},"Application teams do not need to administer every control-plane security feature, but they do control most workload-level risk: image contents, runtime identity, permissions, secret handling, and Pod configuration. A senior engineer should make secure behavior the default in reusable manifests rather than depending on reviewers to catch every dangerous field.",[101,7031,7033],{"id":7032},"_1-give-workloads-a-narrow-identity-with-rbac","1. Give workloads a narrow identity with RBAC",[97,7035,7036],{},"Every Pod receives a Kubernetes service account identity unless token mounting is disabled. That identity should not inherit broad permissions or use the namespace's default service account. If an API does not call the Kubernetes API, disable token mounting entirely:",[112,7038,7040],{"className":469,"code":7039,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: web-api\n  namespace: web\nautomountServiceAccountToken: false\n",[119,7041,7042,7050,7059,7065,7073,7081],{"__ignoreMap":117},[122,7043,7044,7046,7048],{"class":124,"line":12},[122,7045,479],{"class":478},[122,7047,482],{"class":131},[122,7049,485],{"class":138},[122,7051,7052,7054,7056],{"class":124,"line":21},[122,7053,490],{"class":478},[122,7055,482],{"class":131},[122,7057,7058],{"class":138},"ServiceAccount\n",[122,7060,7061,7063],{"class":124,"line":26},[122,7062,500],{"class":478},[122,7064,503],{"class":131},[122,7066,7067,7069,7071],{"class":124,"line":31},[122,7068,508],{"class":478},[122,7070,482],{"class":131},[122,7072,2710],{"class":138},[122,7074,7075,7077,7079],{"class":124,"line":58},[122,7076,6820],{"class":478},[122,7078,482],{"class":131},[122,7080,3373],{"class":138},[122,7082,7083,7086,7088],{"class":124,"line":231},[122,7084,7085],{"class":478},"automountServiceAccountToken",[122,7087,482],{"class":131},[122,7089,6585],{"class":162},[97,7091,7092],{},"When a service genuinely needs access, grant specific verbs on specific resource types in one namespace:",[112,7094,7096],{"className":469,"code":7095,"language":471,"meta":117,"style":117},"apiVersion: rbac.authorization.k8s.io\u002Fv1\nkind: Role\nmetadata:\n  name: config-reader\n  namespace: web\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"configmaps\"]\n    resourceNames: [\"runtime-flags\"]\n    verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io\u002Fv1\nkind: RoleBinding\nmetadata:\n  name: web-api-config-reader\n  namespace: web\nsubjects:\n  - kind: ServiceAccount\n    name: web-api\n    namespace: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: config-reader\n",[119,7097,7098,7107,7116,7122,7131,7139,7146,7160,7172,7184,7196,7201,7209,7218,7224,7233,7241,7248,7258,7266,7274,7281,7291,7300],{"__ignoreMap":117},[122,7099,7100,7102,7104],{"class":124,"line":12},[122,7101,479],{"class":478},[122,7103,482],{"class":131},[122,7105,7106],{"class":138},"rbac.authorization.k8s.io\u002Fv1\n",[122,7108,7109,7111,7113],{"class":124,"line":21},[122,7110,490],{"class":478},[122,7112,482],{"class":131},[122,7114,7115],{"class":138},"Role\n",[122,7117,7118,7120],{"class":124,"line":26},[122,7119,500],{"class":478},[122,7121,503],{"class":131},[122,7123,7124,7126,7128],{"class":124,"line":31},[122,7125,508],{"class":478},[122,7127,482],{"class":131},[122,7129,7130],{"class":138},"config-reader\n",[122,7132,7133,7135,7137],{"class":124,"line":58},[122,7134,6820],{"class":478},[122,7136,482],{"class":131},[122,7138,3373],{"class":138},[122,7140,7141,7144],{"class":124,"line":231},[122,7142,7143],{"class":478},"rules",[122,7145,503],{"class":131},[122,7147,7148,7150,7153,7155,7158],{"class":124,"line":264},[122,7149,3696],{"class":131},[122,7151,7152],{"class":478},"apiGroups",[122,7154,2041],{"class":131},[122,7156,7157],{"class":138},"\"\"",[122,7159,2047],{"class":131},[122,7161,7162,7165,7167,7170],{"class":124,"line":272},[122,7163,7164],{"class":478},"    resources",[122,7166,2041],{"class":131},[122,7168,7169],{"class":138},"\"configmaps\"",[122,7171,2047],{"class":131},[122,7173,7174,7177,7179,7182],{"class":124,"line":278},[122,7175,7176],{"class":478},"    resourceNames",[122,7178,2041],{"class":131},[122,7180,7181],{"class":138},"\"runtime-flags\"",[122,7183,2047],{"class":131},[122,7185,7186,7189,7191,7194],{"class":124,"line":285},[122,7187,7188],{"class":478},"    verbs",[122,7190,2041],{"class":131},[122,7192,7193],{"class":138},"\"get\"",[122,7195,2047],{"class":131},[122,7197,7198],{"class":124,"line":310},[122,7199,7200],{"class":182},"---\n",[122,7202,7203,7205,7207],{"class":124,"line":316},[122,7204,479],{"class":478},[122,7206,482],{"class":131},[122,7208,7106],{"class":138},[122,7210,7211,7213,7215],{"class":124,"line":321},[122,7212,490],{"class":478},[122,7214,482],{"class":131},[122,7216,7217],{"class":138},"RoleBinding\n",[122,7219,7220,7222],{"class":124,"line":337},[122,7221,500],{"class":478},[122,7223,503],{"class":131},[122,7225,7226,7228,7230],{"class":124,"line":342},[122,7227,508],{"class":478},[122,7229,482],{"class":131},[122,7231,7232],{"class":138},"web-api-config-reader\n",[122,7234,7235,7237,7239],{"class":124,"line":363},[122,7236,6820],{"class":478},[122,7238,482],{"class":131},[122,7240,3373],{"class":138},[122,7242,7243,7246],{"class":124,"line":376},[122,7244,7245],{"class":478},"subjects",[122,7247,503],{"class":131},[122,7249,7250,7252,7254,7256],{"class":124,"line":417},[122,7251,3696],{"class":131},[122,7253,490],{"class":478},[122,7255,482],{"class":131},[122,7257,7058],{"class":138},[122,7259,7260,7262,7264],{"class":124,"line":450},[122,7261,4972],{"class":478},[122,7263,482],{"class":131},[122,7265,2710],{"class":138},[122,7267,7268,7270,7272],{"class":124,"line":2172},[122,7269,6890],{"class":478},[122,7271,482],{"class":131},[122,7273,3373],{"class":138},[122,7275,7276,7279],{"class":124,"line":2183},[122,7277,7278],{"class":478},"roleRef",[122,7280,503],{"class":131},[122,7282,7283,7286,7288],{"class":124,"line":2194},[122,7284,7285],{"class":478},"  apiGroup",[122,7287,482],{"class":131},[122,7289,7290],{"class":138},"rbac.authorization.k8s.io\n",[122,7292,7293,7296,7298],{"class":124,"line":2202},[122,7294,7295],{"class":478},"  kind",[122,7297,482],{"class":131},[122,7299,7115],{"class":138},[122,7301,7303,7305,7307],{"class":124,"line":7302},24,[122,7304,508],{"class":478},[122,7306,482],{"class":131},[122,7308,7130],{"class":138},[97,7310,7311,7312,7315],{},"Avoid wildcard verbs and resources, and do not grant application service accounts permission to read all Secrets. Test authorization with ",[119,7313,7314],{},"kubectl auth can-i get configmap\u002Fruntime-flags --as=system:serviceaccount:web:web-api -n web"," and also test actions that should be denied.",[101,7317,7319],{"id":7318},"_2-harden-the-pod-and-container-runtime","2. Harden the Pod and container runtime",[97,7321,7322],{},"A secure Node image runs as a known non-root UID, drops Linux capabilities, prevents privilege escalation, and uses a read-only root filesystem where possible. Apply controls at both Pod and container levels:",[112,7324,7326],{"className":469,"code":7325,"language":471,"meta":117,"style":117},"spec:\n  securityContext:\n    runAsNonRoot: true\n    runAsUser: 10001\n    seccompProfile:\n      type: RuntimeDefault\n  containers:\n    - name: api\n      image: ghcr.io\u002Facme\u002Fweb-api@sha256:abc123\n      securityContext:\n        allowPrivilegeEscalation: false\n        readOnlyRootFilesystem: true\n        capabilities:\n          drop: [\"ALL\"]\n      volumeMounts:\n        - name: tmp\n          mountPath: \u002Ftmp\n  volumes:\n    - name: tmp\n      emptyDir: {}\n",[119,7327,7328,7334,7341,7350,7360,7367,7377,7384,7394,7403,7410,7419,7428,7435,7447,7454,7465,7475,7482,7492],{"__ignoreMap":117},[122,7329,7330,7332],{"class":124,"line":12},[122,7331,2569],{"class":478},[122,7333,503],{"class":131},[122,7335,7336,7339],{"class":124,"line":21},[122,7337,7338],{"class":478},"  securityContext",[122,7340,503],{"class":131},[122,7342,7343,7346,7348],{"class":124,"line":26},[122,7344,7345],{"class":478},"    runAsNonRoot",[122,7347,482],{"class":131},[122,7349,6430],{"class":162},[122,7351,7352,7355,7357],{"class":124,"line":31},[122,7353,7354],{"class":478},"    runAsUser",[122,7356,482],{"class":131},[122,7358,7359],{"class":162},"10001\n",[122,7361,7362,7365],{"class":124,"line":58},[122,7363,7364],{"class":478},"    seccompProfile",[122,7366,503],{"class":131},[122,7368,7369,7372,7374],{"class":124,"line":231},[122,7370,7371],{"class":478},"      type",[122,7373,482],{"class":131},[122,7375,7376],{"class":138},"RuntimeDefault\n",[122,7378,7379,7382],{"class":124,"line":264},[122,7380,7381],{"class":478},"  containers",[122,7383,503],{"class":131},[122,7385,7386,7388,7390,7392],{"class":124,"line":272},[122,7387,3044],{"class":131},[122,7389,2801],{"class":478},[122,7391,482],{"class":131},[122,7393,2806],{"class":138},[122,7395,7396,7399,7401],{"class":124,"line":278},[122,7397,7398],{"class":478},"      image",[122,7400,482],{"class":131},[122,7402,6747],{"class":138},[122,7404,7405,7408],{"class":124,"line":285},[122,7406,7407],{"class":478},"      securityContext",[122,7409,503],{"class":131},[122,7411,7412,7415,7417],{"class":124,"line":310},[122,7413,7414],{"class":478},"        allowPrivilegeEscalation",[122,7416,482],{"class":131},[122,7418,6585],{"class":162},[122,7420,7421,7424,7426],{"class":124,"line":316},[122,7422,7423],{"class":478},"        readOnlyRootFilesystem",[122,7425,482],{"class":131},[122,7427,6430],{"class":162},[122,7429,7430,7433],{"class":124,"line":321},[122,7431,7432],{"class":478},"        capabilities",[122,7434,503],{"class":131},[122,7436,7437,7440,7442,7445],{"class":124,"line":337},[122,7438,7439],{"class":478},"          drop",[122,7441,2041],{"class":131},[122,7443,7444],{"class":138},"\"ALL\"",[122,7446,2047],{"class":131},[122,7448,7449,7452],{"class":124,"line":342},[122,7450,7451],{"class":478},"      volumeMounts",[122,7453,503],{"class":131},[122,7455,7456,7458,7460,7462],{"class":124,"line":363},[122,7457,2798],{"class":131},[122,7459,2801],{"class":478},[122,7461,482],{"class":131},[122,7463,7464],{"class":138},"tmp\n",[122,7466,7467,7470,7472],{"class":124,"line":376},[122,7468,7469],{"class":478},"          mountPath",[122,7471,482],{"class":131},[122,7473,7474],{"class":138},"\u002Ftmp\n",[122,7476,7477,7480],{"class":124,"line":417},[122,7478,7479],{"class":478},"  volumes",[122,7481,503],{"class":131},[122,7483,7484,7486,7488,7490],{"class":124,"line":450},[122,7485,3044],{"class":131},[122,7487,2801],{"class":478},[122,7489,482],{"class":131},[122,7491,7464],{"class":138},[122,7493,7494,7497],{"class":124,"line":2172},[122,7495,7496],{"class":478},"      emptyDir",[122,7498,2208],{"class":131},[97,7500,7501],{},"Node frameworks or libraries may write caches and temporary files. Discover those paths in tests and mount only the required writable directories. Do not “fix” permission errors by running as root or making the whole filesystem writable.",[97,7503,7504,7505,7508],{},"Pod Security Admission can enforce the Kubernetes Pod Security Standards at namespace level. Aim for the ",[119,7506,7507],{},"restricted"," profile for normal application namespaces. Test enforcement in staging first, because older charts and operational agents may need changes. Exceptions should be isolated to dedicated namespaces and justified, not silently weaken every workload.",[101,7510,7512],{"id":7511},"_3-secure-the-image-supply-chain","3. Secure the image supply chain",[97,7514,7515],{},"Pin deployments to digests, use minimal maintained base images, and remove compilers, package managers, test dependencies, and source maps not required at runtime. In Node projects, build production artifacts in one stage and copy only required output and production dependencies into the runtime stage. Run scanners against both dependencies and the final image; they reveal different classes of risk.",[97,7517,7518],{},"Image provenance asks more than “does it scan clean?” Sign images or publish build attestations, restrict production to trusted registries, and eventually enforce provenance with an admission policy. Protect the CI workflow and its OIDC permissions because a trusted build pipeline is part of the security boundary. A perfectly hardened Pod running an attacker-produced image is still compromised.",[97,7520,7521],{},"Use immutable references and define a patch process for base images. Digest pinning prevents surprise changes, so automation must deliberately open upgrades when a patched base is available.",[101,7523,7525],{"id":7524},"_4-treat-secrets-as-exposure-minimization","4. Treat secrets as exposure minimization",[97,7527,7528],{},"A Kubernetes Secret is base64-encoded, not inherently encrypted. Cluster configuration may encrypt Secret data at rest, but application teams should still prevent values from entering Git, images, logs, command arguments, or broad environment dumps. Prefer an external secret manager integrated through a controller or workload identity where available.",[97,7530,7531],{},"Environment variables are convenient but are commonly exposed by debug output and cannot update inside a running process. Mounted secret files can be rotated by Kubernetes, although the Node process must reread them or restart safely. Choose based on application behavior rather than assuming one mechanism guarantees safety.",[97,7533,7534,7535,7538],{},"Never return configuration objects from diagnostics endpoints. Redact authorization headers, cookies, tokens, connection strings, and personal data from structured logs. Limit who can ",[119,7536,7537],{},"exec"," into Pods, read Secrets, or view logs. Secret rotation is an operational workflow: test replacement, overlap, reload, revocation, and rollback.",[101,7540,723],{"id":722},[97,7542,7543,7544,7546,7547,7550,7551,7554],{},"Harden one Node Deployment. Give it a dedicated service account with token automount disabled, add the restricted security context above, use a read-only root filesystem with an ",[119,7545,3883],{}," at ",[119,7548,7549],{},"\u002Ftmp",", and pin the image by digest. Run the service's smoke tests, then use ",[119,7552,7553],{},"kubectl auth can-i"," to prove its service account cannot list Secrets. Finally, inspect logs and health responses for accidental configuration leakage.",[101,7556,734],{"id":733},[736,7558,7559,7566],{},[739,7560,7561],{},[742,7562,7565],{"href":7563,"rel":7564},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fsecurity\u002Fpod-security-standards\u002F",[746],"Kubernetes: Pod Security Standards",[739,7567,7568],{},[742,7569,7572],{"href":7570,"rel":7571},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Freference\u002Faccess-authn-authz\u002Frbac\u002F",[746],"Kubernetes: Using RBAC Authorization",[756,7574,7575],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":117,"searchDepth":21,"depth":21,"links":7577},[7578,7579,7580,7581,7582,7583],{"id":7032,"depth":21,"text":7033},{"id":7318,"depth":21,"text":7319},{"id":7511,"depth":21,"text":7512},{"id":7524,"depth":21,"text":7525},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Application teams do not need to administer every control-plane security feature, but they do control most workload-level risk: image contents, runtime…",{},"\u002Fkubernetes-for-web-applications\u002Fday-24",{"title":7020,"description":7584},"kubernetes-for-web-applications\u002Fday-24","5WWakFNQwCfkARbyAEAvLShQDMWrmm3ncv_UYIcfsWs",{"id":7591,"title":7592,"body":7593,"day":7946,"description":8145,"extension":769,"meta":8146,"navigation":147,"path":8147,"seo":8148,"stem":8149,"tag":774,"week":58,"weekName":59,"__hash__":8150},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-25.md","Network security and service boundaries",{"type":89,"value":7594,"toc":8137},[7595,7599,7602,7606,7609,7612,7618,7621,7632,7636,7639,7714,7717,7720,8049,8052,8056,8070,8073,8076,8080,8087,8090,8093,8095,8117,8119,8135],[92,7596,7598],{"id":7597},"day-25-network-security-and-service-boundaries","Day 25 — Network security and service boundaries",[97,7600,7601],{},"Kubernetes networking is permissive by default: if the network plugin enforces NetworkPolicy, Pods can usually still talk to one another until policies select them. For a senior engineer, network policy is executable architecture—an allowlist derived from real dependencies—not a substitute for authentication, TLS, or application authorization.",[101,7603,7605],{"id":7604},"_1-model-east-west-traffic-before-writing-policy","1. Model east-west traffic before writing policy",[97,7607,7608],{},"North-south traffic enters or leaves the cluster; east-west traffic moves between workloads inside it. A production-shaped web system may have an ingress controller calling a Nuxt frontend, the frontend calling a Node API, the API calling an external managed database and an internal Redis service, and all Pods querying cluster DNS.",[97,7610,7611],{},"Write those edges down with direction, port, protocol, and identity:",[112,7613,7616],{"className":7614,"code":7615,"language":6953,"meta":117},[6951],"ingress-controller -> frontend : TCP\u002F3000\nfrontend           -> api      : TCP\u002F3000\napi                -> redis    : TCP\u002F6379\napi                -> db.example.com : TCP\u002F5432\nall selected pods  -> cluster DNS : UDP+TCP\u002F53\n",[119,7617,7615],{"__ignoreMap":117},[97,7619,7620],{},"This exercise often uncovers accidental coupling: a frontend directly querying a database, broad internet egress, or debug tools that quietly depend on unrestricted access. NetworkPolicy selects Pods and peers by labels and namespaces; stable labels therefore become security-sensitive API. Protect who can create or relabel Pods in controlled namespaces.",[97,7622,7623,7624,7627,7628,7631],{},"NetworkPolicy operates at layers 3 and 4. Standard policy cannot express “allow HTTP GET ",[119,7625,7626],{},"\u002Fhealth"," but deny ",[119,7629,7630],{},"\u002Fadmin",",” identify a DNS name reliably, or replace service-level authentication. Service meshes, API gateways, or application controls may enforce higher-layer rules, but add them only when the requirement warrants their complexity.",[101,7633,7635],{"id":7634},"_2-start-from-default-deny-then-add-explicit-flows","2. Start from default deny, then add explicit flows",[97,7637,7638],{},"A namespace-wide default deny establishes that no ingress or egress is allowed unless another policy permits it:",[112,7640,7642],{"className":469,"code":7641,"language":471,"meta":117,"style":117},"apiVersion: networking.k8s.io\u002Fv1\nkind: NetworkPolicy\nmetadata:\n  name: default-deny\n  namespace: web\nspec:\n  podSelector: {}\n  policyTypes: [Ingress, Egress]\n",[119,7643,7644,7652,7661,7667,7676,7684,7690,7697],{"__ignoreMap":117},[122,7645,7646,7648,7650],{"class":124,"line":12},[122,7647,479],{"class":478},[122,7649,482],{"class":131},[122,7651,3349],{"class":138},[122,7653,7654,7656,7658],{"class":124,"line":21},[122,7655,490],{"class":478},[122,7657,482],{"class":131},[122,7659,7660],{"class":138},"NetworkPolicy\n",[122,7662,7663,7665],{"class":124,"line":26},[122,7664,500],{"class":478},[122,7666,503],{"class":131},[122,7668,7669,7671,7673],{"class":124,"line":31},[122,7670,508],{"class":478},[122,7672,482],{"class":131},[122,7674,7675],{"class":138},"default-deny\n",[122,7677,7678,7680,7682],{"class":124,"line":58},[122,7679,6820],{"class":478},[122,7681,482],{"class":131},[122,7683,3373],{"class":138},[122,7685,7686,7688],{"class":124,"line":231},[122,7687,2569],{"class":478},[122,7689,503],{"class":131},[122,7691,7692,7695],{"class":124,"line":264},[122,7693,7694],{"class":478},"  podSelector",[122,7696,2208],{"class":131},[122,7698,7699,7702,7704,7707,7709,7712],{"class":124,"line":272},[122,7700,7701],{"class":478},"  policyTypes",[122,7703,2041],{"class":131},[122,7705,7706],{"class":138},"Ingress",[122,7708,199],{"class":131},[122,7710,7711],{"class":138},"Egress",[122,7713,2047],{"class":131},[97,7715,7716],{},"Policies are additive. A Pod selected by this policy remains isolated for both directions until an allow policy contributes matching rules. Apply default deny in a test namespace first: DNS, telemetry, identity providers, package downloads, and external APIs are common hidden dependencies.",[97,7718,7719],{},"An API policy can permit traffic only from frontend Pods and allow DNS plus Redis egress:",[112,7721,7723],{"className":469,"code":7722,"language":471,"meta":117,"style":117},"apiVersion: networking.k8s.io\u002Fv1\nkind: NetworkPolicy\nmetadata:\n  name: api-allowed-flows\n  namespace: web\nspec:\n  podSelector:\n    matchLabels: { app: api }\n  policyTypes: [Ingress, Egress]\n  ingress:\n    - from:\n        - podSelector:\n            matchLabels: { app: frontend }\n      ports:\n        - { protocol: TCP, port: 3000 }\n  egress:\n    - to:\n        - namespaceSelector:\n            matchLabels:\n              kubernetes.io\u002Fmetadata.name: kube-system\n          podSelector:\n            matchLabels:\n              k8s-app: kube-dns\n      ports:\n        - { protocol: UDP, port: 53 }\n        - { protocol: TCP, port: 53 }\n    - to:\n        - podSelector:\n            matchLabels: { app: redis }\n      ports:\n        - { protocol: TCP, port: 6379 }\n",[119,7724,7725,7733,7741,7747,7756,7764,7770,7776,7790,7804,7811,7819,7828,7844,7851,7874,7881,7890,7899,7905,7915,7922,7928,7938,7944,7967,7988,7997,8005,8021,8027],{"__ignoreMap":117},[122,7726,7727,7729,7731],{"class":124,"line":12},[122,7728,479],{"class":478},[122,7730,482],{"class":131},[122,7732,3349],{"class":138},[122,7734,7735,7737,7739],{"class":124,"line":21},[122,7736,490],{"class":478},[122,7738,482],{"class":131},[122,7740,7660],{"class":138},[122,7742,7743,7745],{"class":124,"line":26},[122,7744,500],{"class":478},[122,7746,503],{"class":131},[122,7748,7749,7751,7753],{"class":124,"line":31},[122,7750,508],{"class":478},[122,7752,482],{"class":131},[122,7754,7755],{"class":138},"api-allowed-flows\n",[122,7757,7758,7760,7762],{"class":124,"line":58},[122,7759,6820],{"class":478},[122,7761,482],{"class":131},[122,7763,3373],{"class":138},[122,7765,7766,7768],{"class":124,"line":231},[122,7767,2569],{"class":478},[122,7769,503],{"class":131},[122,7771,7772,7774],{"class":124,"line":264},[122,7773,7694],{"class":478},[122,7775,503],{"class":131},[122,7777,7778,7780,7782,7784,7786,7788],{"class":124,"line":272},[122,7779,2738],{"class":478},[122,7781,5548],{"class":131},[122,7783,5551],{"class":478},[122,7785,482],{"class":131},[122,7787,3274],{"class":138},[122,7789,5559],{"class":131},[122,7791,7792,7794,7796,7798,7800,7802],{"class":124,"line":278},[122,7793,7701],{"class":478},[122,7795,2041],{"class":131},[122,7797,7706],{"class":138},[122,7799,199],{"class":131},[122,7801,7711],{"class":138},[122,7803,2047],{"class":131},[122,7805,7806,7809],{"class":124,"line":285},[122,7807,7808],{"class":478},"  ingress",[122,7810,503],{"class":131},[122,7812,7813,7815,7817],{"class":124,"line":310},[122,7814,3044],{"class":131},[122,7816,135],{"class":478},[122,7818,503],{"class":131},[122,7820,7821,7823,7826],{"class":124,"line":316},[122,7822,2798],{"class":131},[122,7824,7825],{"class":478},"podSelector",[122,7827,503],{"class":131},[122,7829,7830,7833,7835,7837,7839,7842],{"class":124,"line":321},[122,7831,7832],{"class":478},"            matchLabels",[122,7834,5548],{"class":131},[122,7836,5551],{"class":478},[122,7838,482],{"class":131},[122,7840,7841],{"class":138},"frontend",[122,7843,5559],{"class":131},[122,7845,7846,7849],{"class":124,"line":337},[122,7847,7848],{"class":478},"      ports",[122,7850,503],{"class":131},[122,7852,7853,7856,7859,7861,7864,7866,7868,7870,7872],{"class":124,"line":342},[122,7854,7855],{"class":131},"        - { ",[122,7857,7858],{"class":478},"protocol",[122,7860,482],{"class":131},[122,7862,7863],{"class":138},"TCP",[122,7865,199],{"class":131},[122,7867,3075],{"class":478},[122,7869,482],{"class":131},[122,7871,332],{"class":162},[122,7873,5559],{"class":131},[122,7875,7876,7879],{"class":124,"line":363},[122,7877,7878],{"class":478},"  egress",[122,7880,503],{"class":131},[122,7882,7883,7885,7888],{"class":124,"line":376},[122,7884,3044],{"class":131},[122,7886,7887],{"class":478},"to",[122,7889,503],{"class":131},[122,7891,7892,7894,7897],{"class":124,"line":417},[122,7893,2798],{"class":131},[122,7895,7896],{"class":478},"namespaceSelector",[122,7898,503],{"class":131},[122,7900,7901,7903],{"class":124,"line":450},[122,7902,7832],{"class":478},[122,7904,503],{"class":131},[122,7906,7907,7910,7912],{"class":124,"line":2172},[122,7908,7909],{"class":478},"              kubernetes.io\u002Fmetadata.name",[122,7911,482],{"class":131},[122,7913,7914],{"class":138},"kube-system\n",[122,7916,7917,7920],{"class":124,"line":2183},[122,7918,7919],{"class":478},"          podSelector",[122,7921,503],{"class":131},[122,7923,7924,7926],{"class":124,"line":2194},[122,7925,7832],{"class":478},[122,7927,503],{"class":131},[122,7929,7930,7933,7935],{"class":124,"line":2202},[122,7931,7932],{"class":478},"              k8s-app",[122,7934,482],{"class":131},[122,7936,7937],{"class":138},"kube-dns\n",[122,7939,7940,7942],{"class":124,"line":7302},[122,7941,7848],{"class":478},[122,7943,503],{"class":131},[122,7945,7947,7949,7951,7953,7956,7958,7960,7962,7965],{"class":124,"line":7946},25,[122,7948,7855],{"class":131},[122,7950,7858],{"class":478},[122,7952,482],{"class":131},[122,7954,7955],{"class":138},"UDP",[122,7957,199],{"class":131},[122,7959,3075],{"class":478},[122,7961,482],{"class":131},[122,7963,7964],{"class":162},"53",[122,7966,5559],{"class":131},[122,7968,7970,7972,7974,7976,7978,7980,7982,7984,7986],{"class":124,"line":7969},26,[122,7971,7855],{"class":131},[122,7973,7858],{"class":478},[122,7975,482],{"class":131},[122,7977,7863],{"class":138},[122,7979,199],{"class":131},[122,7981,3075],{"class":478},[122,7983,482],{"class":131},[122,7985,7964],{"class":162},[122,7987,5559],{"class":131},[122,7989,7991,7993,7995],{"class":124,"line":7990},27,[122,7992,3044],{"class":131},[122,7994,7887],{"class":478},[122,7996,503],{"class":131},[122,7998,7999,8001,8003],{"class":124,"line":767},[122,8000,2798],{"class":131},[122,8002,7825],{"class":478},[122,8004,503],{"class":131},[122,8006,8008,8010,8012,8014,8016,8019],{"class":124,"line":8007},29,[122,8009,7832],{"class":478},[122,8011,5548],{"class":131},[122,8013,5551],{"class":478},[122,8015,482],{"class":131},[122,8017,8018],{"class":138},"redis",[122,8020,5559],{"class":131},[122,8022,8023,8025],{"class":124,"line":10},[122,8024,7848],{"class":478},[122,8026,503],{"class":131},[122,8028,8030,8032,8034,8036,8038,8040,8042,8044,8047],{"class":124,"line":8029},31,[122,8031,7855],{"class":131},[122,8033,7858],{"class":478},[122,8035,482],{"class":131},[122,8037,7863],{"class":138},[122,8039,199],{"class":131},[122,8041,3075],{"class":478},[122,8043,482],{"class":131},[122,8045,8046],{"class":162},"6379",[122,8048,5559],{"class":131},[97,8050,8051],{},"Verify the actual DNS labels in your cluster; CoreDNS labels vary by distribution. Also confirm the installed CNI supports and enforces NetworkPolicy. The API may accept policy objects even when the network implementation does nothing with them.",[101,8053,8055],{"id":8054},"_3-understand-selectors-directions-and-reply-traffic","3. Understand selectors, directions, and reply traffic",[97,8057,8058,8059,8061,8062,8064,8065,3536,8067,8069],{},"A ",[119,8060,7825],{}," without a ",[119,8063,7896],{}," selects Pods in the policy's namespace. To identify a peer in another namespace, combine both selectors in the same ",[119,8066,135],{},[119,8068,7887],{}," item. Be careful with YAML list structure: two separate items mean logical OR, while selectors in one item are combined as AND.",[97,8071,8072],{},"Ingress isolation is evaluated at the destination; egress isolation is evaluated at the source. For a new connection to succeed, egress from the source and ingress to the destination must both permit it when both Pods are isolated. Return packets for an allowed connection are generally permitted automatically, but exact behavior can depend on the network implementation.",[97,8074,8075],{},"Ingress controllers frequently run in another namespace. Allowing “any Pod with label app=ingress” without constraining its namespace could permit an attacker to create a matching Pod elsewhere. Combine namespace and Pod selectors, and use immutable or governance-controlled namespace labels where possible.",[101,8077,8079],{"id":8078},"_4-egress-and-external-services-need-special-care","4. Egress and external services need special care",[97,8081,8082,8083,8086],{},"Standard NetworkPolicy primarily addresses IPs, ports, Pods, and namespaces. Managed databases and third-party APIs may resolve to changing addresses, making ",[119,8084,8085],{},"ipBlock"," rules brittle. Some CNI implementations add DNS-aware or fully qualified domain name policies; otherwise route egress through a controlled gateway or use stable provider network ranges where appropriate.",[97,8088,8089],{},"Do not assume allowed network access means trusted access. Redis should require authentication where supported; databases should use TLS and individual credentials; APIs should authorize each request. Network policy limits reachability and blast radius, complementing—not replacing—identity controls.",[97,8091,8092],{},"Testing must include allowed and denied paths. Launch a temporary Pod with the same labels and service account as the caller, test DNS resolution and TCP connection, then test from a deliberately unauthorized Pod. Observe drops using CNI tooling, flow logs, or metrics. A policy that only passes the happy path may still be overly broad.",[101,8094,723],{"id":722},[97,8096,8097,8098,199,8100,1044,8102,8105,8106,199,8109,8112,8113,8116],{},"Create a namespace with three tiny workloads labeled ",[119,8099,7841],{},[119,8101,3274],{},[119,8103,8104],{},"data",". Confirm all three can initially connect. Apply a default-deny ingress-and-egress policy, then add policies allowing ",[119,8107,8108],{},"frontend -> api:3000",[119,8110,8111],{},"api -> data:6379",", and DNS only. Prove each allowed edge works and prove ",[119,8114,8115],{},"frontend -> data"," fails. Save a dependency table beside the manifests so future reviewers can compare intent with policy.",[101,8118,734],{"id":733},[736,8120,8121,8128],{},[739,8122,8123],{},[742,8124,8127],{"href":8125,"rel":8126},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fnetwork-policies\u002F",[746],"Kubernetes: Network Policies",[739,8129,8130],{},[742,8131,8134],{"href":8132,"rel":8133},"https:\u002F\u002Feditor.networkpolicy.io\u002F",[746],"NetworkPolicy Editor by Cilium",[756,8136,7003],{},{"title":117,"searchDepth":21,"depth":21,"links":8138},[8139,8140,8141,8142,8143,8144],{"id":7604,"depth":21,"text":7605},{"id":7634,"depth":21,"text":7635},{"id":8054,"depth":21,"text":8055},{"id":8078,"depth":21,"text":8079},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Kubernetes networking is permissive by default: if the network plugin enforces NetworkPolicy, Pods can usually still talk to one another until policies…",{},"\u002Fkubernetes-for-web-applications\u002Fday-25",{"title":7592,"description":8145},"kubernetes-for-web-applications\u002Fday-25","rLMFcDzHGsJRliCpngcJWXNdpHzhV01SQzLG20wl7Vs",{"id":8152,"title":8153,"body":8154,"day":7969,"description":8779,"extension":769,"meta":8780,"navigation":147,"path":8781,"seo":8782,"stem":8783,"tag":774,"week":58,"weekName":59,"__hash__":8784},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-26.md","Multi-environment platform design",{"type":89,"value":8155,"toc":8771},[8156,8160,8163,8167,8179,8182,8456,8459,8463,8466,8469,8483,8492,8496,8499,8573,8580,8583,8696,8699,8703,8706,8709,8729,8732,8734,8750,8752,8768],[92,8157,8159],{"id":8158},"day-26-multi-environment-platform-design","Day 26 — Multi-environment platform design",[97,8161,8162],{},"A senior engineer should treat environment topology as an explicit risk and ownership decision, not as a folder-naming convention. The right design makes production hard to damage, keeps staging useful, and gives developers fast feedback without multiplying platform work.",[101,8164,8166],{"id":8165},"_1-namespaces-are-boundaries-but-not-complete-isolation","1. Namespaces are boundaries, but not complete isolation",[97,8168,8169,8170,8173,8174,1306,8176,8178],{},"A namespace scopes names and many policies. It lets ",[119,8171,8172],{},"web"," exist in both ",[119,8175,5670],{},[119,8177,5673],{},", and gives you a natural target for RBAC, quotas, default resource limits, and NetworkPolicies. It is not a security sandbox by itself: nodes, cluster-scoped resources, the control plane, and often ingress controllers are shared.",[97,8180,8181],{},"Start each namespace with guardrails rather than adding them after an incident:",[112,8183,8185],{"className":469,"code":8184,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: Namespace\nmetadata:\n  name: staging\n  labels:\n    pod-security.kubernetes.io\u002Fenforce: restricted\n---\napiVersion: v1\nkind: ResourceQuota\nmetadata:\n  name: workload-budget\n  namespace: staging\nspec:\n  hard:\n    requests.cpu: \"4\"\n    requests.memory: 8Gi\n    limits.cpu: \"8\"\n    limits.memory: 16Gi\n    pods: \"30\"\n---\napiVersion: v1\nkind: LimitRange\nmetadata:\n  name: container-defaults\n  namespace: staging\nspec:\n  limits:\n    - type: Container\n      defaultRequest: { cpu: 100m, memory: 128Mi }\n      default: { cpu: 500m, memory: 512Mi }\n",[119,8186,8187,8195,8204,8210,8219,8226,8236,8240,8248,8257,8263,8272,8280,8286,8293,8303,8313,8323,8333,8343,8347,8355,8364,8370,8379,8387,8393,8399,8410,8433],{"__ignoreMap":117},[122,8188,8189,8191,8193],{"class":124,"line":12},[122,8190,479],{"class":478},[122,8192,482],{"class":131},[122,8194,485],{"class":138},[122,8196,8197,8199,8201],{"class":124,"line":21},[122,8198,490],{"class":478},[122,8200,482],{"class":131},[122,8202,8203],{"class":138},"Namespace\n",[122,8205,8206,8208],{"class":124,"line":26},[122,8207,500],{"class":478},[122,8209,503],{"class":131},[122,8211,8212,8214,8216],{"class":124,"line":31},[122,8213,508],{"class":478},[122,8215,482],{"class":131},[122,8217,8218],{"class":138},"staging\n",[122,8220,8221,8224],{"class":124,"line":58},[122,8222,8223],{"class":478},"  labels",[122,8225,503],{"class":131},[122,8227,8228,8231,8233],{"class":124,"line":231},[122,8229,8230],{"class":478},"    pod-security.kubernetes.io\u002Fenforce",[122,8232,482],{"class":131},[122,8234,8235],{"class":138},"restricted\n",[122,8237,8238],{"class":124,"line":264},[122,8239,7200],{"class":182},[122,8241,8242,8244,8246],{"class":124,"line":272},[122,8243,479],{"class":478},[122,8245,482],{"class":131},[122,8247,485],{"class":138},[122,8249,8250,8252,8254],{"class":124,"line":278},[122,8251,490],{"class":478},[122,8253,482],{"class":131},[122,8255,8256],{"class":138},"ResourceQuota\n",[122,8258,8259,8261],{"class":124,"line":285},[122,8260,500],{"class":478},[122,8262,503],{"class":131},[122,8264,8265,8267,8269],{"class":124,"line":310},[122,8266,508],{"class":478},[122,8268,482],{"class":131},[122,8270,8271],{"class":138},"workload-budget\n",[122,8273,8274,8276,8278],{"class":124,"line":316},[122,8275,6820],{"class":478},[122,8277,482],{"class":131},[122,8279,8218],{"class":138},[122,8281,8282,8284],{"class":124,"line":321},[122,8283,2569],{"class":478},[122,8285,503],{"class":131},[122,8287,8288,8291],{"class":124,"line":337},[122,8289,8290],{"class":478},"  hard",[122,8292,503],{"class":131},[122,8294,8295,8298,8300],{"class":124,"line":342},[122,8296,8297],{"class":478},"    requests.cpu",[122,8299,482],{"class":131},[122,8301,8302],{"class":138},"\"4\"\n",[122,8304,8305,8308,8310],{"class":124,"line":363},[122,8306,8307],{"class":478},"    requests.memory",[122,8309,482],{"class":131},[122,8311,8312],{"class":138},"8Gi\n",[122,8314,8315,8318,8320],{"class":124,"line":376},[122,8316,8317],{"class":478},"    limits.cpu",[122,8319,482],{"class":131},[122,8321,8322],{"class":138},"\"8\"\n",[122,8324,8325,8328,8330],{"class":124,"line":417},[122,8326,8327],{"class":478},"    limits.memory",[122,8329,482],{"class":131},[122,8331,8332],{"class":138},"16Gi\n",[122,8334,8335,8338,8340],{"class":124,"line":450},[122,8336,8337],{"class":478},"    pods",[122,8339,482],{"class":131},[122,8341,8342],{"class":138},"\"30\"\n",[122,8344,8345],{"class":124,"line":2172},[122,8346,7200],{"class":182},[122,8348,8349,8351,8353],{"class":124,"line":2183},[122,8350,479],{"class":478},[122,8352,482],{"class":131},[122,8354,485],{"class":138},[122,8356,8357,8359,8361],{"class":124,"line":2194},[122,8358,490],{"class":478},[122,8360,482],{"class":131},[122,8362,8363],{"class":138},"LimitRange\n",[122,8365,8366,8368],{"class":124,"line":2202},[122,8367,500],{"class":478},[122,8369,503],{"class":131},[122,8371,8372,8374,8376],{"class":124,"line":7302},[122,8373,508],{"class":478},[122,8375,482],{"class":131},[122,8377,8378],{"class":138},"container-defaults\n",[122,8380,8381,8383,8385],{"class":124,"line":7946},[122,8382,6820],{"class":478},[122,8384,482],{"class":131},[122,8386,8218],{"class":138},[122,8388,8389,8391],{"class":124,"line":7969},[122,8390,2569],{"class":478},[122,8392,503],{"class":131},[122,8394,8395,8397],{"class":124,"line":7990},[122,8396,4152],{"class":478},[122,8398,503],{"class":131},[122,8400,8401,8403,8405,8407],{"class":124,"line":767},[122,8402,3044],{"class":131},[122,8404,5010],{"class":478},[122,8406,482],{"class":131},[122,8408,8409],{"class":138},"Container\n",[122,8411,8412,8415,8417,8419,8421,8423,8425,8427,8429,8431],{"class":124,"line":8007},[122,8413,8414],{"class":478},"      defaultRequest",[122,8416,5548],{"class":131},[122,8418,5754],{"class":478},[122,8420,482],{"class":131},[122,8422,5759],{"class":138},[122,8424,199],{"class":131},[122,8426,5764],{"class":478},[122,8428,482],{"class":131},[122,8430,5769],{"class":138},[122,8432,5559],{"class":131},[122,8434,8435,8438,8440,8442,8444,8446,8448,8450,8452,8454],{"class":124,"line":10},[122,8436,8437],{"class":478},"      default",[122,8439,5548],{"class":131},[122,8441,5754],{"class":478},[122,8443,482],{"class":131},[122,8445,4098],{"class":138},[122,8447,199],{"class":131},[122,8449,5764],{"class":478},[122,8451,482],{"class":131},[122,8453,6149],{"class":138},[122,8455,5559],{"class":131},[97,8457,8458],{},"Also scope service accounts and deployment permissions per namespace. A CI identity that can update staging should not automatically update production.",[101,8460,8462],{"id":8461},"_2-decide-where-a-cluster-boundary-is-worth-the-cost","2. Decide where a cluster boundary is worth the cost",[97,8464,8465],{},"A separate cluster gives a stronger failure, access, and change boundary. A broken cluster-wide admission policy, CNI upgrade, or privileged workload in development cannot take down production if production has its own cluster. The cost is duplicated infrastructure, upgrades, observability, ingress, and operational attention.",[97,8467,8468],{},"A pragmatic small-product strategy is often:",[736,8470,8471,8474,8477,8480],{},[739,8472,8473],{},"one non-production cluster with namespaces for development and staging;",[739,8475,8476],{},"one production cluster with stricter access and change controls;",[739,8478,8479],{},"ephemeral preview environments as namespaces, with quotas and automatic expiry;",[739,8481,8482],{},"separate cloud accounts or projects for production when billing and identity isolation matter.",[97,8484,8485,8486,3536,8488,8491],{},"Do not create one cluster per developer unless a concrete isolation or testing requirement justifies it. Local ",[119,8487,490],{},[119,8489,8490],{},"minikube"," clusters usually serve experimentation better. Conversely, do not place production in the same cluster as unrestricted experiments merely to save a modest control-plane fee.",[101,8493,8495],{"id":8494},"_3-promote-artifacts-do-not-rebuild-per-environment","3. Promote artifacts; do not rebuild per environment",[97,8497,8498],{},"Environment parity does not mean identical capacity. It means the same application artifact, deployment structure, and behavioral assumptions. Build the Node image once, identify it by digest, and promote that digest:",[112,8500,8502],{"className":469,"code":8501,"language":471,"meta":117,"style":117},"# production kustomization.yaml\nimages:\n  - name: ghcr.io\u002Facme\u002Fapi\n    newName: ghcr.io\u002Facme\u002Fapi\n    digest: sha256:4a1f...c92b\npatches:\n  - path: replicas.yaml\n  - path: production-resources.yaml\n",[119,8503,8504,8509,8515,8526,8535,8545,8551,8562],{"__ignoreMap":117},[122,8505,8506],{"class":124,"line":12},[122,8507,8508],{"class":281},"# production kustomization.yaml\n",[122,8510,8511,8513],{"class":124,"line":21},[122,8512,5930],{"class":478},[122,8514,503],{"class":131},[122,8516,8517,8519,8521,8523],{"class":124,"line":26},[122,8518,3696],{"class":131},[122,8520,2801],{"class":478},[122,8522,482],{"class":131},[122,8524,8525],{"class":138},"ghcr.io\u002Facme\u002Fapi\n",[122,8527,8528,8531,8533],{"class":124,"line":31},[122,8529,8530],{"class":478},"    newName",[122,8532,482],{"class":131},[122,8534,8525],{"class":138},[122,8536,8537,8540,8542],{"class":124,"line":58},[122,8538,8539],{"class":478},"    digest",[122,8541,482],{"class":131},[122,8543,8544],{"class":138},"sha256:4a1f...c92b\n",[122,8546,8547,8549],{"class":124,"line":231},[122,8548,6017],{"class":478},[122,8550,503],{"class":131},[122,8552,8553,8555,8557,8559],{"class":124,"line":264},[122,8554,3696],{"class":131},[122,8556,3461],{"class":478},[122,8558,482],{"class":131},[122,8560,8561],{"class":138},"replicas.yaml\n",[122,8563,8564,8566,8568,8570],{"class":124,"line":272},[122,8565,3696],{"class":131},[122,8567,3461],{"class":478},[122,8569,482],{"class":131},[122,8571,8572],{"class":138},"production-resources.yaml\n",[97,8574,8575,8576,8579],{},"Runtime configuration varies: database endpoints, replica counts, domain names, and external secret references. The executable image must not. Rebuilding ",[119,8577,8578],{},"api:production"," from the same commit can still produce a different dependency tree or base layer, invalidating staging evidence.",[97,8581,8582],{},"For Node.js, validate required runtime configuration at startup:",[112,8584,8586],{"className":114,"code":8585,"language":116,"meta":117,"style":117},"import { z } from \"zod\";\n\nexport const config = z.object({\n  NODE_ENV: z.enum([\"development\", \"test\", \"production\"]),\n  DATABASE_URL: z.string().url(),\n  PUBLIC_ORIGIN: z.string().url()\n}).parse(process.env);\n",[119,8587,8588,8602,8606,8627,8654,8671,8685],{"__ignoreMap":117},[122,8589,8590,8592,8595,8597,8600],{"class":124,"line":12},[122,8591,128],{"class":127},[122,8593,8594],{"class":131}," { z } ",[122,8596,135],{"class":127},[122,8598,8599],{"class":138}," \"zod\"",[122,8601,142],{"class":131},[122,8603,8604],{"class":124,"line":21},[122,8605,148],{"emptyLinePlaceholder":147},[122,8607,8608,8610,8613,8616,8618,8621,8624],{"class":124,"line":26},[122,8609,561],{"class":127},[122,8611,8612],{"class":127}," const",[122,8614,8615],{"class":162}," config",[122,8617,176],{"class":127},[122,8619,8620],{"class":131}," z.",[122,8622,8623],{"class":182},"object",[122,8625,8626],{"class":131},"({\n",[122,8628,8629,8632,8635,8638,8641,8643,8646,8648,8651],{"class":124,"line":31},[122,8630,8631],{"class":131},"  NODE_ENV: z.",[122,8633,8634],{"class":182},"enum",[122,8636,8637],{"class":131},"([",[122,8639,8640],{"class":138},"\"development\"",[122,8642,199],{"class":131},[122,8644,8645],{"class":138},"\"test\"",[122,8647,199],{"class":131},[122,8649,8650],{"class":138},"\"production\"",[122,8652,8653],{"class":131},"]),\n",[122,8655,8656,8659,8662,8665,8668],{"class":124,"line":58},[122,8657,8658],{"class":131},"  DATABASE_URL: z.",[122,8660,8661],{"class":182},"string",[122,8663,8664],{"class":131},"().",[122,8666,8667],{"class":182},"url",[122,8669,8670],{"class":131},"(),\n",[122,8672,8673,8676,8678,8680,8682],{"class":124,"line":231},[122,8674,8675],{"class":131},"  PUBLIC_ORIGIN: z.",[122,8677,8661],{"class":182},[122,8679,8664],{"class":131},[122,8681,8667],{"class":182},[122,8683,8684],{"class":131},"()\n",[122,8686,8687,8690,8693],{"class":124,"line":264},[122,8688,8689],{"class":131},"}).",[122,8691,8692],{"class":182},"parse",[122,8694,8695],{"class":131},"(process.env);\n",[97,8697,8698],{},"A missing value should fail deployment readiness immediately, not surface as a malformed request hours later.",[101,8700,8702],{"id":8701},"_4-define-promotion-and-data-policies-explicitly","4. Define promotion and data policies explicitly",[97,8704,8705],{},"Staging is valuable only if its purpose is clear. Use it for release-candidate verification, integration with realistic dependencies, migration rehearsal, and operational checks. It should not become an unstable shared development dump.",[97,8707,8708],{},"Write down answers to these questions:",[736,8710,8711,8714,8717,8720,8723,8726],{},[739,8712,8713],{},"Who may deploy to each environment, and through which path?",[739,8715,8716],{},"Does production require approval, a pull request, or a deployment window?",[739,8718,8719],{},"How are schema migrations tested and ordered relative to application rollout?",[739,8721,8722],{},"Is production data prohibited outside production, or sanitized before copying?",[739,8724,8725],{},"What is the rollback mechanism, and who owns it?",[739,8727,8728],{},"How are preview namespaces expired and their DNS, secrets, and databases removed?",[97,8730,8731],{},"Prefer backward-compatible database migrations: expand the schema, deploy code that supports old and new forms, migrate data, then contract later. This keeps promotion and rollback viable across independently changing application replicas.",[101,8733,723],{"id":722},[97,8735,8736,8737,8739,8740,1044,8743,8746,8747,916],{},"Create a one-page environment decision record for a small Nuxt frontend and Node API. Choose namespace, cluster, and cloud-account boundaries for local, preview, staging, and production. Add a table with deployment authority, data classification, artifact source, capacity, expiry, and rollback strategy. Then create a ",[119,8738,5670],{}," namespace locally with the Pod Security label, ",[119,8741,8742],{},"ResourceQuota",[119,8744,8745],{},"LimitRange"," above. Deploy a pod without resource settings and inspect the defaults with ",[119,8748,8749],{},"kubectl get pod -n staging -o yaml",[101,8751,734],{"id":733},[736,8753,8754,8761],{},[739,8755,8756],{},[742,8757,8760],{"href":8758,"rel":8759},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Foverview\u002Fworking-with-objects\u002Fnamespaces\u002F",[746],"Kubernetes: Namespaces",[739,8762,8763],{},[742,8764,8767],{"href":8765,"rel":8766},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fsecurity\u002Fmulti-tenancy\u002F",[746],"Kubernetes: Multi-tenancy",[756,8769,8770],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":117,"searchDepth":21,"depth":21,"links":8772},[8773,8774,8775,8776,8777,8778],{"id":8165,"depth":21,"text":8166},{"id":8461,"depth":21,"text":8462},{"id":8494,"depth":21,"text":8495},{"id":8701,"depth":21,"text":8702},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"A senior engineer should treat environment topology as an explicit risk and ownership decision, not as a folder-naming convention. The right design…",{},"\u002Fkubernetes-for-web-applications\u002Fday-26",{"title":8153,"description":8779},"kubernetes-for-web-applications\u002Fday-26","CFDwreQ457D-JEKslXZRbqkG8hL4WI95Hzo36Etlf_E",{"id":8786,"title":8787,"body":8788,"day":7990,"description":9208,"extension":769,"meta":9209,"navigation":147,"path":9210,"seo":9211,"stem":9212,"tag":774,"week":58,"weekName":59,"__hash__":9213},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-27.md","Managed Kubernetes and cloud primitives",{"type":89,"value":8789,"toc":9200},[8790,8794,8797,8801,8804,8807,8824,8827,8831,8841,8964,8971,8974,8978,8981,9133,9136,9140,9143,9164,9167,9170,9172,9179,9181,9197],[92,8791,8793],{"id":8792},"day-27-managed-kubernetes-and-cloud-primitives","Day 27 — Managed Kubernetes and cloud primitives",[97,8795,8796],{},"Managed Kubernetes removes much of the control-plane toil, but it does not turn a cluster into a complete application platform. Senior engineers still need to understand the cloud resources behind Services, Ingress, storage, identity, and scaling because those integrations determine reliability, security, and cost.",[101,8798,8800],{"id":8799},"_1-know-what-the-provider-manages","1. Know what the provider manages",[97,8802,8803],{},"In EKS, GKE, or AKS, the provider operates the API server and etcd, handles control-plane availability, and usually offers integrated upgrades and backups. Your team still owns application manifests, workload security, node strategy unless using a serverless mode, add-ons, policies, observability, and incident response.",[97,8805,8806],{},"The shared-responsibility line is easy to misread. A healthy managed control plane does not prevent:",[736,8808,8809,8812,8815,8818,8821],{},[739,8810,8811],{},"unschedulable Pods caused by insufficient node capacity;",[739,8813,8814],{},"an incompatible CNI or ingress-controller upgrade;",[739,8816,8817],{},"overly broad RBAC or leaked application credentials;",[739,8819,8820],{},"a bad Deployment, destructive migration, or exhausted database;",[739,8822,8823],{},"unexpected egress, load-balancer, log-ingestion, and storage costs.",[97,8825,8826],{},"Record Kubernetes and add-on versions, define an upgrade cadence, and test upgrades in non-production. “Managed” should reduce undifferentiated work, not remove operational ownership.",[101,8828,8830],{"id":8829},"_2-kubernetes-objects-often-provision-billable-cloud-resources","2. Kubernetes objects often provision billable cloud resources",[97,8832,8058,8833,8836,8837,8840],{},[119,8834,8835],{},"Service"," of type ",[119,8838,8839],{},"LoadBalancer"," is an infrastructure request. A cloud controller may create a load balancer, public IP, health checks, and firewall rules. An Ingress or Gateway may then configure listeners and routes. DNS and certificate controllers can automate records and TLS, but each controller needs carefully scoped cloud permissions.",[112,8842,8844],{"className":469,"code":8843,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: Service\nmetadata:\n  name: ingress-public\n  annotations:\n    # Exact annotation is provider\u002Fcontroller specific.\n    service.beta.kubernetes.io\u002Faws-load-balancer-scheme: internet-facing\nspec:\n  type: LoadBalancer\n  selector:\n    app: ingress-controller\n  ports:\n    - name: https\n      port: 443\n      targetPort: 8443\n",[119,8845,8846,8854,8862,8868,8877,8884,8889,8899,8905,8914,8920,8929,8935,8946,8955],{"__ignoreMap":117},[122,8847,8848,8850,8852],{"class":124,"line":12},[122,8849,479],{"class":478},[122,8851,482],{"class":131},[122,8853,485],{"class":138},[122,8855,8856,8858,8860],{"class":124,"line":21},[122,8857,490],{"class":478},[122,8859,482],{"class":131},[122,8861,2997],{"class":138},[122,8863,8864,8866],{"class":124,"line":26},[122,8865,500],{"class":478},[122,8867,503],{"class":131},[122,8869,8870,8872,8874],{"class":124,"line":31},[122,8871,508],{"class":478},[122,8873,482],{"class":131},[122,8875,8876],{"class":138},"ingress-public\n",[122,8878,8879,8882],{"class":124,"line":58},[122,8880,8881],{"class":478},"  annotations",[122,8883,503],{"class":131},[122,8885,8886],{"class":124,"line":231},[122,8887,8888],{"class":281},"    # Exact annotation is provider\u002Fcontroller specific.\n",[122,8890,8891,8894,8896],{"class":124,"line":264},[122,8892,8893],{"class":478},"    service.beta.kubernetes.io\u002Faws-load-balancer-scheme",[122,8895,482],{"class":131},[122,8897,8898],{"class":138},"internet-facing\n",[122,8900,8901,8903],{"class":124,"line":272},[122,8902,2569],{"class":478},[122,8904,503],{"class":131},[122,8906,8907,8909,8911],{"class":124,"line":278},[122,8908,4704],{"class":478},[122,8910,482],{"class":131},[122,8912,8913],{"class":138},"LoadBalancer\n",[122,8915,8916,8918],{"class":124,"line":285},[122,8917,2731],{"class":478},[122,8919,503],{"class":131},[122,8921,8922,8924,8926],{"class":124,"line":310},[122,8923,3028],{"class":478},[122,8925,482],{"class":131},[122,8927,8928],{"class":138},"ingress-controller\n",[122,8930,8931,8933],{"class":124,"line":316},[122,8932,3037],{"class":478},[122,8934,503],{"class":131},[122,8936,8937,8939,8941,8943],{"class":124,"line":321},[122,8938,3044],{"class":131},[122,8940,2801],{"class":478},[122,8942,482],{"class":131},[122,8944,8945],{"class":138},"https\n",[122,8947,8948,8950,8952],{"class":124,"line":337},[122,8949,3055],{"class":478},[122,8951,482],{"class":131},[122,8953,8954],{"class":162},"443\n",[122,8956,8957,8959,8961],{"class":124,"line":342},[122,8958,3065],{"class":478},[122,8960,482],{"class":131},[122,8962,8963],{"class":162},"8443\n",[97,8965,8966,8967,8970],{},"Prefer one shared ingress or Gateway where isolation requirements allow it, rather than one load balancer per small service. Use ",[119,8968,8969],{},"external-dns","-style automation and cert-manager deliberately: automation increases consistency, but a compromised controller can alter a large DNS zone or request certificates broadly.",[97,8972,8973],{},"PersistentVolumeClaims similarly trigger provider behavior through a StorageClass. Understand zone binding, expansion, snapshots, performance tiers, and reclaim policy. A zonal disk can constrain Pod scheduling; deleting a claim with the wrong reclaim policy can destroy data.",[101,8975,8977],{"id":8976},"_3-use-workload-identity-instead-of-long-lived-cloud-keys","3. Use workload identity instead of long-lived cloud keys",[97,8979,8980],{},"Cloud providers offer a way to map a Kubernetes ServiceAccount to a cloud IAM principal. The mechanism differs—such as IAM Roles for Service Accounts or Workload Identity—but the design is consistent: a Pod receives short-lived, scoped credentials without storing an access key in a Secret.",[112,8982,8984],{"className":469,"code":8983,"language":471,"meta":117,"style":117},"apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: invoice-worker\n  namespace: production\n  annotations:\n    # Example only; use your provider's identity integration.\n    eks.amazonaws.com\u002Frole-arn: arn:aws:iam::123456789012:role\u002Finvoice-worker\n---\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: invoice-worker\nspec:\n  template:\n    spec:\n      serviceAccountName: invoice-worker\n      containers:\n        - name: worker\n          image: ghcr.io\u002Facme\u002Finvoice-worker@sha256:abc123\n",[119,8985,8986,8994,9002,9008,9017,9025,9031,9036,9046,9050,9058,9066,9072,9080,9086,9092,9098,9107,9113,9124],{"__ignoreMap":117},[122,8987,8988,8990,8992],{"class":124,"line":12},[122,8989,479],{"class":478},[122,8991,482],{"class":131},[122,8993,485],{"class":138},[122,8995,8996,8998,9000],{"class":124,"line":21},[122,8997,490],{"class":478},[122,8999,482],{"class":131},[122,9001,7058],{"class":138},[122,9003,9004,9006],{"class":124,"line":26},[122,9005,500],{"class":478},[122,9007,503],{"class":131},[122,9009,9010,9012,9014],{"class":124,"line":31},[122,9011,508],{"class":478},[122,9013,482],{"class":131},[122,9015,9016],{"class":138},"invoice-worker\n",[122,9018,9019,9021,9023],{"class":124,"line":58},[122,9020,6820],{"class":478},[122,9022,482],{"class":131},[122,9024,5661],{"class":138},[122,9026,9027,9029],{"class":124,"line":231},[122,9028,8881],{"class":478},[122,9030,503],{"class":131},[122,9032,9033],{"class":124,"line":264},[122,9034,9035],{"class":281},"    # Example only; use your provider's identity integration.\n",[122,9037,9038,9041,9043],{"class":124,"line":272},[122,9039,9040],{"class":478},"    eks.amazonaws.com\u002Frole-arn",[122,9042,482],{"class":131},[122,9044,9045],{"class":138},"arn:aws:iam::123456789012:role\u002Finvoice-worker\n",[122,9047,9048],{"class":124,"line":278},[122,9049,7200],{"class":182},[122,9051,9052,9054,9056],{"class":124,"line":285},[122,9053,479],{"class":478},[122,9055,482],{"class":131},[122,9057,2686],{"class":138},[122,9059,9060,9062,9064],{"class":124,"line":310},[122,9061,490],{"class":478},[122,9063,482],{"class":131},[122,9065,2695],{"class":138},[122,9067,9068,9070],{"class":124,"line":316},[122,9069,500],{"class":478},[122,9071,503],{"class":131},[122,9073,9074,9076,9078],{"class":124,"line":321},[122,9075,508],{"class":478},[122,9077,482],{"class":131},[122,9079,9016],{"class":138},[122,9081,9082,9084],{"class":124,"line":337},[122,9083,2569],{"class":478},[122,9085,503],{"class":131},[122,9087,9088,9090],{"class":124,"line":342},[122,9089,2754],{"class":478},[122,9091,503],{"class":131},[122,9093,9094,9096],{"class":124,"line":363},[122,9095,2784],{"class":478},[122,9097,503],{"class":131},[122,9099,9100,9103,9105],{"class":124,"line":376},[122,9101,9102],{"class":478},"      serviceAccountName",[122,9104,482],{"class":131},[122,9106,9016],{"class":138},[122,9108,9109,9111],{"class":124,"line":417},[122,9110,2791],{"class":478},[122,9112,503],{"class":131},[122,9114,9115,9117,9119,9121],{"class":124,"line":450},[122,9116,2798],{"class":131},[122,9118,2801],{"class":478},[122,9120,482],{"class":131},[122,9122,9123],{"class":138},"worker\n",[122,9125,9126,9128,9130],{"class":124,"line":2172},[122,9127,2811],{"class":478},[122,9129,482],{"class":131},[122,9131,9132],{"class":138},"ghcr.io\u002Facme\u002Finvoice-worker@sha256:abc123\n",[97,9134,9135],{},"Grant that cloud role only the specific queue, bucket prefix, or secret it needs. Keep Kubernetes RBAC separate from cloud IAM: one governs access to the Kubernetes API; the other governs provider resources.",[101,9137,9139],{"id":9138},"_4-compare-platforms-using-workload-needs-and-total-ownership","4. Compare platforms using workload needs and total ownership",[97,9141,9142],{},"Do not choose managed Kubernetes merely because it is the most flexible option. Compare at least three paths:",[736,9144,9145,9152,9158],{},[739,9146,9147,9151],{},[9148,9149,9150],"strong",{},"Local Kubernetes:"," ideal for learning and some integration tests; not a production availability strategy.",[739,9153,9154,9157],{},[9148,9155,9156],{},"Managed Kubernetes:"," useful for multiple services, specialized controllers, portability requirements, or teams that can own a platform.",[739,9159,9160,9163],{},[9148,9161,9162],{},"Serverless containers\u002Fapplication platforms:"," often better for a small stateless Node service when rapid delivery and low operational burden matter more than control.",[97,9165,9166],{},"Estimate total cost, not just compute. Include cluster fees, idle node headroom, load balancers, NAT and cross-zone traffic, disks and snapshots, observability ingestion, engineering time, and incident burden. Autoscaling reduces some waste but cannot eliminate baseline capacity or poorly designed traffic paths.",[97,9168,9169],{},"For a Nuxt application, separate static assets onto an object store\u002FCDN when appropriate. Keep APIs close to their database to reduce latency and egress. Measure whether Kubernetes-only capabilities—custom networking, controllers, scheduling, or a common multi-service platform—actually create product value.",[101,9171,723],{"id":722},[97,9173,9174,9175,9178],{},"Pick one cloud provider and sketch the complete path for ",[119,9176,9177],{},"https:\u002F\u002Fapp.example.com\u002Fapi",": DNS record, certificate, public load balancer, ingress\u002FGateway, Service, Pod, and managed database. Beside each component, name its owner, failure signal, monthly cost driver, and credential boundary. Then compare that design with the provider’s serverless container offering using five criteria: deployment effort, scaling, networking, observability, and estimated idle cost. Write a short recommendation with the conditions that would cause you to revisit it.",[101,9180,734],{"id":733},[736,9182,9183,9190],{},[739,9184,9185],{},[742,9186,9189],{"href":9187,"rel":9188},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Farchitecture\u002Fcloud-controller\u002F",[746],"Kubernetes: Cloud Controller Manager",[739,9191,9192],{},[742,9193,9196],{"href":9194,"rel":9195},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fservice\u002F#loadbalancer",[746],"Kubernetes: Service type LoadBalancer",[756,9198,9199],{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":117,"searchDepth":21,"depth":21,"links":9201},[9202,9203,9204,9205,9206,9207],{"id":8799,"depth":21,"text":8800},{"id":8829,"depth":21,"text":8830},{"id":8976,"depth":21,"text":8977},{"id":9138,"depth":21,"text":9139},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"Managed Kubernetes removes much of the control-plane toil, but it does not turn a cluster into a complete application platform. Senior engineers still…",{},"\u002Fkubernetes-for-web-applications\u002Fday-27",{"title":8787,"description":9208},"kubernetes-for-web-applications\u002Fday-27","JlEv21nxk5BU6a0swhTpRplEitIdhfmE3hW06-6Hy_Q",{"id":86,"title":87,"body":9215,"day":767,"description":768,"extension":769,"meta":9697,"navigation":147,"path":771,"seo":9698,"stem":773,"tag":774,"week":58,"weekName":59,"__hash__":775},{"type":89,"value":9216,"toc":9689},[9217,9219,9221,9223,9225,9227,9467,9471,9473,9475,9523,9525,9527,9529,9531,9533,9651,9653,9655,9657,9659,9665,9667,9669,9673,9675,9687],[92,9218,95],{"id":94},[97,9220,99],{},[101,9222,104],{"id":103},[97,9224,107],{},[97,9226,110],{},[112,9228,9229],{"className":114,"code":115,"language":116,"meta":117,"style":117},[119,9230,9231,9243,9247,9259,9289,9301,9323,9329,9333,9337,9357,9361,9365,9377,9381,9397,9407,9437,9463],{"__ignoreMap":117},[122,9232,9233,9235,9237,9239,9241],{"class":124,"line":12},[122,9234,128],{"class":127},[122,9236,132],{"class":131},[122,9238,135],{"class":127},[122,9240,139],{"class":138},[122,9242,142],{"class":131},[122,9244,9245],{"class":124,"line":21},[122,9246,148],{"emptyLinePlaceholder":147},[122,9248,9249,9251,9253,9255,9257],{"class":124,"line":26},[122,9250,153],{"class":127},[122,9252,156],{"class":131},[122,9254,159],{"class":127},[122,9256,163],{"class":162},[122,9258,142],{"class":131},[122,9260,9261,9263,9265,9267,9269,9271,9273,9275,9277,9279,9281,9283,9285,9287],{"class":124,"line":31},[122,9262,170],{"class":127},[122,9264,173],{"class":162},[122,9266,176],{"class":127},[122,9268,179],{"class":131},[122,9270,183],{"class":182},[122,9272,186],{"class":131},[122,9274,189],{"class":127},[122,9276,192],{"class":131},[122,9278,196],{"class":195},[122,9280,199],{"class":131},[122,9282,202],{"class":195},[122,9284,205],{"class":131},[122,9286,208],{"class":127},[122,9288,211],{"class":131},[122,9290,9291,9293,9295,9297,9299],{"class":124,"line":58},[122,9292,216],{"class":127},[122,9294,219],{"class":131},[122,9296,222],{"class":127},[122,9298,225],{"class":138},[122,9300,228],{"class":131},[122,9302,9303,9305,9307,9309,9311,9313,9315,9317,9319,9321],{"class":124,"line":231},[122,9304,234],{"class":131},[122,9306,237],{"class":182},[122,9308,240],{"class":131},[122,9310,243],{"class":127},[122,9312,246],{"class":162},[122,9314,249],{"class":127},[122,9316,252],{"class":162},[122,9318,255],{"class":131},[122,9320,258],{"class":182},[122,9322,261],{"class":131},[122,9324,9325,9327],{"class":124,"line":264},[122,9326,267],{"class":127},[122,9328,142],{"class":131},[122,9330,9331],{"class":124,"line":272},[122,9332,275],{"class":131},[122,9334,9335],{"class":124,"line":278},[122,9336,282],{"class":281},[122,9338,9339,9341,9343,9345,9347,9349,9351,9353,9355],{"class":124,"line":285},[122,9340,288],{"class":131},[122,9342,237],{"class":182},[122,9344,186],{"class":131},[122,9346,295],{"class":162},[122,9348,255],{"class":131},[122,9350,258],{"class":182},[122,9352,186],{"class":131},[122,9354,304],{"class":138},[122,9356,307],{"class":131},[122,9358,9359],{"class":124,"line":310},[122,9360,313],{"class":131},[122,9362,9363],{"class":124,"line":316},[122,9364,148],{"emptyLinePlaceholder":147},[122,9366,9367,9369,9371,9373,9375],{"class":124,"line":321},[122,9368,324],{"class":131},[122,9370,327],{"class":182},[122,9372,186],{"class":131},[122,9374,332],{"class":162},[122,9376,307],{"class":131},[122,9378,9379],{"class":124,"line":337},[122,9380,148],{"emptyLinePlaceholder":147},[122,9382,9383,9385,9387,9389,9391,9393,9395],{"class":124,"line":342},[122,9384,345],{"class":131},[122,9386,348],{"class":182},[122,9388,186],{"class":131},[122,9390,353],{"class":138},[122,9392,356],{"class":131},[122,9394,208],{"class":127},[122,9396,211],{"class":131},[122,9398,9399,9401,9403,9405],{"class":124,"line":363},[122,9400,366],{"class":131},[122,9402,159],{"class":127},[122,9404,371],{"class":162},[122,9406,142],{"class":131},[122,9408,9409,9411,9413,9415,9417,9419,9421,9423,9425,9427,9429,9431,9433,9435],{"class":124,"line":376},[122,9410,379],{"class":131},[122,9412,382],{"class":182},[122,9414,385],{"class":131},[122,9416,388],{"class":195},[122,9418,205],{"class":131},[122,9420,208],{"class":127},[122,9422,395],{"class":131},[122,9424,398],{"class":182},[122,9426,401],{"class":131},[122,9428,243],{"class":127},[122,9430,406],{"class":162},[122,9432,249],{"class":127},[122,9434,411],{"class":162},[122,9436,414],{"class":131},[122,9438,9439,9441,9443,9445,9447,9449,9451,9453,9455,9457,9459,9461],{"class":124,"line":417},[122,9440,420],{"class":182},[122,9442,423],{"class":131},[122,9444,208],{"class":127},[122,9446,395],{"class":131},[122,9448,398],{"class":182},[122,9450,186],{"class":131},[122,9452,434],{"class":162},[122,9454,437],{"class":131},[122,9456,440],{"class":162},[122,9458,255],{"class":131},[122,9460,445],{"class":182},[122,9462,261],{"class":131},[122,9464,9465],{"class":124,"line":450},[122,9466,313],{"class":131},[97,9468,455,9469,459],{},[119,9470,458],{},[101,9472,463],{"id":462},[97,9474,466],{},[112,9476,9477],{"className":469,"code":470,"language":471,"meta":117,"style":117},[119,9478,9479,9487,9495,9501,9509,9515],{"__ignoreMap":117},[122,9480,9481,9483,9485],{"class":124,"line":12},[122,9482,479],{"class":478},[122,9484,482],{"class":131},[122,9486,485],{"class":138},[122,9488,9489,9491,9493],{"class":124,"line":21},[122,9490,490],{"class":478},[122,9492,482],{"class":131},[122,9494,495],{"class":138},[122,9496,9497,9499],{"class":124,"line":26},[122,9498,500],{"class":478},[122,9500,503],{"class":131},[122,9502,9503,9505,9507],{"class":124,"line":31},[122,9504,508],{"class":478},[122,9506,482],{"class":131},[122,9508,513],{"class":138},[122,9510,9511,9513],{"class":124,"line":58},[122,9512,518],{"class":478},[122,9514,503],{"class":131},[122,9516,9517,9519,9521],{"class":124,"line":231},[122,9518,525],{"class":478},[122,9520,482],{"class":131},[122,9522,530],{"class":138},[97,9524,533],{},[97,9526,536],{},[101,9528,540],{"id":539},[97,9530,543],{},[97,9532,546],{},[112,9534,9535],{"className":114,"code":549,"language":116,"meta":117,"style":117},[119,9536,9537,9541,9551,9579,9583,9587,9597,9615,9631,9643,9647],{"__ignoreMap":117},[122,9538,9539],{"class":124,"line":12},[122,9540,556],{"class":281},[122,9542,9543,9545,9547,9549],{"class":124,"line":21},[122,9544,561],{"class":127},[122,9546,564],{"class":127},[122,9548,567],{"class":182},[122,9550,211],{"class":131},[122,9552,9553,9555,9557,9559,9561,9563,9565,9567,9569,9571,9573,9575,9577],{"class":124,"line":26},[122,9554,574],{"class":182},[122,9556,186],{"class":131},[122,9558,579],{"class":195},[122,9560,582],{"class":127},[122,9562,585],{"class":162},[122,9564,588],{"class":131},[122,9566,582],{"class":127},[122,9568,593],{"class":182},[122,9570,596],{"class":131},[122,9572,599],{"class":195},[122,9574,582],{"class":127},[122,9576,585],{"class":162},[122,9578,606],{"class":131},[122,9580,9581],{"class":124,"line":31},[122,9582,611],{"class":131},[122,9584,9585],{"class":124,"line":58},[122,9586,148],{"emptyLinePlaceholder":147},[122,9588,9589,9591,9593,9595],{"class":124,"line":231},[122,9590,561],{"class":127},[122,9592,622],{"class":127},[122,9594,625],{"class":182},[122,9596,211],{"class":131},[122,9598,9599,9601,9603,9605,9607,9609,9611,9613],{"class":124,"line":264},[122,9600,632],{"class":127},[122,9602,186],{"class":131},[122,9604,637],{"class":127},[122,9606,640],{"class":127},[122,9608,643],{"class":195},[122,9610,582],{"class":127},[122,9612,567],{"class":182},[122,9614,650],{"class":131},[122,9616,9617,9619,9621,9623,9625,9627,9629],{"class":124,"line":272},[122,9618,655],{"class":127},[122,9620,658],{"class":182},[122,9622,186],{"class":131},[122,9624,579],{"class":195},[122,9626,582],{"class":127},[122,9628,585],{"class":162},[122,9630,228],{"class":131},[122,9632,9633,9635,9637,9639,9641],{"class":124,"line":278},[122,9634,267],{"class":127},[122,9636,675],{"class":162},[122,9638,678],{"class":131},[122,9640,681],{"class":182},[122,9642,684],{"class":131},[122,9644,9645],{"class":124,"line":285},[122,9646,275],{"class":131},[122,9648,9649],{"class":124,"line":310},[122,9650,611],{"class":131},[97,9652,695],{},[101,9654,699],{"id":698},[97,9656,702],{},[97,9658,705],{},[97,9660,708,9661,712,9663,716],{},[119,9662,711],{},[119,9664,715],{},[97,9666,719],{},[101,9668,723],{"id":722},[97,9670,726,9671,730],{},[119,9672,729],{},[101,9674,734],{"id":733},[736,9676,9677,9682],{},[739,9678,9679],{},[742,9680,747],{"href":744,"rel":9681},[746],[739,9683,9684],{},[742,9685,754],{"href":752,"rel":9686},[746],[756,9688,758],{},{"title":117,"searchDepth":21,"depth":21,"links":9690},[9691,9692,9693,9694,9695,9696],{"id":103,"depth":21,"text":104},{"id":462,"depth":21,"text":463},{"id":539,"depth":21,"text":540},{"id":698,"depth":21,"text":699},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},{},{"title":87,"description":768},{"id":9700,"title":9701,"body":9702,"day":8007,"description":10315,"extension":769,"meta":10316,"navigation":147,"path":10317,"seo":10318,"stem":10319,"tag":774,"week":58,"weekName":59,"__hash__":10320},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-29.md","Capstone: deploy a production-shaped web application",{"type":89,"value":9703,"toc":10307},[9704,9708,9711,9715,9718,9721,9727,9730,9743,9747,9750,10125,10137,10140,10144,10147,10164,10167,10171,10174,10177,10265,10272,10275,10277,10291,10293,10305],[92,9705,9707],{"id":9706},"day-29-capstone-deploy-a-production-shaped-web-application","Day 29 — Capstone: deploy a production-shaped web application",[97,9709,9710],{},"The capstone is not about producing the largest manifest set. It is about connecting build, runtime, traffic, security, observability, and recovery decisions into one deployable system. A production-shaped design should be understandable by another engineer and survive routine failures without manual repair.",[101,9712,9714],{"id":9713},"_1-define-a-topology-with-explicit-boundaries","1. Define a topology with explicit boundaries",[97,9716,9717],{},"Use four workloads: a Nuxt frontend, a Node API, a queue-consuming worker, and an external managed database. The frontend and API receive HTTP traffic; the worker has no Service because it consumes jobs. Keep the database outside the cluster unless you deliberately accept its operational burden.",[97,9719,9720],{},"Choose a simple request path:",[112,9722,9725],{"className":9723,"code":9724,"language":6953,"meta":117},[6951],"Internet -> Gateway\u002FIngress -> frontend Service -> Nuxt Pods\n                         \\--> api Service      -> API Pods -> managed PostgreSQL\n                                                        \\-> managed queue -> worker Pods\n",[119,9726,9724],{"__ignoreMap":117},[97,9728,9729],{},"Give each workload its own Deployment and ServiceAccount. Use ClusterIP Services for internal routing, and expose only the required frontend and API routes. Put static assets behind a CDN if that matches the application. Store configuration in per-workload ConfigMaps and source credentials from an external secret manager.",[97,9731,9732,9733,199,9736,1044,9739,9742],{},"Pin every image by digest. Add labels such as ",[119,9734,9735],{},"app.kubernetes.io\u002Fname",[119,9737,9738],{},"component",[119,9740,9741],{},"version"," consistently so dashboards, policies, and operational queries agree.",[101,9744,9746],{"id":9745},"_2-encode-deployability-in-the-workloads","2. Encode deployability in the workloads",[97,9748,9749],{},"A useful API Deployment includes replicas, probes, resources, rollout policy, a security context, and graceful termination:",[112,9751,9753],{"className":469,"code":9752,"language":471,"meta":117,"style":117},"apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: api\nspec:\n  replicas: 3\n  strategy:\n    rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }\n  selector:\n    matchLabels: { app: api }\n  template:\n    metadata:\n      labels: { app: api }\n    spec:\n      serviceAccountName: api\n      terminationGracePeriodSeconds: 30\n      containers:\n        - name: api\n          image: ghcr.io\u002Facme\u002Fapi@sha256:0123456789abcdef\n          ports: [{ name: http, containerPort: 3000 }]\n          readinessProbe:\n            httpGet: { path: \u002Fready, port: http }\n            periodSeconds: 5\n          livenessProbe:\n            httpGet: { path: \u002Flive, port: http }\n            periodSeconds: 15\n          resources:\n            requests: { cpu: 200m, memory: 256Mi }\n            limits: { memory: 512Mi }\n          securityContext:\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n            runAsNonRoot: true\n            capabilities: { drop: [\"ALL\"] }\n",[119,9754,9755,9763,9771,9777,9785,9791,9799,9806,9829,9835,9849,9855,9861,9875,9881,9889,9899,9905,9915,9924,9949,9956,9980,9989,9996,10019,10028,10034,10057,10071,10078,10087,10097,10107],{"__ignoreMap":117},[122,9756,9757,9759,9761],{"class":124,"line":12},[122,9758,479],{"class":478},[122,9760,482],{"class":131},[122,9762,2686],{"class":138},[122,9764,9765,9767,9769],{"class":124,"line":21},[122,9766,490],{"class":478},[122,9768,482],{"class":131},[122,9770,2695],{"class":138},[122,9772,9773,9775],{"class":124,"line":26},[122,9774,500],{"class":478},[122,9776,503],{"class":131},[122,9778,9779,9781,9783],{"class":124,"line":31},[122,9780,508],{"class":478},[122,9782,482],{"class":131},[122,9784,2806],{"class":138},[122,9786,9787,9789],{"class":124,"line":58},[122,9788,2569],{"class":478},[122,9790,503],{"class":131},[122,9792,9793,9795,9797],{"class":124,"line":231},[122,9794,2721],{"class":478},[122,9796,482],{"class":131},[122,9798,4986],{"class":162},[122,9800,9801,9804],{"class":124,"line":264},[122,9802,9803],{"class":478},"  strategy",[122,9805,503],{"class":131},[122,9807,9808,9811,9813,9815,9817,9819,9821,9823,9825,9827],{"class":124,"line":272},[122,9809,9810],{"class":478},"    rollingUpdate",[122,9812,5548],{"class":131},[122,9814,4676],{"class":478},[122,9816,482],{"class":131},[122,9818,977],{"class":162},[122,9820,199],{"class":131},[122,9822,4214],{"class":478},[122,9824,482],{"class":131},[122,9826,434],{"class":162},[122,9828,5559],{"class":131},[122,9830,9831,9833],{"class":124,"line":278},[122,9832,2731],{"class":478},[122,9834,503],{"class":131},[122,9836,9837,9839,9841,9843,9845,9847],{"class":124,"line":285},[122,9838,2738],{"class":478},[122,9840,5548],{"class":131},[122,9842,5551],{"class":478},[122,9844,482],{"class":131},[122,9846,3274],{"class":138},[122,9848,5559],{"class":131},[122,9850,9851,9853],{"class":124,"line":310},[122,9852,2754],{"class":478},[122,9854,503],{"class":131},[122,9856,9857,9859],{"class":124,"line":316},[122,9858,2761],{"class":478},[122,9860,503],{"class":131},[122,9862,9863,9865,9867,9869,9871,9873],{"class":124,"line":321},[122,9864,2768],{"class":478},[122,9866,5548],{"class":131},[122,9868,5551],{"class":478},[122,9870,482],{"class":131},[122,9872,3274],{"class":138},[122,9874,5559],{"class":131},[122,9876,9877,9879],{"class":124,"line":337},[122,9878,2784],{"class":478},[122,9880,503],{"class":131},[122,9882,9883,9885,9887],{"class":124,"line":342},[122,9884,9102],{"class":478},[122,9886,482],{"class":131},[122,9888,2806],{"class":138},[122,9890,9891,9894,9896],{"class":124,"line":363},[122,9892,9893],{"class":478},"      terminationGracePeriodSeconds",[122,9895,482],{"class":131},[122,9897,9898],{"class":162},"30\n",[122,9900,9901,9903],{"class":124,"line":376},[122,9902,2791],{"class":478},[122,9904,503],{"class":131},[122,9906,9907,9909,9911,9913],{"class":124,"line":417},[122,9908,2798],{"class":131},[122,9910,2801],{"class":478},[122,9912,482],{"class":131},[122,9914,2806],{"class":138},[122,9916,9917,9919,9921],{"class":124,"line":450},[122,9918,2811],{"class":478},[122,9920,482],{"class":131},[122,9922,9923],{"class":138},"ghcr.io\u002Facme\u002Fapi@sha256:0123456789abcdef\n",[122,9925,9926,9928,9931,9933,9935,9938,9940,9942,9944,9946],{"class":124,"line":2172},[122,9927,2821],{"class":478},[122,9929,9930],{"class":131},": [{ ",[122,9932,2801],{"class":478},[122,9934,482],{"class":131},[122,9936,9937],{"class":138},"http",[122,9939,199],{"class":131},[122,9941,5629],{"class":478},[122,9943,482],{"class":131},[122,9945,332],{"class":162},[122,9947,9948],{"class":131}," }]\n",[122,9950,9951,9954],{"class":124,"line":2183},[122,9952,9953],{"class":478},"          readinessProbe",[122,9955,503],{"class":131},[122,9957,9958,9961,9963,9965,9967,9970,9972,9974,9976,9978],{"class":124,"line":2194},[122,9959,9960],{"class":478},"            httpGet",[122,9962,5548],{"class":131},[122,9964,3461],{"class":478},[122,9966,482],{"class":131},[122,9968,9969],{"class":138},"\u002Fready",[122,9971,199],{"class":131},[122,9973,3075],{"class":478},[122,9975,482],{"class":131},[122,9977,9937],{"class":138},[122,9979,5559],{"class":131},[122,9981,9982,9985,9987],{"class":124,"line":2202},[122,9983,9984],{"class":478},"            periodSeconds",[122,9986,482],{"class":131},[122,9988,4765],{"class":162},[122,9990,9991,9994],{"class":124,"line":7302},[122,9992,9993],{"class":478},"          livenessProbe",[122,9995,503],{"class":131},[122,9997,9998,10000,10002,10004,10006,10009,10011,10013,10015,10017],{"class":124,"line":7946},[122,9999,9960],{"class":478},[122,10001,5548],{"class":131},[122,10003,3461],{"class":478},[122,10005,482],{"class":131},[122,10007,10008],{"class":138},"\u002Flive",[122,10010,199],{"class":131},[122,10012,3075],{"class":478},[122,10014,482],{"class":131},[122,10016,9937],{"class":138},[122,10018,5559],{"class":131},[122,10020,10021,10023,10025],{"class":124,"line":7969},[122,10022,9984],{"class":478},[122,10024,482],{"class":131},[122,10026,10027],{"class":162},"15\n",[122,10029,10030,10032],{"class":124,"line":7990},[122,10031,5842],{"class":478},[122,10033,503],{"class":131},[122,10035,10036,10038,10040,10042,10044,10047,10049,10051,10053,10055],{"class":124,"line":767},[122,10037,6115],{"class":478},[122,10039,5548],{"class":131},[122,10041,5754],{"class":478},[122,10043,482],{"class":131},[122,10045,10046],{"class":138},"200m",[122,10048,199],{"class":131},[122,10050,5764],{"class":478},[122,10052,482],{"class":131},[122,10054,6133],{"class":138},[122,10056,5559],{"class":131},[122,10058,10059,10061,10063,10065,10067,10069],{"class":124,"line":8007},[122,10060,6140],{"class":478},[122,10062,5548],{"class":131},[122,10064,5764],{"class":478},[122,10066,482],{"class":131},[122,10068,6149],{"class":138},[122,10070,5559],{"class":131},[122,10072,10073,10076],{"class":124,"line":10},[122,10074,10075],{"class":478},"          securityContext",[122,10077,503],{"class":131},[122,10079,10080,10083,10085],{"class":124,"line":8029},[122,10081,10082],{"class":478},"            allowPrivilegeEscalation",[122,10084,482],{"class":131},[122,10086,6585],{"class":162},[122,10088,10090,10093,10095],{"class":124,"line":10089},32,[122,10091,10092],{"class":478},"            readOnlyRootFilesystem",[122,10094,482],{"class":131},[122,10096,6430],{"class":162},[122,10098,10100,10103,10105],{"class":124,"line":10099},33,[122,10101,10102],{"class":478},"            runAsNonRoot",[122,10104,482],{"class":131},[122,10106,6430],{"class":162},[122,10108,10110,10113,10115,10118,10120,10122],{"class":124,"line":10109},34,[122,10111,10112],{"class":478},"            capabilities",[122,10114,5548],{"class":131},[122,10116,10117],{"class":478},"drop",[122,10119,2041],{"class":131},[122,10121,7444],{"class":138},[122,10123,10124],{"class":131},"] }\n",[97,10126,10127,10128,10130,10131,10133,10134,10136],{},"Mount a writable ",[119,10129,3883],{}," only for required temporary paths. Make ",[119,10132,10008],{}," check process health and ",[119,10135,9969],{}," indicate traffic readiness; avoid turning a transient database issue into a liveness restart storm. Give the worker a shutdown handler that stops polling, finishes or safely returns its current job, and exits within the grace period.",[97,10138,10139],{},"Add a PodDisruptionBudget and topology spread where availability warrants them. Configure an HPA only after metrics exist and requests reflect observed usage.",[101,10141,10143],{"id":10142},"_3-make-observability-part-of-the-release","3. Make observability part of the release",[97,10145,10146],{},"Emit structured JSON logs to stdout with timestamp, severity, service, version, trace ID, and request ID. Instrument inbound HTTP, database calls, and queue processing with OpenTelemetry. A minimal dashboard should show:",[736,10148,10149,10152,10155,10158,10161],{},[739,10150,10151],{},"request rate, error rate, and latency percentiles by route;",[739,10153,10154],{},"Pod restarts, readiness, CPU, memory, and throttling;",[739,10156,10157],{},"database pool saturation and query latency;",[739,10159,10160],{},"queue depth, oldest-message age, and worker failures;",[739,10162,10163],{},"current deployed image digest and rollout status.",[97,10165,10166],{},"Define at least one user-oriented objective, such as “99.9% of API requests succeed over 30 days, excluding valid 4xx responses.” Alert on sustained symptoms that threaten that objective, not every noisy infrastructure fluctuation.",[101,10168,10170],{"id":10169},"_4-build-release-and-rollback-as-executable-procedures","4. Build release and rollback as executable procedures",[97,10172,10173],{},"A delivery pipeline should test code, build once, scan the image, publish it, update the deployment repository with the digest, and let GitOps reconcile. Before promotion, validate manifests and run integration tests. Database changes must be backward compatible with both old and new application versions.",[97,10175,10176],{},"Your runbook should include commands and decision points:",[112,10178,10180],{"className":2474,"code":10179,"language":2476,"meta":117,"style":117},"kubectl -n production rollout status deployment\u002Fapi --timeout=5m\nkubectl -n production get pods -l app=api\nkubectl -n production logs deployment\u002Fapi --since=10m\nkubectl -n production rollout history deployment\u002Fapi\nkubectl -n production rollout undo deployment\u002Fapi\n",[119,10181,10182,10200,10218,10234,10250],{"__ignoreMap":117},[122,10183,10184,10186,10188,10191,10193,10195,10197],{"class":124,"line":12},[122,10185,2428],{"class":182},[122,10187,5185],{"class":162},[122,10189,10190],{"class":138}," production",[122,10192,5206],{"class":138},[122,10194,5209],{"class":138},[122,10196,5212],{"class":138},[122,10198,10199],{"class":162}," --timeout=5m\n",[122,10201,10202,10204,10206,10208,10210,10213,10215],{"class":124,"line":21},[122,10203,2428],{"class":182},[122,10205,5185],{"class":162},[122,10207,10190],{"class":138},[122,10209,2485],{"class":138},[122,10211,10212],{"class":138}," pods",[122,10214,5191],{"class":162},[122,10216,10217],{"class":138}," app=api\n",[122,10219,10220,10222,10224,10226,10229,10231],{"class":124,"line":26},[122,10221,2428],{"class":182},[122,10223,5185],{"class":162},[122,10225,10190],{"class":138},[122,10227,10228],{"class":138}," logs",[122,10230,5212],{"class":138},[122,10232,10233],{"class":162}," --since=10m\n",[122,10235,10236,10238,10240,10242,10244,10247],{"class":124,"line":31},[122,10237,2428],{"class":182},[122,10239,5185],{"class":162},[122,10241,10190],{"class":138},[122,10243,5206],{"class":138},[122,10245,10246],{"class":138}," history",[122,10248,10249],{"class":138}," deployment\u002Fapi\n",[122,10251,10252,10254,10256,10258,10260,10263],{"class":124,"line":58},[122,10253,2428],{"class":182},[122,10255,5185],{"class":162},[122,10257,10190],{"class":138},[122,10259,5206],{"class":138},[122,10261,10262],{"class":138}," undo",[122,10264,10249],{"class":138},[97,10266,10267,10268,10271],{},"With GitOps, rollback normally means reverting the digest commit rather than leaving Git and the cluster divergent. State what happens if the release includes a migration that cannot be reversed. Prefer forward fixes and expand\u002Fcontract migrations; never imply ",[119,10269,10270],{},"rollout undo"," restores data.",[97,10273,10274],{},"Include stop conditions: elevated error rate, failed readiness, latency regression, worker backlog growth, or migration failure. Name who decides to continue, pause, or roll back, and verify service health after the action.",[101,10276,723],{"id":722},[97,10278,10279,10280,10282,10283,10286,10287,10290],{},"Create a repository directory with ",[119,10281,6179],{}," manifests for frontend, API, and worker plus a ",[119,10284,10285],{},"production\u002F"," Kustomize overlay. Add Services, Gateway or Ingress, ConfigMaps, probes, resources, security contexts, and a default-deny NetworkPolicy with explicit dependency access. Render it using ",[119,10288,10289],{},"kubectl kustomize production\u002F",", apply it to a local cluster with substitute local dependencies, and run a rollout. Intentionally deploy a broken readiness endpoint, observe the stalled rollout, execute your rollback runbook, and capture the evidence you used to decide.",[101,10292,734],{"id":733},[736,10294,10295,10300],{},[739,10296,10297],{},[742,10298,2937],{"href":2935,"rel":10299},[746],[739,10301,10302],{},[742,10303,5444],{"href":5442,"rel":10304},[746],[756,10306,7575],{},{"title":117,"searchDepth":21,"depth":21,"links":10308},[10309,10310,10311,10312,10313,10314],{"id":9713,"depth":21,"text":9714},{"id":9745,"depth":21,"text":9746},{"id":10142,"depth":21,"text":10143},{"id":10169,"depth":21,"text":10170},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"The capstone is not about producing the largest manifest set. It is about connecting build, runtime, traffic, security, observability, and recovery…",{},"\u002Fkubernetes-for-web-applications\u002Fday-29",{"title":9701,"description":10315},"kubernetes-for-web-applications\u002Fday-29","P920YXt9ZYih1pCaOKgaC08UAQUCTQUrxkMe-IPgpK4",{"id":10322,"title":10323,"body":10324,"day":10,"description":10757,"extension":769,"meta":10758,"navigation":147,"path":10759,"seo":10760,"stem":10761,"tag":774,"week":58,"weekName":59,"__hash__":10762},"kubernetes_for_web_applications\u002Fkubernetes-for-web-applications\u002Fday-30.md","Your Kubernetes application platform blueprint",{"type":89,"value":10325,"toc":10749},[10326,10330,10333,10337,10340,10343,10346,10539,10542,10546,10549,10552,10578,10581,10585,10588,10591,10594,10597,10692,10695,10699,10702,10708,10714,10720,10723,10725,10728,10730,10746],[92,10327,10329],{"id":10328},"day-30-your-kubernetes-application-platform-blueprint","Day 30 — Your Kubernetes application platform blueprint",[97,10331,10332],{},"The final deliverable is a repeatable decision system, not a fashionable stack diagram. A senior engineer’s platform blueprint states when Kubernetes earns its cost, defines safe defaults, and leaves room to evolve from evidence. It should help a team ship a new Node service predictably without hiding operational responsibility.",[101,10334,10336],{"id":10335},"_1-put-a-decision-gate-before-kubernetes","1. Put a decision gate before Kubernetes",[97,10338,10339],{},"Start with workload and organizational constraints. Kubernetes is a reasonable candidate when several of these are true: many independently operated workloads, a need for standardized deployment controls, specialized scheduling or controllers, hybrid portability, mature platform ownership, or scaling patterns that simpler platforms cannot serve.",[97,10341,10342],{},"Prefer a managed application platform or serverless containers when the system is a handful of stateless services, the team is small, time-to-market dominates, and provider constraints are acceptable. Prefer plain VMs only when the operational model or legacy software genuinely fits them; do not choose them merely to avoid learning deployment automation.",[97,10344,10345],{},"Write a lightweight scorecard:",[112,10347,10349],{"className":114,"code":10348,"language":116,"meta":117,"style":117},"type Candidate = {\n  workloadCount: number;\n  needsCustomControllers: boolean;\n  regulatedIsolation: boolean;\n  platformOwners: number;\n  simplerPlatformBlockers: string[];\n};\n\nexport function assess(c: Candidate): \"simpler-platform\" | \"investigate-kubernetes\" {\n  const strongNeed = c.needsCustomControllers || c.simplerPlatformBlockers.length >= 2;\n  const canOwnIt = c.platformOwners >= 2;\n  return strongNeed && canOwnIt ? \"investigate-kubernetes\" : \"simpler-platform\";\n}\n",[119,10350,10351,10362,10374,10386,10397,10408,10420,10425,10429,10463,10492,10511,10535],{"__ignoreMap":117},[122,10352,10353,10355,10358,10360],{"class":124,"line":12},[122,10354,5010],{"class":127},[122,10356,10357],{"class":182}," Candidate",[122,10359,176],{"class":127},[122,10361,211],{"class":131},[122,10363,10364,10367,10369,10372],{"class":124,"line":21},[122,10365,10366],{"class":195},"  workloadCount",[122,10368,582],{"class":127},[122,10370,10371],{"class":162}," number",[122,10373,142],{"class":131},[122,10375,10376,10379,10381,10384],{"class":124,"line":26},[122,10377,10378],{"class":195},"  needsCustomControllers",[122,10380,582],{"class":127},[122,10382,10383],{"class":162}," boolean",[122,10385,142],{"class":131},[122,10387,10388,10391,10393,10395],{"class":124,"line":31},[122,10389,10390],{"class":195},"  regulatedIsolation",[122,10392,582],{"class":127},[122,10394,10383],{"class":162},[122,10396,142],{"class":131},[122,10398,10399,10402,10404,10406],{"class":124,"line":58},[122,10400,10401],{"class":195},"  platformOwners",[122,10403,582],{"class":127},[122,10405,10371],{"class":162},[122,10407,142],{"class":131},[122,10409,10410,10413,10415,10417],{"class":124,"line":231},[122,10411,10412],{"class":195},"  simplerPlatformBlockers",[122,10414,582],{"class":127},[122,10416,585],{"class":162},[122,10418,10419],{"class":131},"[];\n",[122,10421,10422],{"class":124,"line":264},[122,10423,10424],{"class":131},"};\n",[122,10426,10427],{"class":124,"line":272},[122,10428,148],{"emptyLinePlaceholder":147},[122,10430,10431,10433,10436,10439,10441,10444,10446,10448,10450,10452,10455,10458,10461],{"class":124,"line":278},[122,10432,561],{"class":127},[122,10434,10435],{"class":127}," function",[122,10437,10438],{"class":182}," assess",[122,10440,186],{"class":131},[122,10442,10443],{"class":195},"c",[122,10445,582],{"class":127},[122,10447,10357],{"class":182},[122,10449,588],{"class":131},[122,10451,582],{"class":127},[122,10453,10454],{"class":138}," \"simpler-platform\"",[122,10456,10457],{"class":127}," |",[122,10459,10460],{"class":138}," \"investigate-kubernetes\"",[122,10462,211],{"class":131},[122,10464,10465,10468,10471,10473,10476,10478,10481,10484,10487,10490],{"class":124,"line":285},[122,10466,10467],{"class":127},"  const",[122,10469,10470],{"class":162}," strongNeed",[122,10472,176],{"class":127},[122,10474,10475],{"class":131}," c.needsCustomControllers ",[122,10477,1857],{"class":127},[122,10479,10480],{"class":131}," c.simplerPlatformBlockers.",[122,10482,10483],{"class":162},"length",[122,10485,10486],{"class":127}," >=",[122,10488,10489],{"class":162}," 2",[122,10491,142],{"class":131},[122,10493,10494,10496,10499,10501,10504,10507,10509],{"class":124,"line":310},[122,10495,10467],{"class":127},[122,10497,10498],{"class":162}," canOwnIt",[122,10500,176],{"class":127},[122,10502,10503],{"class":131}," c.platformOwners ",[122,10505,10506],{"class":127},">=",[122,10508,10489],{"class":162},[122,10510,142],{"class":131},[122,10512,10513,10516,10519,10522,10525,10527,10529,10531,10533],{"class":124,"line":316},[122,10514,10515],{"class":127},"  return",[122,10517,10518],{"class":131}," strongNeed ",[122,10520,10521],{"class":127},"&&",[122,10523,10524],{"class":131}," canOwnIt ",[122,10526,243],{"class":127},[122,10528,10460],{"class":138},[122,10530,249],{"class":127},[122,10532,10454],{"class":138},[122,10534,142],{"class":131},[122,10536,10537],{"class":124,"line":321},[122,10538,611],{"class":131},[97,10540,10541],{},"This is not an algorithm to outsource judgment; it makes assumptions reviewable. Revisit the decision when service count, compliance, scale, or team ownership changes.",[101,10543,10545],{"id":10544},"_2-define-a-paved-road-with-secure-defaults","2. Define a paved road with secure defaults",[97,10547,10548],{},"Your preferred deployment unit should include a Deployment, Service, ServiceAccount, probes, resource requests, memory limits, topology behavior, and a restrictive security context. Package it with a small Helm chart or Kustomize base that teams can understand rather than a universal abstraction with dozens of hidden switches.",[97,10550,10551],{},"The platform baseline should specify:",[736,10553,10554,10557,10560,10563,10566,10569,10572,10575],{},[739,10555,10556],{},"separate production and non-production failure boundaries;",[739,10558,10559],{},"digest-pinned images built once and promoted;",[739,10561,10562],{},"namespace-scoped RBAC and cloud workload identity;",[739,10564,10565],{},"restricted Pod Security admission and default-deny networking;",[739,10567,10568],{},"external secret management and documented rotation;",[739,10570,10571],{},"managed databases by default;",[739,10573,10574],{},"controlled ingress\u002FGateway, DNS, and certificate automation;",[739,10576,10577],{},"quotas, policy validation, and expiry for preview environments.",[97,10579,10580],{},"Allow exceptions through a documented review with an owner and expiration date. A paved road succeeds because it is easier than bypassing it, not because every choice is forbidden.",[101,10582,10584],{"id":10583},"_3-connect-gitops-observability-and-incident-operations","3. Connect GitOps, observability, and incident operations",[97,10586,10587],{},"Use application repositories for code, tests, and image builds. Use a deployment repository for environment-specific desired state and promotion pull requests. Argo CD or Flux reconciles that state, exposes drift, and provides an audit trail. Restrict direct production mutation to documented emergency procedures, followed by reconciliation back into Git.",[97,10589,10590],{},"Standardize telemetry for Node and Nuxt services with structured logs and OpenTelemetry. Provide dashboards for request rate, errors, latency, saturation, restarts, rollout state, and relevant queues or databases. Require each service to declare an owner, service-level objective, dependency map, alerts, and runbook.",[97,10592,10593],{},"A platform is incomplete if it only handles successful deployment. Define response paths for bad releases, credential compromise, node or zone loss, dependency outage, certificate failure, and capacity exhaustion. Test rollback and database restore rather than treating documentation as proof.",[97,10595,10596],{},"A concise service contract might live beside the manifests:",[112,10598,10600],{"className":469,"code":10599,"language":471,"meta":117,"style":117},"service:\n  name: checkout-api\n  owner: commerce\n  tier: critical\n  slo:\n    availability: 99.9\n    latencyP95Ms: 400\n  runbook: https:\u002F\u002Fdocs.example.com\u002Frunbooks\u002Fcheckout-api\n  dependencies: [postgres-orders, payments-api]\n",[119,10601,10602,10609,10618,10628,10638,10645,10655,10665,10675],{"__ignoreMap":117},[122,10603,10604,10607],{"class":124,"line":12},[122,10605,10606],{"class":478},"service",[122,10608,503],{"class":131},[122,10610,10611,10613,10615],{"class":124,"line":21},[122,10612,508],{"class":478},[122,10614,482],{"class":131},[122,10616,10617],{"class":138},"checkout-api\n",[122,10619,10620,10623,10625],{"class":124,"line":26},[122,10621,10622],{"class":478},"  owner",[122,10624,482],{"class":131},[122,10626,10627],{"class":138},"commerce\n",[122,10629,10630,10633,10635],{"class":124,"line":31},[122,10631,10632],{"class":478},"  tier",[122,10634,482],{"class":131},[122,10636,10637],{"class":138},"critical\n",[122,10639,10640,10643],{"class":124,"line":58},[122,10641,10642],{"class":478},"  slo",[122,10644,503],{"class":131},[122,10646,10647,10650,10652],{"class":124,"line":231},[122,10648,10649],{"class":478},"    availability",[122,10651,482],{"class":131},[122,10653,10654],{"class":162},"99.9\n",[122,10656,10657,10660,10662],{"class":124,"line":264},[122,10658,10659],{"class":478},"    latencyP95Ms",[122,10661,482],{"class":131},[122,10663,10664],{"class":162},"400\n",[122,10666,10667,10670,10672],{"class":124,"line":272},[122,10668,10669],{"class":478},"  runbook",[122,10671,482],{"class":131},[122,10673,10674],{"class":138},"https:\u002F\u002Fdocs.example.com\u002Frunbooks\u002Fcheckout-api\n",[122,10676,10677,10680,10682,10685,10687,10690],{"class":124,"line":278},[122,10678,10679],{"class":478},"  dependencies",[122,10681,2041],{"class":131},[122,10683,10684],{"class":138},"postgres-orders",[122,10686,199],{"class":131},[122,10688,10689],{"class":138},"payments-api",[122,10691,2047],{"class":131},[97,10693,10694],{},"This metadata can drive catalog pages, alerts, and ownership checks without inventing a heavy internal developer platform on day one.",[101,10696,10698],{"id":10697},"_4-build-a-90-day-adoption-loop","4. Build a 90-day adoption loop",[97,10700,10701],{},"Avoid a big-bang platform program. Use three evidence-producing phases.",[97,10703,10704,10707],{},[9148,10705,10706],{},"Days 1–30: establish the reference."," Deploy one low-risk but real service. Document image standards, manifests, identity, networking, telemetry, and rollback. Record baseline lead time, deployment failure rate, recovery time, idle cost, and developer effort.",[97,10709,10710,10713],{},[9148,10711,10712],{},"Days 31–60: prove operations."," Add production-like load, autoscaling evidence, disruption tests, an upgrade rehearsal, a failed rollout drill, and a secret rotation. Onboard a second service to expose assumptions embedded in the first.",[97,10715,10716,10719],{},[9148,10717,10718],{},"Days 61–90: standardize selectively."," Convert repeated patterns into a maintained template, add policy checks in CI, clarify ownership, and remove components that did not create value. Review metrics with application engineers and decide whether to expand, pause, or choose a simpler platform.",[97,10721,10722],{},"Track outcomes rather than manifest count: deployment frequency, lead time, change failure rate, recovery time, SLO attainment, platform support load, and cost per environment. The blueprint is successful when product teams can move faster while understanding—not merely delegating—the failure model.",[101,10724,723],{"id":722},[97,10726,10727],{},"Write a two-page “Kubernetes application platform v1” proposal for your next realistic project. Include: a Kubernetes decision scorecard; one architecture diagram; environment boundaries; the standard Node workload contract; image promotion; RBAC, identity, secrets, and network defaults; GitOps flow; telemetry and SLO requirements; release and incident ownership; monthly cost categories; and the 30\u002F60\u002F90-day adoption plan. Finish with three explicit non-goals and three exit criteria that would move the project to a simpler platform. Ask another engineer to challenge the assumptions, then revise the document rather than defending the tools.",[101,10729,734],{"id":733},[736,10731,10732,10739],{},[739,10733,10734],{},[742,10735,10738],{"href":10736,"rel":10737},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fsetup\u002Fproduction-environment\u002F",[746],"Kubernetes: Production environment considerations",[739,10740,10741],{},[742,10742,10745],{"href":10743,"rel":10744},"https:\u002F\u002Fgithub.com\u002Fcncf\u002Ftrailmap",[746],"CNCF Cloud Native Trail Map",[756,10747,10748],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":117,"searchDepth":21,"depth":21,"links":10750},[10751,10752,10753,10754,10755,10756],{"id":10335,"depth":21,"text":10336},{"id":10544,"depth":21,"text":10545},{"id":10583,"depth":21,"text":10584},{"id":10697,"depth":21,"text":10698},{"id":722,"depth":21,"text":723},{"id":733,"depth":21,"text":734},"The final deliverable is a repeatable decision system, not a fashionable stack diagram. A senior engineer’s platform blueprint states when Kubernetes…",{},"\u002Fkubernetes-for-web-applications\u002Fday-30",{"title":10323,"description":10757},"kubernetes-for-web-applications\u002Fday-30","xayFvoLsmDrvHR8SOk0sIvYkTjTyFbrUgsBsF8-U1rg",1785539325184]