[{"data":1,"prerenderedAt":2331},["ShallowReactive",2],{"navigation":3,"\u002Fdeployment\u002Fscaled-mode":573,"\u002Fdeployment\u002Fscaled-mode-surround":2326},[4,22,64,89,223,511],{"title":5,"path":6,"stem":7,"children":8},"Introduction","\u002Fgetting-started","1.getting-started\u002F1.index",[9,10,14,18],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation",{"title":15,"path":16,"stem":17},"Key Ideas","\u002Fgetting-started\u002Fkey-ideas","1.getting-started\u002F3.key-ideas",{"title":19,"path":20,"stem":21},"Releases","\u002Fgetting-started\u002Freleases","1.getting-started\u002F4.releases",{"title":23,"path":24,"stem":25,"children":26},"Overview","\u002Fcore-concepts","2.core-concepts\u002F0.index",[27,28,32,36,40,44,48,52,56,60],{"title":23,"path":24,"stem":25},{"title":29,"path":30,"stem":31},"How It All Works","\u002Fcore-concepts\u002Farchitecture","2.core-concepts\u002F1.architecture",{"title":33,"path":34,"stem":35},"The Data Model","\u002Fcore-concepts\u002Fthe-data-model","2.core-concepts\u002F2.the-data-model",{"title":37,"path":38,"stem":39},"Layouts & Pages","\u002Fcore-concepts\u002Flayouts-and-pages","2.core-concepts\u002F3.layouts-and-pages",{"title":41,"path":42,"stem":43},"Dynamic Pages","\u002Fcore-concepts\u002Fdynamic-pages","2.core-concepts\u002F4.dynamic-pages",{"title":45,"path":46,"stem":47},"Components","\u002Fcore-concepts\u002Fcomponents","2.core-concepts\u002F5.components",{"title":49,"path":50,"stem":51},"Draft & Publish Workflow","\u002Fcore-concepts\u002Fdraft-and-publish","2.core-concepts\u002F6.draft-and-publish",{"title":53,"path":54,"stem":55},"The Admin Panel","\u002Fcore-concepts\u002Fadmin-panel","2.core-concepts\u002F7.admin-panel",{"title":57,"path":58,"stem":59},"Caching","\u002Fcore-concepts\u002Fcaching","2.core-concepts\u002F8.caching",{"title":61,"path":62,"stem":63},"Glossary","\u002Fcore-concepts\u002Fglossary","2.core-concepts\u002F9.glossary",{"title":23,"path":65,"stem":66,"children":67},"\u002Fguides","3.guides\u002F0.index",[68,69,73,77,81,85],{"title":23,"path":65,"stem":66},{"title":70,"path":71,"stem":72},"Your First Layout","\u002Fguides\u002Fyour-first-layout","3.guides\u002F1.your-first-layout",{"title":74,"path":75,"stem":76},"Your First Page Template","\u002Fguides\u002Fyour-first-page-template","3.guides\u002F2.your-first-page-template",{"title":78,"path":79,"stem":80},"Your First Component","\u002Fguides\u002Fyour-first-component","3.guides\u002F3.your-first-component",{"title":82,"path":83,"stem":84},"Alternative UI Variants","\u002Fguides\u002Falternative-ui-variants","3.guides\u002F4.alternative-ui-variants",{"title":86,"path":87,"stem":88},"Your First Dynamic Page","\u002Fguides\u002Fyour-first-dynamic-page","3.guides\u002F5.your-first-dynamic-page",{"title":23,"path":90,"stem":91,"children":92},"\u002Fapi","4.api\u002F0.index",[93,94,98,102,120,166,170,174,203,207,211,215,219],{"title":23,"path":90,"stem":91},{"title":95,"path":96,"stem":97},"Bundle Setup","\u002Fapi\u002Fbundle-setup","4.api\u002F02.bundle-setup",{"title":99,"path":100,"stem":101},"Configuration Reference","\u002Fapi\u002Fconfiguration","4.api\u002F03.configuration",{"title":103,"path":104,"stem":105,"children":106,"page":119},"Users","\u002Fapi\u002Fusers","4.api\u002F04.users",[107,111,115],{"title":108,"path":109,"stem":110},"Authentication","\u002Fapi\u002Fusers\u002Fauthentication","4.api\u002F04.users\u002F1.authentication",{"title":112,"path":113,"stem":114},"User Emails","\u002Fapi\u002Fusers\u002Fuser-emails","4.api\u002F04.users\u002F2.user-emails",{"title":116,"path":117,"stem":118},"Access Control","\u002Fapi\u002Fusers\u002Faccess-control","4.api\u002F04.users\u002F3.access-control",false,{"title":45,"path":121,"stem":122,"children":123,"page":119},"\u002Fapi\u002Fcomponents","4.api\u002F05.components",[124,128,149,162],{"title":125,"path":126,"stem":127},"Creating Components","\u002Fapi\u002Fcomponents\u002Fcreating-components","4.api\u002F05.components\u002F1.creating-components",{"title":129,"path":130,"stem":131,"children":132,"page":119},"Annotations","\u002Fapi\u002Fcomponents\u002Fannotations","4.api\u002F05.components\u002F2.annotations",[133,137,141,145],{"title":134,"path":135,"stem":136},"Publishable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fpublishable","4.api\u002F05.components\u002F2.annotations\u002F1.publishable",{"title":138,"path":139,"stem":140},"Uploadable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable","4.api\u002F05.components\u002F2.annotations\u002F2.uploadable",{"title":142,"path":143,"stem":144},"Timestamped","\u002Fapi\u002Fcomponents\u002Fannotations\u002Ftimestamped","4.api\u002F05.components\u002F2.annotations\u002F3.timestamped",{"title":146,"path":147,"stem":148},"Explicit Allow Only","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fexplicit-allow-only","4.api\u002F05.components\u002F2.annotations\u002F4.explicit-allow-only",{"title":150,"path":151,"stem":152,"children":153,"page":119},"Built Ins","\u002Fapi\u002Fcomponents\u002Fbuilt-ins","4.api\u002F05.components\u002F3.built-ins",[154,158],{"title":155,"path":156,"stem":157},"Collection Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fcollection-component","4.api\u002F05.components\u002F3.built-ins\u002F1.collection-component",{"title":159,"path":160,"stem":161},"Form Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fform-component","4.api\u002F05.components\u002F3.built-ins\u002F2.form-component",{"title":163,"path":164,"stem":165},"Groups & Positions","\u002Fapi\u002Fcomponents\u002Fcomponent-groups","4.api\u002F05.components\u002F4.component-groups",{"title":167,"path":168,"stem":169},"Page Data & Nested Pages","\u002Fapi\u002Fdynamic-pages","4.api\u002F06.dynamic-pages",{"title":171,"path":172,"stem":173},"Routes","\u002Fapi\u002Froutes","4.api\u002F07.routes",{"title":175,"path":176,"stem":177,"children":178,"page":119},"Data Fixtures","\u002Fapi\u002Fdata-fixtures","4.api\u002F08.data-fixtures",[179,183,187,191,195,199],{"title":180,"path":181,"stem":182},"Getting Started with Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgetting-started","4.api\u002F08.data-fixtures\u002F1.getting-started",{"title":184,"path":185,"stem":186},"Writing a Scaffold","\u002Fapi\u002Fdata-fixtures\u002Fwriting-a-scaffold","4.api\u002F08.data-fixtures\u002F2.writing-a-scaffold",{"title":188,"path":189,"stem":190},"Builder Reference","\u002Fapi\u002Fdata-fixtures\u002Fbuilder-reference","4.api\u002F08.data-fixtures\u002F3.builder-reference",{"title":192,"path":193,"stem":194},"Seeding Content","\u002Fapi\u002Fdata-fixtures\u002Fseeding-content","4.api\u002F08.data-fixtures\u002F4.seeding-content",{"title":196,"path":197,"stem":198},"Appending & Multiple Scaffolds","\u002Fapi\u002Fdata-fixtures\u002Fappending","4.api\u002F08.data-fixtures\u002F5.appending",{"title":200,"path":201,"stem":202},"Generating Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgenerating-fixtures","4.api\u002F08.data-fixtures\u002F6.generating-fixtures",{"title":204,"path":205,"stem":206},"Real-Time (Mercure)","\u002Fapi\u002Freal-time","4.api\u002F09.real-time",{"title":208,"path":209,"stem":210},"HTTP Caching","\u002Fapi\u002Fhttp-caching","4.api\u002F10.http-caching",{"title":212,"path":213,"stem":214},"Orphaned Data","\u002Fapi\u002Forphaned-data","4.api\u002F11.orphaned-data",{"title":216,"path":217,"stem":218},"Console Commands","\u002Fapi\u002Fconsole-commands","4.api\u002F12.console-commands",{"title":220,"path":221,"stem":222},"Debugging & Errors","\u002Fapi\u002Fdebugging","4.api\u002F13.debugging",{"title":23,"path":224,"stem":225,"children":226},"\u002Fnuxt-module","5.nuxt-module\u002F0.index",[227,228,232,245,265,294,315,438,472,475,503,507],{"title":23,"path":224,"stem":225},{"title":229,"path":230,"stem":231},"Module Setup","\u002Fnuxt-module\u002Fmodule-setup","5.nuxt-module\u002F01.module-setup",{"title":233,"path":234,"stem":235,"children":236,"page":119},"Configuration","\u002Fnuxt-module\u002Fconfiguration","5.nuxt-module\u002F02.configuration",[237,241],{"title":238,"path":239,"stem":240},"Nuxt Config","\u002Fnuxt-module\u002Fconfiguration\u002Fnuxt-config","5.nuxt-module\u002F02.configuration\u002F1.nuxt-config",{"title":242,"path":243,"stem":244},"Site Config & SEO","\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo","5.nuxt-module\u002F02.configuration\u002F2.site-config-and-seo",{"title":246,"path":247,"stem":248,"children":249,"page":119},"Building Your Ui","\u002Fnuxt-module\u002Fbuilding-your-ui","5.nuxt-module\u002F03.building-your-ui",[250,254,258,261],{"title":251,"path":252,"stem":253},"Layouts","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-layouts","5.nuxt-module\u002F03.building-your-ui\u002F1.creating-layouts",{"title":255,"path":256,"stem":257},"Page Templates","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-page-templates","5.nuxt-module\u002F03.building-your-ui\u002F2.creating-page-templates",{"title":45,"path":259,"stem":260},"\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-components","5.nuxt-module\u002F03.building-your-ui\u002F3.creating-components",{"title":262,"path":263,"stem":264},"CLI Generator","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcwa-cli","5.nuxt-module\u002F03.building-your-ui\u002F4.cwa-cli",{"title":266,"path":267,"stem":268,"children":269,"page":119},"Component Helpers","\u002Fnuxt-module\u002Fcomponent-helpers","5.nuxt-module\u002F04.component-helpers",[270,274,278,282,286,290],{"title":271,"path":272,"stem":273},"Images & Media","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fimages-and-uploads","5.nuxt-module\u002F04.component-helpers\u002F1.images-and-uploads",{"title":275,"path":276,"stem":277},"Collections & Pagination","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fcollections-and-pagination","5.nuxt-module\u002F04.component-helpers\u002F2.collections-and-pagination",{"title":279,"path":280,"stem":281},"HTML Content","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fhtml-content","5.nuxt-module\u002F04.component-helpers\u002F3.html-content",{"title":283,"path":284,"stem":285},"Rich-Text Editor","\u002Fnuxt-module\u002Fcomponent-helpers\u002Frich-text-editor","5.nuxt-module\u002F04.component-helpers\u002F4.rich-text-editor",{"title":287,"path":288,"stem":289},"Real-Time Updates","\u002Fnuxt-module\u002Fcomponent-helpers\u002Freal-time-updates","5.nuxt-module\u002F04.component-helpers\u002F5.real-time-updates",{"title":291,"path":292,"stem":293},"Forms","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fforms","5.nuxt-module\u002F04.component-helpers\u002F6.forms",{"title":295,"path":296,"stem":297,"children":298,"page":119},"Cwa Components","\u002Fnuxt-module\u002Fcwa-components","5.nuxt-module\u002F05.cwa-components",[299,303,307,311],{"title":300,"path":301,"stem":302},"\u003CCwaComponentGroup \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-component-group","5.nuxt-module\u002F05.cwa-components\u002F1.cwa-component-group",{"title":304,"path":305,"stem":306},"\u003CCwaPage \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-page","5.nuxt-module\u002F05.cwa-components\u002F2.cwa-page",{"title":308,"path":309,"stem":310},"\u003CCwaLink \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-link","5.nuxt-module\u002F05.cwa-components\u002F3.cwa-link",{"title":312,"path":313,"stem":314},"\u003CCwaDefaultLayout \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-default-layout","5.nuxt-module\u002F05.cwa-components\u002F5.cwa-default-layout",{"title":316,"path":317,"stem":318,"children":319,"page":119},"Composables","\u002Fnuxt-module\u002Fcomposables","5.nuxt-module\u002F06.composables",[320,323,331,356,376,393,413],{"title":23,"path":321,"stem":322},"\u002Fnuxt-module\u002Fcomposables\u002Foverview","5.nuxt-module\u002F06.composables\u002F0.overview",{"title":324,"path":325,"stem":326,"children":327,"page":119},"Layout","\u002Fnuxt-module\u002Fcomposables\u002Flayout","5.nuxt-module\u002F06.composables\u002F1.layout",[328],{"title":324,"path":329,"stem":330},"\u002Fnuxt-module\u002Fcomposables\u002Flayout\u002Fuse-cwa-layout","5.nuxt-module\u002F06.composables\u002F1.layout\u002F1.use-cwa-layout",{"title":332,"path":333,"stem":334,"children":335,"page":119},"Component","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent","5.nuxt-module\u002F06.composables\u002F2.component",[336,340,344,348,352],{"title":337,"path":338,"stem":339},"Component (recommended)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-component","5.nuxt-module\u002F06.composables\u002F2.component\u002F1.use-cwa-component",{"title":341,"path":342,"stem":343},"Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F2.use-cwa-resource",{"title":345,"path":346,"stem":347},"File Field","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-file-field","5.nuxt-module\u002F06.composables\u002F2.component\u002F3.use-cwa-file-field",{"title":349,"path":350,"stem":351},"Collection Pagination","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-pagination","5.nuxt-module\u002F06.composables\u002F2.component\u002F4.use-cwa-collection-pagination",{"title":353,"path":354,"stem":355},"Collection Resource (deprecated)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F9.use-cwa-collection-resource",{"title":357,"path":358,"stem":359,"children":360,"page":119},"Form","\u002Fnuxt-module\u002Fcomposables\u002Fform","5.nuxt-module\u002F06.composables\u002F3.form",[361,364,368,372],{"title":357,"path":362,"stem":363},"\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form","5.nuxt-module\u002F06.composables\u002F3.form\u002F1.use-cwa-form",{"title":365,"path":366,"stem":367},"Form Input","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-input","5.nuxt-module\u002F06.composables\u002F3.form\u002F2.use-cwa-form-input",{"title":369,"path":370,"stem":371},"Form Repeated","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-repeated","5.nuxt-module\u002F06.composables\u002F3.form\u002F3.use-cwa-form-repeated",{"title":373,"path":374,"stem":375},"Form Collection","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-collection","5.nuxt-module\u002F06.composables\u002F3.form\u002F4.use-cwa-form-collection",{"title":377,"path":378,"stem":379,"children":380,"page":119},"Admin Manager","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager","5.nuxt-module\u002F06.composables\u002F4.admin-manager",[381,385,389],{"title":382,"path":383,"stem":384},"Manager Tab","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-manager-tab","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F1.use-cwa-resource-manager-tab",{"title":386,"path":387,"stem":388},"Resource Model","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-model","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F2.use-cwa-resource-model",{"title":390,"path":391,"stem":392},"Resource Upload","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-resource-upload","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F3.use-cwa-resource-upload",{"title":394,"path":395,"stem":396,"children":397,"page":119},"Utilities","\u002Fnuxt-module\u002Fcomposables\u002Futilities","5.nuxt-module\u002F06.composables\u002F5.utilities",[398,402,406,410],{"title":399,"path":400,"stem":401},"Resource Endpoint","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-endpoint","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F1.use-cwa-resource-endpoint",{"title":403,"path":404,"stem":405},"Query Model","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-query-bound-model","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F2.use-query-bound-model",{"title":407,"path":408,"stem":409},"Resource Route","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-route","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F3.use-cwa-resource-route",{"title":279,"path":411,"stem":412},"\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-html-content","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F4.use-html-content",{"title":414,"path":415,"stem":416,"children":417,"page":119},"Auth","\u002Fnuxt-module\u002Fcomposables\u002Fauth","5.nuxt-module\u002F06.composables\u002F6.auth",[418,422,426,430,434],{"title":419,"path":420,"stem":421},"Login","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-login","5.nuxt-module\u002F06.composables\u002F6.auth\u002F1.use-login",{"title":423,"path":424,"stem":425},"Forgot Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-forgot-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F2.use-forgot-password",{"title":427,"path":428,"stem":429},"Reset Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-reset-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F3.use-reset-password",{"title":431,"path":432,"stem":433},"Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F4.use-verify-email",{"title":435,"path":436,"stem":437},"Resend Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-resend-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F5.use-resend-verify-email",{"title":439,"path":440,"stem":441,"children":442,"page":119},"Cwa Api","\u002Fnuxt-module\u002Fcwa-api","5.nuxt-module\u002F07.cwa-api",[443,446,450,454,457,460,464,468],{"title":23,"path":444,"stem":445},"\u002Fnuxt-module\u002Fcwa-api\u002Foverview","5.nuxt-module\u002F07.cwa-api\u002F1.overview",{"title":447,"path":448,"stem":449},"Resources","\u002Fnuxt-module\u002Fcwa-api\u002Fresources","5.nuxt-module\u002F07.cwa-api\u002F2.resources",{"title":451,"path":452,"stem":453},"Resources Manager","\u002Fnuxt-module\u002Fcwa-api\u002Fresources-manager","5.nuxt-module\u002F07.cwa-api\u002F3.resources-manager",{"title":414,"path":455,"stem":456},"\u002Fnuxt-module\u002Fcwa-api\u002Fauth","5.nuxt-module\u002F07.cwa-api\u002F4.auth",{"title":291,"path":458,"stem":459},"\u002Fnuxt-module\u002Fcwa-api\u002Fforms","5.nuxt-module\u002F07.cwa-api\u002F5.forms",{"title":461,"path":462,"stem":463},"Admin","\u002Fnuxt-module\u002Fcwa-api\u002Fadmin","5.nuxt-module\u002F07.cwa-api\u002F6.admin",{"title":465,"path":466,"stem":467},"Site Config","\u002Fnuxt-module\u002Fcwa-api\u002Fsite-config","5.nuxt-module\u002F07.cwa-api\u002F7.site-config",{"title":469,"path":470,"stem":471},"Orphaned Resources","\u002Fnuxt-module\u002Fcwa-api\u002Forphaned-resources","5.nuxt-module\u002F07.cwa-api\u002F8.orphaned-resources",{"title":108,"path":473,"stem":474},"\u002Fnuxt-module\u002Fauthentication","5.nuxt-module\u002F08.authentication",{"title":476,"path":477,"stem":478,"children":479,"page":119},"Cwa Layer","\u002Fnuxt-module\u002Fcwa-layer","5.nuxt-module\u002F9.cwa-layer",[480,483,487,491,495,499],{"title":23,"path":481,"stem":482},"\u002Fnuxt-module\u002Fcwa-layer\u002Foverview","5.nuxt-module\u002F9.cwa-layer\u002F1.overview",{"title":484,"path":485,"stem":486},"Auth Pages","\u002Fnuxt-module\u002Fcwa-layer\u002Fauth-pages","5.nuxt-module\u002F9.cwa-layer\u002F2.auth-pages",{"title":488,"path":489,"stem":490},"Admin Panel","\u002Fnuxt-module\u002Fcwa-layer\u002Fadmin-panel","5.nuxt-module\u002F9.cwa-layer\u002F3.admin-panel",{"title":492,"path":493,"stem":494},"Styling","\u002Fnuxt-module\u002Fcwa-layer\u002Fstyling","5.nuxt-module\u002F9.cwa-layer\u002F4.styling",{"title":496,"path":497,"stem":498},"Date Pickers","\u002Fnuxt-module\u002Fcwa-layer\u002Fdate-pickers","5.nuxt-module\u002F9.cwa-layer\u002F5.date-pickers",{"title":500,"path":501,"stem":502},"Maintenance","\u002Fnuxt-module\u002Fcwa-layer\u002Fmaintenance","5.nuxt-module\u002F9.cwa-layer\u002F6.maintenance",{"title":504,"path":505,"stem":506},"PWA & Offline","\u002Fnuxt-module\u002Fpwa","5.nuxt-module\u002F91.pwa",{"title":508,"path":509,"stem":510},"Page Caching","\u002Fnuxt-module\u002Fpage-caching","5.nuxt-module\u002F92.page-caching",{"title":512,"path":513,"stem":514,"children":515},"Deploying a CWA Site","\u002Fdeployment","6.deployment\u002F0.index",[516,517,521,525,529,533,537,541,545,549,553,557,561,565,569],{"title":512,"path":513,"stem":514},{"title":518,"path":519,"stem":520},"Docker","\u002Fdeployment\u002Fdocker","6.deployment\u002F1.docker",{"title":522,"path":523,"stem":524},"Kubernetes & Helm","\u002Fdeployment\u002Fkubernetes","6.deployment\u002F2.kubernetes",{"title":526,"path":527,"stem":528},"Scaled Mode","\u002Fdeployment\u002Fscaled-mode","6.deployment\u002F2.scaled-mode",{"title":530,"path":531,"stem":532},"CI\u002FCD","\u002Fdeployment\u002Fci-cd","6.deployment\u002F3.ci-cd",{"title":534,"path":535,"stem":536},"Migrations and Backups","\u002Fdeployment\u002Fmigrations-and-backups","6.deployment\u002F3.migrations-and-backups",{"title":538,"path":539,"stem":540},"CI\u002FCD Variables","\u002Fdeployment\u002Fvariables","6.deployment\u002F4.variables",{"title":542,"path":543,"stem":544},"Cloudflare","\u002Fdeployment\u002Fcloudflare","6.deployment\u002F5.cloudflare",{"title":546,"path":547,"stem":548},"Origin Protection","\u002Fdeployment\u002Forigin-protection","6.deployment\u002F5.origin-protection",{"title":550,"path":551,"stem":552},"Load Testing","\u002Fdeployment\u002Fload-testing","6.deployment\u002F6.load-testing",{"title":554,"path":555,"stem":556},"Performance Audit","\u002Fdeployment\u002Fperformance-audit","6.deployment\u002F6.performance-audit",{"title":558,"path":559,"stem":560},"Reading Lighthouse Scores","\u002Fdeployment\u002Freading-lighthouse-scores","6.deployment\u002F6.reading-lighthouse-scores",{"title":562,"path":563,"stem":564},"Browser Tests","\u002Fdeployment\u002Fbrowser-tests","6.deployment\u002F7.browser-tests",{"title":566,"path":567,"stem":568},"Faster Functional Tests","\u002Fdeployment\u002Ffaster-functional-tests","6.deployment\u002F7.faster-functional-tests",{"title":570,"path":571,"stem":572},"Troubleshooting","\u002Fdeployment\u002Ftroubleshooting","6.deployment\u002F7.troubleshooting",{"id":574,"title":526,"badge":575,"body":576,"description":2320,"extension":2321,"links":575,"meta":2322,"navigation":2323,"path":527,"seo":2324,"stem":528,"__hash__":2325},"docs\u002F6.deployment\u002F2.scaled-mode.md",null,{"type":577,"value":578,"toc":2292},"minimark",[579,616,622,628,637,642,714,718,724,729,745,750,768,772,775,790,802,809,813,816,1019,1022,1032,1037,1044,1159,1169,1173,1177,1191,1232,1255,1259,1287,1328,1331,1335,1346,1388,1396,1403,1406,1417,1435,1456,1493,1517,1520,1524,1533,1553,1556,1559,1563,1580,1598,1664,1679,1703,1706,1715,1719,1722,1802,1805,1909,1919,1928,1934,1938,1941,2008,2011,2015,2018,2093,2097,2130,2133,2137,2156,2234,2237,2277,2281],[580,581,584],"callout",{"icon":582,"color":583},"i-heroicons-exclamation-triangle","warning",[585,586,587,591,592,599,600,609,610,615],"p",{},[588,589,590],"strong",{},"Scaled mode is still being built: don't turn it on for a real site yet."," The template's own notes describe it as \"being built; don't turn it on yet\", and its changelog lists each part as \"still being built\". What's left is the rest of the rehearsal on the ",[593,594,598],"a",{"href":595,"rel":596},"https:\u002F\u002Fpreview.cwa.rocks",[597],"nofollow","preview.cwa.rocks"," playground. This page describes what the template has now, so you can plan for it. ",[601,602,603,604],"em",{},"Since ",[593,605,608],{"href":606,"rel":607},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Freleases\u002Ftag\u002Fv2.0.0-alpha.43",[597],"template 2.0.0-alpha.43"," (",[593,611,614],{"href":612,"rel":613},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F85",[597],"#85",")",[585,617,618,621],{},[588,619,620],{},"Scaled mode"," runs a site's API (the php pods) on two or more pods instead of one. It's off by default, and a project turns it on for each deploy track with one switch.",[585,623,624,627],{},[588,625,626],{},"The goal is redundancy."," By default one php pod serves the whole API, so every node upgrade, eviction or out-of-memory restart is an API outage until the pod comes back. With two or more pods, losing one is not an outage. Uncached (\"cold\") load also spreads across the pods, and pods can be added for a deploy's cache warm and then removed again.",[580,629,631],{"icon":630},"i-heroicons-photo",[585,632,633,636],{},[588,634,635],{},"Image placeholder:"," two diagrams side by side. Left, the default: one php pod holding its own cache and Mercure hub. Right, scaled mode: two or three php pods behind the API Service, all reading and writing one Redis cache, publishing to one Mercure hub pod, with a PodDisruptionBudget around the php pods.",[638,639,641],"h2",{"id":640},"terms-used-on-this-page","Terms used on this page",[643,644,645,668,686,692,698,708],"ul",{},[646,647,648,651,652,655,656,659,660,663,664,667],"li",{},[588,649,650],{},"Pod:"," one running copy of a server. A ",[588,653,654],{},"replica"," is one of several identical pods. A ",[588,657,658],{},"node"," is the machine pods run on. A ",[588,661,662],{},"node upgrade"," or ",[588,665,666],{},"eviction"," moves pods off a node by stopping them.",[646,669,670,673,674,677,678,681,682,685],{},[588,671,672],{},"Souin:"," the HTTP cache inside each php pod. A ",[588,675,676],{},"purge"," removes the cached copies of one thing (a tag). A ",[588,679,680],{},"flush"," removes everything. A ",[588,683,684],{},"warm"," requests API responses so they're cached before visitors ask for them.",[646,687,688,691],{},[588,689,690],{},"Redis:"," a small in-memory database. In scaled mode it holds Souin's cache, so every php pod shares one copy.",[646,693,694,697],{},[588,695,696],{},"Mercure:"," the hub that pushes live updates to open browsers.",[646,699,700,703,704,707],{},[588,701,702],{},"HPA (autoscaler):"," adds and removes pods by CPU load, between a minimum and a maximum. ",[588,705,706],{},"PDB (PodDisruptionBudget):"," stops Kubernetes taking down more than a set number of pods at once.",[646,709,710,713],{},[588,711,712],{},"Track:"," where a deploy goes: review, staging, canary or production.",[638,715,717],{"id":716},"do-you-need-it","Do you need it?",[585,719,720,723],{},[588,721,722],{},"One API pod is enough for most sites."," Most requests are cache hits, and one pod serves several hundred cached responses a second. During a short API outage, Cloudflare and browsers keep serving pages they have already cached. For most brochure and CMS sites, a few minutes of API downtime during node maintenance is acceptable.",[585,725,726],{},[588,727,728],{},"Turn it on when:",[643,730,731,734,737],{},[646,732,733],{},"the site can't accept a few minutes of API downtime during a node upgrade, an eviction or a crash;",[646,735,736],{},"the API pod keeps restarting because it runs out of memory;",[646,738,739,740,744],{},"uncached traffic (signed-in users, a busy user area, heavy editing, cold pages after a flush) is more than one pod can render, after you've tried a bigger pod first (see ",[593,741,743],{"href":742},"\u002Fdeployment\u002Fkubernetes#resource-requests-and-limits","Kubernetes & Helm › Resource Requests and Limits",").",[585,746,747],{},[588,748,749],{},"What it doesn't protect:",[643,751,752,758],{},[646,753,754,757],{},[588,755,756],{},"The database."," There is still one. If it's down, cached pages keep working, but logins, edits and uncached requests fail in either mode.",[646,759,760,763,764,744],{},[588,761,762],{},"Redis and the Mercure hub"," are one pod each. Losing either slows the site or pauses live updates, but doesn't take the site down (see ",[593,765,767],{"href":766},"#what-can-go-wrong","What can go wrong",[638,769,771],{"id":770},"why-a-second-pod-needs-a-shared-cache","Why a second pod needs a shared cache",[585,773,774],{},"Without scaled mode, two things live inside the php pod that break with a second pod:",[776,777,778,784],"ol",{},[646,779,780,783],{},[588,781,782],{},"The cache."," Souin keeps it in the pod's memory. A write handled by pod B purges only pod B's cache, so pod A keeps serving the old response. With production's one-year cache lifetime, that copy stays stale.",[646,785,786,789],{},[588,787,788],{},"Mercure's queue."," Visitors connected to pod A never get updates published on pod B, and no error is shown.",[585,791,792,793,797,798,801],{},"That's why the template refuses more than one API pod unless scaled mode is on (see ",[593,794,796],{"href":795},"\u002Fdeployment\u002Fkubernetes#php-stays-at-one-replica","Kubernetes & Helm › php stays at one replica","). Scaled mode moves the cache into one Redis and Mercure into one hub, which every php pod shares. ",[588,799,800],{},"The symptoms of several pods without a shared cache"," are an edit that shows on some refreshes and not others, and live updates that reach some people and not others.",[585,803,804,805,744],{},"The third problem, several pods running migrations at once, is already solved in every mode: the entrypoint takes a database lock, so one pod migrates and the others wait (see ",[593,806,808],{"href":807},"\u002Fdeployment\u002Fkubernetes#running-migrations","Kubernetes & Helm › Running Migrations",[638,810,812],{"id":811},"turning-it-on","Turning it on",[585,814,815],{},"Set the switch for the track and deploy. Nothing else needs setting by hand.",[817,818,819,835],"table",{},[820,821,822],"thead",{},[823,824,825,829,832],"tr",{},[826,827,828],"th",{},"Variable",[826,830,831],{},"Default",[826,833,834],{},"What it does",[836,837,838,870,896,911,932,951,966,984,1004],"tbody",{},[823,839,840,857,863],{},[841,842,843,847,848,847,851,847,854],"td",{},[844,845,846],"code",{},"CWA_CI_API_SCALED_PRODUCTION",", ",[844,849,850],{},"_STAGING",[844,852,853],{},"_CANARY",[844,855,856],{},"_REVIEW",[841,858,859,860],{},"off unless ",[844,861,862],{},"\"true\"",[841,864,865,866,869],{},"Turns scaled mode on for that track. There's one name per track because GitLab's staging job runs under the ",[844,867,868],{},"production"," environment, so a variable scoped to an environment would reach staging too",[823,871,872,881,890],{},[841,873,874,877,878],{},[844,875,876],{},"CWA_CI_API_AUTOSCALE_MIN"," \u002F ",[844,879,880],{},"_MAX",[841,882,883,877,886,889],{},[844,884,885],{},"2",[844,887,888],{},"3"," in scaled mode",[841,891,892,893],{},"The fewest and most php pods. Outside scaled mode both default to ",[844,894,895],{},"1",[823,897,898,903,908],{},[841,899,900],{},[844,901,902],{},"CWA_CI_API_AUTOSCALE_CPU_PERCENT",[841,904,905],{},[844,906,907],{},"90",[841,909,910],{},"The HPA adds a pod when average CPU passes this percentage of the pod's CPU request (100m)",[823,912,913,918,923],{},[841,914,915],{},[844,916,917],{},"CWA_CI_API_REDIS_MEMORY",[841,919,920],{},[844,921,922],{},"256Mi",[841,924,925,926,663,929],{},"How much the cache may hold, in ",[844,927,928],{},"Mi",[844,930,931],{},"Gi",[823,933,934,939,944],{},[841,935,936],{},[844,937,938],{},"CWA_CI_API_WARM_SURGE",[841,940,941],{},[844,942,943],{},"0",[841,945,946,947,615],{},"Extra php pods for a deploy's flush and warm (see ",[593,948,950],{"href":949},"#every-deploy-flushes-and-warms","Every deploy flushes and warms",[823,952,953,958,963],{},[841,954,955],{},[844,956,957],{},"CWA_CI_API_WARM_SURGE_TIMEOUT",[841,959,960],{},[844,961,962],{},"300",[841,964,965],{},"Seconds the deploy waits for the surge's pods",[823,967,968,973,977],{},[841,969,970],{},[844,971,972],{},"CWA_CI_API_WARM_CONCURRENCY",[841,974,975],{},[844,976,885],{},[841,978,979,980,983],{},"Requests the warm sends at once ",[588,981,982],{},"for each php pod",", multiplied by the pods during the warm, a surge's included",[823,985,986,991,994],{},[841,987,988],{},[844,989,990],{},"CWA_CI_API_REDIS_WATCH",[841,992,993],{},"on",[841,995,996,999,1000],{},[844,997,998],{},"\"false\""," turns off the ",[593,1001,1003],{"href":1002},"#the-redis-watch","Redis watch",[823,1005,1006,1011,1016],{},[841,1007,1008],{},[844,1009,1010],{},"CWA_CI_API_REDIS_WATCH_SCHEDULE",[841,1012,1013],{},[844,1014,1015],{},"*\u002F2 * * * *",[841,1017,1018],{},"How often the Redis watch runs",[585,1020,1021],{},"Then read the deploy log. It prints the mode before it touches the cluster:",[1023,1024,1029],"pre",{"className":1025,"code":1027,"language":1028},[1026],"language-text","Scaled mode: 2 to 3 API pods sharing this release's Redis (256Mi).\n","text",[844,1030,1027],{"__ignoreMap":1031},"",[1033,1034,1036],"h3",{"id":1035},"what-the-deploy-refuses","What the deploy refuses",[585,1038,1039,1040,1043],{},"The deploy checks these before it changes anything in the cluster, and fails with ",[844,1041,1042],{},"The API's scaling settings can't be deployed:"," followed by each problem:",[643,1045,1046,1086,1092,1104,1112,1126,1143],{},[646,1047,1048,1051,1052,663,1054,1057,1058,1061,1062,1065,1066,1068,1069,1072,1073],{},[588,1049,1050],{},"More than one php pod without scaled mode",": ",[844,1053,876],{},[844,1055,1056],{},"CWA_CI_API_AUTOSCALE_MAX"," above 1 while the autoscaler is on (the default), or ",[844,1059,1060],{},"CWA_CI_API_REPLICA_COUNT"," above 1 with ",[844,1063,1064],{},"CWA_CI_API_AUTOSCALE=false",". Like the chart, the check ignores the setting that has no effect, so a ",[844,1067,1060],{}," of 2 beside the autoscaler is allowed: the chart doesn't use it. The message names the switch to set. The chart refuses more than one pod too, for a ",[844,1070,1071],{},"helm install"," without the pipeline. ",[601,1074,603,1075,609,1078,1085],{},[593,1076,608],{"href":606,"rel":1077},[597],[593,1079,1082],{"href":1080,"rel":1081},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F32e919996cb78408e822fddffe46877ac6a22ad8",[597],[844,1083,1084],{},"32e9199","); before, any of the three above 1 was refused.",[646,1087,1088,1091],{},[588,1089,1090],{},"A minimum above the maximum",", or below 1.",[646,1093,1094,1103],{},[588,1095,1096,1099,1100],{},[844,1097,1098],{},"CWA_CI_API_AUTOSCALE"," set to anything but ",[844,1101,1102],{},"true"," in scaled mode, which runs the API under its autoscaler.",[646,1105,1106,1111],{},[588,1107,1108],{},[844,1109,1110],{},"CWA_CI_RESET_DATABASE=true"," in scaled mode, which would drop the schema each time an API pod starts.",[646,1113,1114,1117,1118,1121,1122,1125],{},[588,1115,1116],{},"A cache store set by hand."," In scaled mode ",[844,1119,1120],{},"CWA_API_CADDY_CACHE_STORAGE"," must be unset, because the store is the release's own Redis. Without scaled mode, a store that names ",[844,1123,1124],{},"redis"," is refused.",[646,1127,1128,1138,1139,1142],{},[588,1129,1130,1131,1133,1134,663,1136],{},"A ",[844,1132,917],{}," not written in ",[844,1135,928],{},[844,1137,931],{},", such as ",[844,1140,1141],{},"256M",".",[646,1144,1145,1152,1153,1155,1156,1158],{},[588,1146,1130,1147,663,1149,1151],{},[844,1148,938],{},[844,1150,972],{}," that isn't a whole number",", or a ",[844,1154,972],{}," of ",[844,1157,943],{},": it's requests per pod, so at least 1.",[585,1160,1161,1164,1165,744],{},[844,1162,1163],{},"CADDY_CACHE_EXTRA_CONFIG"," can't name a store in any mode (see ",[593,1166,1168],{"href":1167},"\u002Fdeployment\u002Fvariables#http-cache","CI\u002FCD Variables › HTTP cache",[638,1170,1172],{"id":1171},"what-runs-in-scaled-mode","What runs in scaled mode",[1033,1174,1176],{"id":1175},"the-shared-cache","The shared cache",[585,1178,1179,1180,847,1183,1190],{},"The chart starts one Redis for each release (",[844,1181,1182],{},"templates\u002Fredis.yaml",[593,1184,1187],{"href":1185,"rel":1186},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F62bfeb928ad665d29666e4569507f1f79c47172a",[597],[844,1188,1189],{},"62bfeb9","). Helm creates and removes it with the release, so staging, production and each review app get their own.",[643,1192,1193,1199,1205,1215,1223],{},[646,1194,1195,1198],{},[588,1196,1197],{},"It's only a cache."," It keeps nothing on disk, so a restart starts empty. Nothing in it ever needs keeping.",[646,1200,1201,1204],{},[588,1202,1203],{},"Nothing else may use it."," A flush deletes every key in it.",[646,1206,1207,1210,1211,1214],{},[588,1208,1209],{},"It never evicts Souin's tag index."," Redis uses ",[844,1212,1213],{},"volatile-lru",": when it's full it drops cached responses, which expire, and never the tag index, which doesn't. Without the index, a purge couldn't find the pages it should remove.",[646,1216,1217,1222],{},[588,1218,1219,1220,1142],{},"Its memory is ",[844,1221,917],{}," The container gets about 1.4 times that, plus 16Mi, for Redis's own overhead: 374Mi for the default 256Mi.",[646,1224,1225,1051,1228,1231],{},[588,1226,1227],{},"It has a password",[844,1229,1230],{},"REDIS_PASSWORD"," if you set one, else the one already deployed, else a new one. A NetworkPolicy lets only the release's php pods and its Redis watch reach it, on clusters that enforce NetworkPolicy. The password protects it either way.",[585,1233,1234,1235,1238,1239,1242,1243,1246,1247,1250,1251,744],{},"The chart points every php pod's Souin at it through ",[844,1236,1237],{},"CADDY_CACHE_STORAGE",". ",[588,1240,1241],{},"Its timeouts are 100ms, with no retries",", so a Redis outage makes requests uncached rather than slow. While Redis is down, responses carry a ",[844,1244,1245],{},"Cache-Status"," with ",[844,1248,1249],{},"REDIS-INSERTION-ERROR",", and caching starts again by itself once Redis is back. The image's Redis store is patched so that pods sharing one Redis all drop every page under a purged tag (see ",[593,1252,1254],{"href":1253},"\u002Fdeployment\u002Fdocker#php-dockerfile-stages","Docker › PHP Dockerfile stages",[1033,1256,1258],{"id":1257},"the-mercure-hub","The Mercure hub",[585,1260,1261,1262,847,1265,847,1272,1279,1280,1246,1283,1286],{},"Mercure gets its own pod (",[844,1263,1264],{},"templates\u002Fmercure-hub.yaml",[593,1266,1269],{"href":1267,"rel":1268},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fd51851c8fecb429aca2702c871198eec9c663cb6",[597],[844,1270,1271],{},"d51851c",[593,1273,1276],{"href":1274,"rel":1275},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Feca4d2bdd92e21a671f99d19552d97adcf7c67e0",[597],[844,1277,1278],{},"eca4d2b","). It runs the php image's own ",[844,1281,1282],{},"frankenphp",[844,1284,1285],{},"mercure-hub.Caddyfile",", so its Mercure version always matches php's, and it has no database and never migrates.",[643,1288,1289,1295,1305,1315],{},[646,1290,1291,1294],{},[588,1292,1293],{},"php publishes to the hub's pod",", and stops running a hub of its own.",[646,1296,1297,1304],{},[588,1298,1299,1300,1303],{},"Browsers still subscribe at ",[844,1301,1302],{},"\u002F.well-known\u002Fmercure"," on the API's hostname."," php forwards those requests to the hub's pod, so the ingress and TLS don't change.",[646,1306,1307,1310,1311,1314],{},[588,1308,1309],{},"The hub's settings are php's."," Both read them from ",[844,1312,1313],{},"api\u002Ffrankenphp\u002Fmercure-hub.caddy",". If you change a hub setting, change it there, never in either Caddyfile.",[646,1316,1317,1320,1321,744],{},[588,1318,1319],{},"It stops cleanly."," A 5-second grace period ends open subscriptions when the pod stops, and browsers reconnect (",[593,1322,1325],{"href":1323,"rel":1324},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F05090d5850f1fe2d3e78e3422e6299b27c10fb10",[597],[844,1326,1327],{},"05090d5",[585,1329,1330],{},"It's the free, open-source hub, which runs as one copy. When it restarts, live updates pause and browsers reconnect.",[1033,1332,1334],{"id":1333},"the-api-pods","The API pods",[585,1336,1337,1338,1345],{},"In scaled mode the php pods change in four ways (",[593,1339,1342],{"href":1340,"rel":1341},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F9959bf387d9813d01587e9972e1545c51282a81f",[597],[844,1343,1344],{},"9959bf3","):",[643,1347,1348,1366,1372,1378],{},[646,1349,1350,1356,1357,1361,1362,1365],{},[588,1351,1352,1353,1142],{},"A PodDisruptionBudget with ",[844,1354,1355],{},"maxUnavailable: 1"," A node drain moves the pods one at a time, so one keeps serving. With one pod a budget would block every drain, which is why the default has none (see ",[593,1358,1360],{"href":1359},"\u002Fdeployment\u002Fkubernetes#surviving-node-scale-down","Kubernetes & Helm › Surviving node scale-down","). The single pod's ",[844,1363,1364],{},"safe-to-evict: \"false\""," annotation goes, because the budget replaces it.",[646,1367,1368,1371],{},[588,1369,1370],{},"Pods spread across nodes"," where possible, so losing a node never takes them all.",[646,1373,1374,1377],{},[588,1375,1376],{},"An HPA on CPU only",", between 2 and 3 pods by default. It adds pods at once and removes them only after ten minutes. The memory target is dropped, because php rarely hands memory back and a memory target would hold the HPA at its maximum.",[646,1379,1380,1383,1384,744],{},[588,1381,1382],{},"No startup warm."," A new pod shares a cache that's already warm, so it skips the warm a single pod runs before its probes start (see ",[593,1385,1387],{"href":1386},"\u002Fdeployment\u002Fkubernetes#warming-the-api-cache-on-start","Kubernetes & Helm › Warming the API cache on start",[585,1389,1390,1391,1395],{},"The ",[593,1392,1394],{"href":1393},"\u002Fdeployment\u002Forigin-protection#rate-limit-on-cache-misses","Caddy rate limit"," stays per pod and isn't divided: each pod limits the traffic that reaches it, which is what protects it.",[580,1397,1398],{"icon":630},[585,1399,1400,1402],{},[588,1401,635],{}," a node drain in scaled mode: two php pods on two nodes, the drain evicting one, the PDB refusing to evict the second until the replacement is ready.",[638,1404,950],{"id":1405},"every-deploy-flushes-and-warms",[585,1407,1408,1409,1416],{},"Redis outlives the php pods, so a rollout no longer empties the cache. ",[588,1410,1411,1412,1415],{},"Every scaled deploy flushes the whole cache, then warms ",[844,1413,1414],{},"\u002F_api"," again."," The deploy that turns scaled mode on flushes too.",[585,1418,1419,1420,1427,1428,744],{},"The deploy waits for the rollout, and then for the old php pods to be gone (up to 120 seconds), before it flushes. Straight after a rollout, the API Service still sends some connections to pods that are stopping, and warm requests on those fail (",[593,1421,1424],{"href":1422,"rel":1423},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F4d2ad505bdc724992f1cad89677c52f8533a4245",[597],[844,1425,1426],{},"4d2ad50","). Commands run in a php pod that is ready and not stopping (",[593,1429,1432],{"href":1430,"rel":1431},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fba19fb47c8ead79cc55c2b291bac596279f0a895",[597],[844,1433,1434],{},"ba19fb4",[585,1436,1437,1440,1441,1448,1449,1451,1452,1455],{},[588,1438,1439],{},"The warm goes through the API Service",", so every pod renders a share and each response is stored once in Redis (",[593,1442,1445],{"href":1443,"rel":1444},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fa4bd81836fcb436a220cbd09985eac0bd1fb2243",[597],[844,1446,1447],{},"a4bd818","). It sends ",[844,1450,972],{}," requests at a time for each pod (2 by default), times the HPA's minimum plus any surge: 4 at the default minimum of 2. Keep the total within what the database can take. Each pass still stops at ",[844,1453,1454],{},"CWA_CI_API_WARM_HTTP_CACHE_MAX_TIME"," seconds. Fixture loads and the image variants job warm through the Service the same way after their flushes.",[585,1457,1458,609,1461,1464,1465,1469,1470,1473,1474],{},[588,1459,1460],{},"The warm opens a new connection for every request",[844,1462,1463],{},"--requests-per-connection=1",", see ",[593,1466,1468],{"href":1467},"\u002Fapi\u002Fconsole-commands#spreading-a-warm-across-pods","Console Commands › Spreading a warm across pods","), so the Service picks a pod for each one. The Service balances connections, not requests, and a warm that kept its connections open split unevenly: on preview one pod served 252 requests and the other 38. A warm on a single pod, through ",[844,1471,1472],{},"http:\u002F\u002Flocalhost",", keeps its connections open as before. ",[601,1475,603,1476,609,1479,1486,1487,1492],{},[593,1477,608],{"href":606,"rel":1478},[597],[593,1480,1483],{"href":1481,"rel":1482},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F47792375ded99d08a1f201832cdd6a112db3f058",[597],[844,1484,1485],{},"4779237","), which pins ",[593,1488,1491],{"href":1489,"rel":1490},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fapi-components-bundle\u002Freleases\u002Ftag\u002F2.0.0-alpha.34",[597],"bundle 2.0.0-alpha.34",": the option needs it.",[580,1494,1495],{"icon":582,"color":583},[585,1496,1497,1502,1503,1510,1511,1514,1515,1142],{},[588,1498,1499,1501],{},[844,1500,972],{}," is per pod now."," Until ",[593,1504,1507],{"href":1505,"rel":1506},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Ffa946403dfebb701b1948f683304c824cc9f74dc",[597],[844,1508,1509],{},"fa94640"," it was the warm's total. A project that set a total divides it by its pod count: ",[844,1512,1513],{},"8"," for 4 pods becomes ",[844,1516,885],{},[585,1518,1519],{},"A failed warm only warns. It never fails a live deploy.",[1033,1521,1523],{"id":1522},"more-pods-for-the-warm","More pods for the warm",[585,1525,1526,1527,1529,1530,1532],{},"With ",[844,1528,938],{}," set (say ",[844,1531,885],{},"), a scaled deploy:",[776,1534,1535,1538,1544,1550],{},[646,1536,1537],{},"raises the HPA's minimum by that many pods before the flush (2 + 2 = 4), and its maximum too when it would be below the new minimum;",[646,1539,1540,1541,1543],{},"waits for the extra pods to be ready, for up to ",[844,1542,957],{}," seconds, because the cluster may need a new node. If they aren't all ready in time, it warms on the pods there are and warns;",[646,1545,1546,1547,1549],{},"warms with ",[844,1548,972],{}," requests per pod, counting the extra pods: (2 + 2) × 2 = 8 by default;",[646,1551,1552],{},"puts the minimum and maximum back. The HPA removes the extra pods ten minutes later.",[585,1554,1555],{},"If the deploy job dies during the surge, the higher minimum stays until the next deploy resets it.",[585,1557,1558],{},"A surge is only useful in scaled mode. With a cache in each pod, every extra pod would warm its own copy.",[638,1560,1562],{"id":1561},"the-redis-watch","The Redis watch",[585,1564,1565,1566,1569,1570,609,1573,744],{},"While php can't reach Redis, purges are lost but still answer ",[844,1567,1568],{},"204",". An edit made then could leave old copies cached, for up to a year in production. ",[588,1571,1572],{},"The Redis watch notices, flushes once Redis is back, and emails",[593,1574,1577],{"href":1575,"rel":1576},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F0d9f9d519b18e2e24bebec06fc2d090b4edd6ee3",[597],[844,1578,1579],{},"0d9f9d5",[585,1581,1582,1583,1586,1587,1590,1591,1594,1595,1597],{},"It's a CronJob in the release (",[844,1584,1585],{},"templates\u002Fredis-watch-cronjob.yaml",") that runs the template's ",[844,1588,1589],{},"app:redis-watch"," command every 2 minutes. It's on in scaled mode unless ",[844,1592,1593],{},"CWA_CI_API_REDIS_WATCH=\"false\"",", and ",[844,1596,1010],{}," changes the schedule. Each run:",[776,1599,1600,1613,1619,1638,1651],{},[646,1601,1602,1605,1606,1609,1610,1612],{},[588,1603,1604],{},"Checks Redis three times, 2 seconds apart."," Redis counts as down only if every check fails. A check fails when the job can't connect to Redis itself, or when php can't: the job requests ",[844,1607,1608],{},"\u002F_api\u002F_\u002Fsite_config_parameters.jsonld"," through the API Service and reads its ",[844,1611,1245],{},". php's view is what matters, because its 100ms timeouts can fail while the job's own 2-second connection works.",[646,1614,1615,1618],{},[588,1616,1617],{},"When Redis is down,"," emails once that the cache store is unreachable. Later runs don't repeat the email. A failed send is tried again next run.",[646,1620,1621,1624,1625,744],{},[588,1622,1623],{},"When Redis is reachable again after an outage,"," flushes it once, and emails that the cache store is back and was flushed. A failed flush keeps the outage open, so the next run tries again. A failed send of the \"back\" email is kept and sent, with the same text, by the next run that reaches Redis, without flushing again (",[601,1626,1627,1628,847,1631],{},"since ",[593,1629,608],{"href":606,"rel":1630},[597],[593,1632,1635],{"href":1633,"rel":1634},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fe0e30c8d1b3fc6554fa813a592b6d46bba932c15",[597],[844,1636,1637],{},"e0e30c8",[646,1639,1640,1650],{},[588,1641,1526,1642,1645,1646,1649],{},[844,1643,1644],{},"provider cloudflare"," in ",[844,1647,1648],{},"CADDY_CACHE_CDN_CONFIG",","," also purges everything in the Cloudflare zone, because the edge may have cached a stale copy from Redis. A failed Cloudflare purge is tried again next run, without flushing Redis again.",[646,1652,1653,1656,1657,744],{},[588,1654,1655],{},"When Redis restarted between two runs,"," only emails. A restart starts empty, so nothing is stale. A restart caused by a deploy sends no email: the job knows the Helm release's revision, and a deploy that changes Redis's spec recreates it under a new revision, so only a restart within one revision emails (",[593,1658,1661],{"href":1659,"rel":1660},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002F230869d",[597],[844,1662,1663],{},"230869d",[585,1665,1666,1667,1670,1671,1675,1676,1678],{},"The email goes to ",[844,1668,1669],{},"MAILER_EMAIL",", the same address as the ",[593,1672,1674],{"href":1673},"\u002Fdeployment\u002Fkubernetes#daily-orphan-scan","daily orphan scan",". With ",[844,1677,1669],{}," empty, no email is sent. The subject starts with the site's name.",[585,1680,1681,1684,1685,1688,1689,847,1692,1695,1696,1699,1700],{},[588,1682,1683],{},"Its state is kept in the database",", because it must outlive Redis: one row in ",[844,1686,1687],{},"redis_watch_state",", created by the template's migrations ",[844,1690,1691],{},"Version20261020162222",[844,1693,1694],{},"Version20261020171138"," (the release revision) and ",[844,1697,1698],{},"Version20261020172842"," (an unsent recovery email). A project that copies the watch needs all three migrations too. A run that starts before the release has migrated exits cleanly with ",[844,1701,1702],{},"The database is not migrated yet; checking next run.",[585,1704,1705],{},"If the job can't reach Redis but php can, the run fails as the job's own fault (its password, or a NetworkPolicy that leaves it out) and isn't treated as an outage.",[580,1707,1709],{"icon":1708},"i-heroicons-information-circle",[585,1710,1711,1714],{},[588,1712,1713],{},"An outage shorter than the schedule, with no restart, can be missed."," The watch sees only what happens during its runs, and a restart only through Redis's run ID. If you suspect one, flush by hand (below).",[1033,1716,1718],{"id":1717},"when-redis-is-unreachable","When Redis is unreachable",[585,1720,1721],{},"The site stays up, uncached and a little slower: about 0.2 seconds a request in the template's tests. The watch usually handles it. If it lasts, or you want to check:",[776,1723,1724,1736,1762,1771,1793],{},[646,1725,1726,1729,1730,1732,1733,1735],{},[588,1727,1728],{},"Notice:"," the watch's email; responses whose ",[844,1731,1245],{}," shows ",[844,1734,1249],{},"; or Redis connection errors in php's log.",[646,1737,1738,1741,1742,1745,1746,1749,1750,1753,1754,1757,1758,1761],{},[588,1739,1740],{},"Confirm:"," check the Redis pod's status and log with ",[844,1743,1744],{},"kubectl get pods"," and ",[844,1747,1748],{},"kubectl logs"," for ",[844,1751,1752],{},"\u003Crelease>-redis",". An ",[844,1755,1756],{},"OOMKilled"," reason in ",[844,1759,1760],{},"kubectl describe pod"," means it was full.",[646,1763,1764,1767,1768,1770],{},[588,1765,1766],{},"Fix:"," restart Redis, or raise ",[844,1769,917],{}," and deploy if it was full.",[646,1772,1773,1776,1777,1780,1781,1785,1786,1792],{},[588,1774,1775],{},"Clean up:"," if the watch hasn't flushed, press ",[588,1778,1779],{},"Purge all cached data"," in the site settings (see ",[593,1782,1784],{"href":1783},"\u002Fnuxt-module\u002Fpage-caching#purging-all-cached-data","Page Caching › Purging All Cached Data","), or run ",[593,1787,1789],{"href":1788},"\u002Fapi\u002Fconsole-commands#purge-http-cache",[844,1790,1791],{},"purge-http-cache"," in a php pod.",[646,1794,1795,1798,1799,1801],{},[588,1796,1797],{},"Check:"," ",[844,1800,1245],{}," no longer shows the error, and an edit shows on the live page.",[638,1803,767],{"id":1804},"what-can-go-wrong",[817,1806,1807,1820],{},[820,1808,1809],{},[823,1810,1811,1814,1817],{},[826,1812,1813],{},"What happens",[826,1815,1816],{},"What visitors see",[826,1818,1819],{},"What to do",[836,1821,1822,1833,1848,1862,1876,1887,1898],{},[823,1823,1824,1827,1830],{},[841,1825,1826],{},"One php pod dies or is drained",[841,1828,1829],{},"Nothing for cached pages. Requests in flight on that pod fail, so a form may need submitting again",[841,1831,1832],{},"Nothing. The other pod serves, and the PDB stops a drain taking both",[823,1834,1835,1838,1845],{},[841,1836,1837],{},"Redis restarts",[841,1839,1840,1841],{},"Nothing stale, because the cache starts empty. Pages are slower until they're cached again, and a busy site may get 503s from ",[593,1842,1844],{"href":1843},"\u002Fdeployment\u002Forigin-protection#load-shedding","load shedding",[841,1846,1847],{},"Nothing. The Redis watch emails",[823,1849,1850,1853,1856],{},[841,1851,1852],{},"Redis is unreachable but running",[841,1854,1855],{},"Requests are uncached and a little slower",[841,1857,1858,1859],{},"The Redis watch flushes once Redis is back and emails. See ",[593,1860,1718],{"href":1861},"#when-redis-is-unreachable",[823,1863,1864,1867,1870],{},[841,1865,1866],{},"Redis is full",[841,1868,1869],{},"Pages work but stop being cached",[841,1871,1872,1873,1875],{},"Raise ",[844,1874,917],{},". Souin's tag index only grows between deploys, and every deploy's flush resets it",[823,1877,1878,1881,1884],{},[841,1879,1880],{},"The Mercure hub restarts",[841,1882,1883],{},"Live updates pause. Browsers reconnect",[841,1885,1886],{},"Nothing. Saves still succeed: the bundle catches and logs a failed publish",[823,1888,1889,1892,1895],{},[841,1890,1891],{},"The database is down",[841,1893,1894],{},"Cached pages work. Logins and edits fail",[841,1896,1897],{},"The same as with one pod",[823,1899,1900,1903,1906],{},[841,1901,1902],{},"Every deploy",[841,1904,1905],{},"The first requests after the flush are cold until the warm finishes",[841,1907,1908],{},"A warm surge shortens it",[585,1910,1911,1914,1915,1918],{},[588,1912,1913],{},"The database is the next ceiling."," Each php pod runs 4 workers (",[844,1916,1917],{},"php.workers","), and each worker can hold a database connection, so pods × 4 must stay well under the database's connection limit. Past that, the database needs more resources or a connection pooler.",[585,1920,1921,1924,1925,1927],{},[588,1922,1923],{},"The HPA is too slow for a sudden spike."," It takes up to a minute to see the CPU, then about half a minute for each pod, and minutes if the cluster needs a new node. For a launch you know about, raise ",[844,1926,876],{}," and deploy beforehand.",[585,1929,1930,1933],{},[588,1931,1932],{},"Identical misses can render once per pod."," Souin merges identical requests for the same uncached item only within one pod, so with 3 pods the same item can render up to 3 times at once.",[638,1935,1937],{"id":1936},"what-it-costs","What it costs",[585,1939,1940],{},"On top of the default single pod, for each scaled release, at the minimum of 2 pods:",[817,1942,1943,1956],{},[820,1944,1945],{},[823,1946,1947,1950,1953],{},[826,1948,1949],{},"Item",[826,1951,1952],{},"CPU requested",[826,1954,1955],{},"Memory requested",[836,1957,1958,1969,1980,1991],{},[823,1959,1960,1963,1966],{},[841,1961,1962],{},"One more php pod",[841,1964,1965],{},"100m",[841,1967,1968],{},"350Mi in production (256Mi on other tracks)",[823,1970,1971,1974,1977],{},[841,1972,1973],{},"Redis, for 256Mi of cache",[841,1975,1976],{},"50m",[841,1978,1979],{},"374Mi",[823,1981,1982,1985,1988],{},[841,1983,1984],{},"Mercure hub",[841,1986,1987],{},"10m",[841,1989,1990],{},"64Mi",[823,1992,1993,1998,2003],{},[841,1994,1995],{},[588,1996,1997],{},"Total",[841,1999,2000],{},[588,2001,2002],{},"160m",[841,2004,2005],{},[588,2006,2007],{},"about 790Mi",[585,2009,2010],{},"The HPA can add a third php pod at peak, and a warm surge adds pods for about ten minutes per deploy. The Redis watch requests 50m and 128Mi for a few seconds every 2 minutes.",[638,2012,2014],{"id":2013},"what-changes-for-your-sites-code","What changes for your site's code",[585,2016,2017],{},"Most of this already matters with one pod, because pods restart:",[643,2019,2020,2037,2053,2059,2065,2075,2081],{},[646,2021,2022,2028,2029,2032,2033,2036],{},[588,2023,2024,2027],{},[844,2025,2026],{},"cache.app"," and your own cache pools"," are per pod. For data every pod must see, use the database (Symfony's ",[844,2030,2031],{},"DoctrineDbalAdapter","). ",[588,2034,2035],{},"Never use the cache's Redis:"," a flush deletes everything in it.",[646,2038,2039,609,2042,847,2045,2048,2049,2052],{},[588,2040,2041],{},"Local files",[844,2043,2044],{},"var\u002F",[844,2046,2047],{},"\u002Ftmp",", a ",[844,2050,2051],{},"local"," Flysystem adapter) are per pod. Use Cloud Storage.",[646,2054,2055,2058],{},[588,2056,2057],{},"Sessions:"," the template's firewalls are stateless. A stateful firewall or flash messages need a session handler in the database.",[646,2060,2061,2064],{},[588,2062,2063],{},"Rate limiters and login throttling"," need a store the pods share, such as the database.",[646,2066,2067,2070,2071,2074],{},[588,2068,2069],{},"Scheduled or one-off work"," belongs in a CronJob or a Job, never in the entrypoint, ",[844,2072,2073],{},"postStart"," or a timer inside php.",[646,2076,2077,2080],{},[588,2078,2079],{},"Anything you added to the entrypoint after migrations"," runs on every pod. Make it safe to run more than once at the same time.",[646,2082,2083,2092],{},[588,2084,2085,2088,2089],{},[844,2086,2087],{},"kubectl exec deploy\u002F…"," in your ",[844,2090,2091],{},"project.sh"," reaches one pod. That's fine for the shared cache or the database, and wrong for anything held in one pod's memory.",[638,2094,2096],{"id":2095},"testing-it","Testing it",[585,2098,1390,2099,2102,2103,2032,2110,2113,2114,2117,2118,2121,2122,2125,2126,1142],{},[593,2100,2101],{"href":563},"browser tests"," can run against scaled mode (",[593,2104,2107],{"href":2105,"rel":2106},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fcommit\u002Fdd7ffe950768ea4e09746b20ef8f6003639ade3e",[597],[844,2108,2109],{},"dd7ffe9",[844,2111,2112],{},"E2E_SCALED=true sh e2e\u002Frun.sh"," adds ",[844,2115,2116],{},"e2e\u002Fcompose.scaled.yaml",": two php replicas behind one ",[844,2119,2120],{},"php"," service, sharing a Redis and the hub, with the chart's store settings. Docker's DNS spreads the browser's and Nuxt's connections across the replicas, and the two replicas starting together also exercise the migration lock. In CI, set ",[844,2123,2124],{},"CWA_CI_E2E_SCALED=\"true\"",". This is how to reproduce something that works on one pod and breaks on two. See ",[593,2127,2129],{"href":2128},"\u002Fdeployment\u002Fbrowser-tests#running-them-locally","Browser Tests › Running them locally",[585,2131,2132],{},"Local development stays on one php pod.",[1033,2134,2136],{"id":2135},"what-has-been-checked-so-far","What has been checked so far",[585,2138,2139,2146,2147,2150,2151,1142],{},[588,2140,2141,2142,2145],{},"The rehearsal on ",[593,2143,598],{"href":595,"rel":2144},[597],", 2026-10-20",", with ",[844,2148,2149],{},"CWA_CI_API_SCALED_PRODUCTION=true",": two to three API pods on different nodes, Redis at 256Mi and the hub pod. The full results, with the commands, are in ",[593,2152,2155],{"href":2153,"rel":2154},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F85#issuecomment-6296742043",[597],"components-web-app #85 › Rehearsal on preview.cwa.rocks",[817,2157,2158,2168],{},[820,2159,2160],{},[823,2161,2162,2165],{},[826,2163,2164],{},"Test",[826,2166,2167],{},"Result",[836,2169,2170,2182,2190,2201,2212,2223],{},[823,2171,2172,2175],{},[841,2173,2174],{},"An API pod deleted under traffic (80 uncached requests, 2 a second)",[841,2176,2177,2178,2181],{},"80 of 80 returned ",[844,2179,2180],{},"200",", the slowest in 0.35 s. The replacement was ready in about 35 s",[823,2183,2184,2187],{},[841,2185,2186],{},"Both API pods evicted back to back, as a node drain does",[841,2188,2189],{},"The first eviction was allowed and the second refused by the PodDisruptionBudget",[823,2191,2192,2195],{},[841,2193,2194],{},"Redis deleted under traffic (100 uncached requests, 2 a second)",[841,2196,2197,2198,2200],{},"100 of 100 returned ",[844,2199,2180],{},", the slowest in 0.65 s. The new Redis was ready in about 44 s, and caching resumed by itself",[823,2202,2203,2206],{},[841,2204,2205],{},"The hub deleted during an editor's save",[841,2207,2208,2209,2211],{},"The save returned ",[844,2210,2180],{},". Once the new hub was up, a subscriber through Cloudflare, the ingress, php and the hub received the next edit",[823,2213,2214,2220],{},[841,2215,2216,2217],{},"A deploy with ",[844,2218,2219],{},"CWA_CI_API_WARM_SURGE=2",[841,2221,2222],{},"4 pods ready in 19 s, then the flush and a warm of 144 + 144 requests with none failed. The minimum went back to 2",[823,2224,2225,2228],{},[841,2226,2227],{},"Load from one machine, about 121 requests a second, cold API",[841,2229,2230,2231,2233],{},"The HPA went from 2 to 3 pods within about 30 s. The origin answered only ",[844,2232,2180],{},"s (API median 35 ms, p95 103 ms)",[585,2235,2236],{},"From the template's notes and changelog:",[643,2238,2239,2248,2254,2259,2267],{},[646,2240,2241,1798,2244,2247],{},[588,2242,2243],{},"Purges across pods:",[844,2245,2246],{},"bin\u002Ftest\u002Fsouin-redis-shared-purge.sh"," runs in the unit-tests job. Two instances on one Redis store 200 pages under a shared tag at the same time, and a purge through one leaves none cached.",[646,2249,2250,2253],{},[588,2251,2252],{},"Redis down"," (locally): requests are served uncached, and caching resumes by itself when Redis is back.",[646,2255,2256,2258],{},[588,2257,1562],{}," (locally, with the Redis container paused): an entry cached before the pause was still a hit afterwards; the watch flushed, and the next request was stored afresh.",[646,2260,2261,2264,2265,1142],{},[588,2262,2263],{},"Mercure"," (locally): an admin's draft update published through php reached a subscriber connected through php. With the hub stopped, saves still returned ",[844,2266,2180],{},[646,2268,2269,2272,2273,2276],{},[588,2270,2271],{},"Deploys on preview.cwa.rocks:"," the first scaled deploys found three problems, all fixed on ",[844,2274,2275],{},"main",". The flush ran in the old, stopping pod instead of Redis; 8 of 27 warm requests failed on stopping pods; and Kubernetes refused a surge minimum (4) above the maximum (3).",[638,2278,2280],{"id":2279},"turning-it-off","Turning it off",[585,2282,2283,2284,2286,2287,663,2289,2291],{},"Set the switch to ",[844,2285,998],{},", or delete it, and deploy. Also unset any ",[844,2288,876],{},[844,2290,880],{}," above 1, or the deploy refuses. Helm removes Redis, the hub, the PDB and the Redis watch. php goes back to one pod with its own in-memory cache and its own hub, and that pod starts with an empty cache and warms it before its probes start. Nothing in Redis needs keeping.",{"title":1031,"searchDepth":2293,"depth":2293,"links":2294},2,[2295,2296,2297,2298,2302,2307,2310,2313,2314,2315,2316,2319],{"id":640,"depth":2293,"text":641},{"id":716,"depth":2293,"text":717},{"id":770,"depth":2293,"text":771},{"id":811,"depth":2293,"text":812,"children":2299},[2300],{"id":1035,"depth":2301,"text":1036},3,{"id":1171,"depth":2293,"text":1172,"children":2303},[2304,2305,2306],{"id":1175,"depth":2301,"text":1176},{"id":1257,"depth":2301,"text":1258},{"id":1333,"depth":2301,"text":1334},{"id":1405,"depth":2293,"text":950,"children":2308},[2309],{"id":1522,"depth":2301,"text":1523},{"id":1561,"depth":2293,"text":1562,"children":2311},[2312],{"id":1717,"depth":2301,"text":1718},{"id":1804,"depth":2293,"text":767},{"id":1936,"depth":2293,"text":1937},{"id":2013,"depth":2293,"text":2014},{"id":2095,"depth":2293,"text":2096,"children":2317},[2318],{"id":2135,"depth":2301,"text":2136},{"id":2279,"depth":2293,"text":2280},"The template's opt-in scaled mode runs the API on two or more php pods, sharing one Redis cache and one Mercure hub, so losing a pod is not an outage. When you need it, how to turn it on, what it costs and what can go wrong.","md",{},true,{"title":526,"description":2320},"95mKruRMWM_UYm1pVf_P1Pnojkhn2zZxwMG9VopDrY8",[2327,2329],{"title":522,"path":523,"stem":524,"description":2328,"children":-1},"Deploying the CWA stack to Kubernetes using Helm — values configuration, secrets management, migration Jobs, and rolling updates.",{"title":530,"path":531,"stem":532,"description":2330,"children":-1},"The template ships full CI\u002FCD for both GitLab CI and GitHub Actions — Docker Buildx builds, tests, per-branch review apps, and staged Kubernetes deployments via Helm.",1792522057296]