[{"data":1,"prerenderedAt":7777},["ShallowReactive",2],{"navigation":3,"\u002Fdeployment\u002Fvariables":604,"\u002Fdeployment\u002Fvariables-surround":7772},[4,24,67,93,236,533],{"title":5,"path":6,"stem":7,"children":8,"collapsed":23},"Getting Started","\u002Fgetting-started","1.getting-started\u002F1.index",[9,11,15,19],{"title":10,"path":6,"stem":7},"Introduction",{"title":12,"path":13,"stem":14},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation",{"title":16,"path":17,"stem":18},"Key Ideas","\u002Fgetting-started\u002Fkey-ideas","1.getting-started\u002F3.key-ideas",{"title":20,"path":21,"stem":22},"Releases","\u002Fgetting-started\u002Freleases","1.getting-started\u002F4.releases",true,{"title":25,"path":26,"stem":27,"children":28,"collapsed":23},"Core Concepts","\u002Fcore-concepts","2.core-concepts\u002F0.index",[29,31,35,39,43,47,51,55,59,63],{"title":30,"path":26,"stem":27},"Overview",{"title":32,"path":33,"stem":34},"How It All Works","\u002Fcore-concepts\u002Farchitecture","2.core-concepts\u002F1.architecture",{"title":36,"path":37,"stem":38},"The Data Model","\u002Fcore-concepts\u002Fthe-data-model","2.core-concepts\u002F2.the-data-model",{"title":40,"path":41,"stem":42},"Layouts & Pages","\u002Fcore-concepts\u002Flayouts-and-pages","2.core-concepts\u002F3.layouts-and-pages",{"title":44,"path":45,"stem":46},"Dynamic Pages","\u002Fcore-concepts\u002Fdynamic-pages","2.core-concepts\u002F4.dynamic-pages",{"title":48,"path":49,"stem":50},"Components","\u002Fcore-concepts\u002Fcomponents","2.core-concepts\u002F5.components",{"title":52,"path":53,"stem":54},"Draft & Publish Workflow","\u002Fcore-concepts\u002Fdraft-and-publish","2.core-concepts\u002F6.draft-and-publish",{"title":56,"path":57,"stem":58},"The Admin Panel","\u002Fcore-concepts\u002Fadmin-panel","2.core-concepts\u002F7.admin-panel",{"title":60,"path":61,"stem":62},"Caching","\u002Fcore-concepts\u002Fcaching","2.core-concepts\u002F8.caching",{"title":64,"path":65,"stem":66},"Glossary","\u002Fcore-concepts\u002Fglossary","2.core-concepts\u002F9.glossary",{"title":68,"path":69,"stem":70,"children":71,"collapsed":23},"Guides","\u002Fguides","3.guides\u002F0.index",[72,73,77,81,85,89],{"title":30,"path":69,"stem":70},{"title":74,"path":75,"stem":76},"Your First Layout","\u002Fguides\u002Fyour-first-layout","3.guides\u002F1.your-first-layout",{"title":78,"path":79,"stem":80},"Your First Page Template","\u002Fguides\u002Fyour-first-page-template","3.guides\u002F2.your-first-page-template",{"title":82,"path":83,"stem":84},"Your First Component","\u002Fguides\u002Fyour-first-component","3.guides\u002F3.your-first-component",{"title":86,"path":87,"stem":88},"Alternative UI Variants","\u002Fguides\u002Falternative-ui-variants","3.guides\u002F4.alternative-ui-variants",{"title":90,"path":91,"stem":92},"Your First Dynamic Page","\u002Fguides\u002Fyour-first-dynamic-page","3.guides\u002F5.your-first-dynamic-page",{"title":94,"path":95,"stem":96,"children":97,"collapsed":23},"API Bundle (Back-End)","\u002Fapi","4.api\u002F0.index",[98,99,103,107,129,175,179,183,212,216,220,224,228,232],{"title":30,"path":95,"stem":96},{"title":100,"path":101,"stem":102},"Bundle Setup","\u002Fapi\u002Fbundle-setup","4.api\u002F02.bundle-setup",{"title":104,"path":105,"stem":106},"Configuration Reference","\u002Fapi\u002Fconfiguration","4.api\u002F03.configuration",{"title":108,"path":109,"stem":110,"children":111,"page":128},"Users","\u002Fapi\u002Fusers","4.api\u002F04.users",[112,116,120,124],{"title":113,"path":114,"stem":115},"Authentication","\u002Fapi\u002Fusers\u002Fauthentication","4.api\u002F04.users\u002F1.authentication",{"title":117,"path":118,"stem":119},"User Emails","\u002Fapi\u002Fusers\u002Fuser-emails","4.api\u002F04.users\u002F2.user-emails",{"title":121,"path":122,"stem":123},"Access Control","\u002Fapi\u002Fusers\u002Faccess-control","4.api\u002F04.users\u002F3.access-control",{"title":125,"path":126,"stem":127},"Sites with Non-Admin Users","\u002Fapi\u002Fusers\u002Fnon-admin-users","4.api\u002F04.users\u002F4.non-admin-users",false,{"title":48,"path":130,"stem":131,"children":132,"page":128},"\u002Fapi\u002Fcomponents","4.api\u002F05.components",[133,137,158,171],{"title":134,"path":135,"stem":136},"Creating Components","\u002Fapi\u002Fcomponents\u002Fcreating-components","4.api\u002F05.components\u002F1.creating-components",{"title":138,"path":139,"stem":140,"children":141,"page":128},"Annotations","\u002Fapi\u002Fcomponents\u002Fannotations","4.api\u002F05.components\u002F2.annotations",[142,146,150,154],{"title":143,"path":144,"stem":145},"Publishable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fpublishable","4.api\u002F05.components\u002F2.annotations\u002F1.publishable",{"title":147,"path":148,"stem":149},"Uploadable","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable","4.api\u002F05.components\u002F2.annotations\u002F2.uploadable",{"title":151,"path":152,"stem":153},"Timestamped","\u002Fapi\u002Fcomponents\u002Fannotations\u002Ftimestamped","4.api\u002F05.components\u002F2.annotations\u002F3.timestamped",{"title":155,"path":156,"stem":157},"Explicit Allow Only","\u002Fapi\u002Fcomponents\u002Fannotations\u002Fexplicit-allow-only","4.api\u002F05.components\u002F2.annotations\u002F4.explicit-allow-only",{"title":159,"path":160,"stem":161,"children":162,"page":128},"Built Ins","\u002Fapi\u002Fcomponents\u002Fbuilt-ins","4.api\u002F05.components\u002F3.built-ins",[163,167],{"title":164,"path":165,"stem":166},"Collection Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fcollection-component","4.api\u002F05.components\u002F3.built-ins\u002F1.collection-component",{"title":168,"path":169,"stem":170},"Form Component","\u002Fapi\u002Fcomponents\u002Fbuilt-ins\u002Fform-component","4.api\u002F05.components\u002F3.built-ins\u002F2.form-component",{"title":172,"path":173,"stem":174},"Groups & Positions","\u002Fapi\u002Fcomponents\u002Fcomponent-groups","4.api\u002F05.components\u002F4.component-groups",{"title":176,"path":177,"stem":178},"Page Data & Nested Pages","\u002Fapi\u002Fdynamic-pages","4.api\u002F06.dynamic-pages",{"title":180,"path":181,"stem":182},"Routes","\u002Fapi\u002Froutes","4.api\u002F07.routes",{"title":184,"path":185,"stem":186,"children":187,"page":128},"Data Fixtures","\u002Fapi\u002Fdata-fixtures","4.api\u002F08.data-fixtures",[188,192,196,200,204,208],{"title":189,"path":190,"stem":191},"Getting Started with Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgetting-started","4.api\u002F08.data-fixtures\u002F1.getting-started",{"title":193,"path":194,"stem":195},"Writing a Scaffold","\u002Fapi\u002Fdata-fixtures\u002Fwriting-a-scaffold","4.api\u002F08.data-fixtures\u002F2.writing-a-scaffold",{"title":197,"path":198,"stem":199},"Builder Reference","\u002Fapi\u002Fdata-fixtures\u002Fbuilder-reference","4.api\u002F08.data-fixtures\u002F3.builder-reference",{"title":201,"path":202,"stem":203},"Seeding Content","\u002Fapi\u002Fdata-fixtures\u002Fseeding-content","4.api\u002F08.data-fixtures\u002F4.seeding-content",{"title":205,"path":206,"stem":207},"Appending & Multiple Scaffolds","\u002Fapi\u002Fdata-fixtures\u002Fappending","4.api\u002F08.data-fixtures\u002F5.appending",{"title":209,"path":210,"stem":211},"Generating Fixtures","\u002Fapi\u002Fdata-fixtures\u002Fgenerating-fixtures","4.api\u002F08.data-fixtures\u002F6.generating-fixtures",{"title":213,"path":214,"stem":215},"Real-Time (Mercure)","\u002Fapi\u002Freal-time","4.api\u002F09.real-time",{"title":217,"path":218,"stem":219},"Versions & Conflicts","\u002Fapi\u002Fversions-and-conflicts","4.api\u002F09.versions-and-conflicts",{"title":221,"path":222,"stem":223},"HTTP Caching","\u002Fapi\u002Fhttp-caching","4.api\u002F10.http-caching",{"title":225,"path":226,"stem":227},"Orphaned Data","\u002Fapi\u002Forphaned-data","4.api\u002F11.orphaned-data",{"title":229,"path":230,"stem":231},"Console Commands","\u002Fapi\u002Fconsole-commands","4.api\u002F12.console-commands",{"title":233,"path":234,"stem":235},"Debugging & Errors","\u002Fapi\u002Fdebugging","4.api\u002F13.debugging",{"title":237,"path":238,"stem":239,"children":240,"collapsed":23},"Nuxt Module (Front-End)","\u002Fnuxt-module","5.nuxt-module\u002F0.index",[241,242,246,259,279,308,329,460,494,497,525,529],{"title":30,"path":238,"stem":239},{"title":243,"path":244,"stem":245},"Module Setup","\u002Fnuxt-module\u002Fmodule-setup","5.nuxt-module\u002F01.module-setup",{"title":247,"path":248,"stem":249,"children":250,"page":128},"Configuration","\u002Fnuxt-module\u002Fconfiguration","5.nuxt-module\u002F02.configuration",[251,255],{"title":252,"path":253,"stem":254},"Nuxt Config","\u002Fnuxt-module\u002Fconfiguration\u002Fnuxt-config","5.nuxt-module\u002F02.configuration\u002F1.nuxt-config",{"title":256,"path":257,"stem":258},"Site Config & SEO","\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo","5.nuxt-module\u002F02.configuration\u002F2.site-config-and-seo",{"title":260,"path":261,"stem":262,"children":263,"page":128},"Building Your Ui","\u002Fnuxt-module\u002Fbuilding-your-ui","5.nuxt-module\u002F03.building-your-ui",[264,268,272,275],{"title":265,"path":266,"stem":267},"Layouts","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-layouts","5.nuxt-module\u002F03.building-your-ui\u002F1.creating-layouts",{"title":269,"path":270,"stem":271},"Page Templates","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-page-templates","5.nuxt-module\u002F03.building-your-ui\u002F2.creating-page-templates",{"title":48,"path":273,"stem":274},"\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcreating-components","5.nuxt-module\u002F03.building-your-ui\u002F3.creating-components",{"title":276,"path":277,"stem":278},"CLI Generator","\u002Fnuxt-module\u002Fbuilding-your-ui\u002Fcwa-cli","5.nuxt-module\u002F03.building-your-ui\u002F4.cwa-cli",{"title":280,"path":281,"stem":282,"children":283,"page":128},"Component Helpers","\u002Fnuxt-module\u002Fcomponent-helpers","5.nuxt-module\u002F04.component-helpers",[284,288,292,296,300,304],{"title":285,"path":286,"stem":287},"Images & Media","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fimages-and-uploads","5.nuxt-module\u002F04.component-helpers\u002F1.images-and-uploads",{"title":289,"path":290,"stem":291},"Collections & Pagination","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fcollections-and-pagination","5.nuxt-module\u002F04.component-helpers\u002F2.collections-and-pagination",{"title":293,"path":294,"stem":295},"HTML Content","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fhtml-content","5.nuxt-module\u002F04.component-helpers\u002F3.html-content",{"title":297,"path":298,"stem":299},"Rich-Text Editor","\u002Fnuxt-module\u002Fcomponent-helpers\u002Frich-text-editor","5.nuxt-module\u002F04.component-helpers\u002F4.rich-text-editor",{"title":301,"path":302,"stem":303},"Real-Time Updates","\u002Fnuxt-module\u002Fcomponent-helpers\u002Freal-time-updates","5.nuxt-module\u002F04.component-helpers\u002F5.real-time-updates",{"title":305,"path":306,"stem":307},"Forms","\u002Fnuxt-module\u002Fcomponent-helpers\u002Fforms","5.nuxt-module\u002F04.component-helpers\u002F6.forms",{"title":309,"path":310,"stem":311,"children":312,"page":128},"Cwa Components","\u002Fnuxt-module\u002Fcwa-components","5.nuxt-module\u002F05.cwa-components",[313,317,321,325],{"title":314,"path":315,"stem":316},"\u003CCwaComponentGroup \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-component-group","5.nuxt-module\u002F05.cwa-components\u002F1.cwa-component-group",{"title":318,"path":319,"stem":320},"\u003CCwaPage \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-page","5.nuxt-module\u002F05.cwa-components\u002F2.cwa-page",{"title":322,"path":323,"stem":324},"\u003CCwaLink \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-link","5.nuxt-module\u002F05.cwa-components\u002F3.cwa-link",{"title":326,"path":327,"stem":328},"\u003CCwaDefaultLayout \u002F>","\u002Fnuxt-module\u002Fcwa-components\u002Fcwa-default-layout","5.nuxt-module\u002F05.cwa-components\u002F5.cwa-default-layout",{"title":330,"path":331,"stem":332,"children":333,"page":128},"Composables","\u002Fnuxt-module\u002Fcomposables","5.nuxt-module\u002F06.composables",[334,337,345,370,390,415,435],{"title":30,"path":335,"stem":336},"\u002Fnuxt-module\u002Fcomposables\u002Foverview","5.nuxt-module\u002F06.composables\u002F0.overview",{"title":338,"path":339,"stem":340,"children":341,"page":128},"Layout","\u002Fnuxt-module\u002Fcomposables\u002Flayout","5.nuxt-module\u002F06.composables\u002F1.layout",[342],{"title":338,"path":343,"stem":344},"\u002Fnuxt-module\u002Fcomposables\u002Flayout\u002Fuse-cwa-layout","5.nuxt-module\u002F06.composables\u002F1.layout\u002F1.use-cwa-layout",{"title":346,"path":347,"stem":348,"children":349,"page":128},"Component","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent","5.nuxt-module\u002F06.composables\u002F2.component",[350,354,358,362,366],{"title":351,"path":352,"stem":353},"Component (recommended)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-component","5.nuxt-module\u002F06.composables\u002F2.component\u002F1.use-cwa-component",{"title":355,"path":356,"stem":357},"Resource","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F2.use-cwa-resource",{"title":359,"path":360,"stem":361},"File Field","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-file-field","5.nuxt-module\u002F06.composables\u002F2.component\u002F3.use-cwa-file-field",{"title":363,"path":364,"stem":365},"Collection Pagination","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-pagination","5.nuxt-module\u002F06.composables\u002F2.component\u002F4.use-cwa-collection-pagination",{"title":367,"path":368,"stem":369},"Collection Resource (deprecated)","\u002Fnuxt-module\u002Fcomposables\u002Fcomponent\u002Fuse-cwa-collection-resource","5.nuxt-module\u002F06.composables\u002F2.component\u002F9.use-cwa-collection-resource",{"title":371,"path":372,"stem":373,"children":374,"page":128},"Form","\u002Fnuxt-module\u002Fcomposables\u002Fform","5.nuxt-module\u002F06.composables\u002F3.form",[375,378,382,386],{"title":371,"path":376,"stem":377},"\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form","5.nuxt-module\u002F06.composables\u002F3.form\u002F1.use-cwa-form",{"title":379,"path":380,"stem":381},"Form Input","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-input","5.nuxt-module\u002F06.composables\u002F3.form\u002F2.use-cwa-form-input",{"title":383,"path":384,"stem":385},"Form Repeated","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-repeated","5.nuxt-module\u002F06.composables\u002F3.form\u002F3.use-cwa-form-repeated",{"title":387,"path":388,"stem":389},"Form Collection","\u002Fnuxt-module\u002Fcomposables\u002Fform\u002Fuse-cwa-form-collection","5.nuxt-module\u002F06.composables\u002F3.form\u002F4.use-cwa-form-collection",{"title":391,"path":392,"stem":393,"children":394,"page":128},"Admin Manager","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager","5.nuxt-module\u002F06.composables\u002F4.admin-manager",[395,399,403,407,411],{"title":396,"path":397,"stem":398},"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":400,"path":401,"stem":402},"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":404,"path":405,"stem":406},"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":408,"path":409,"stem":410},"Remote Changes","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fon-remote-change","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F4.on-remote-change",{"title":412,"path":413,"stem":414},"Real-Time Events","\u002Fnuxt-module\u002Fcomposables\u002Fadmin-manager\u002Fuse-cwa-realtime","5.nuxt-module\u002F06.composables\u002F4.admin-manager\u002F5.use-cwa-realtime",{"title":416,"path":417,"stem":418,"children":419,"page":128},"Utilities","\u002Fnuxt-module\u002Fcomposables\u002Futilities","5.nuxt-module\u002F06.composables\u002F5.utilities",[420,424,428,432],{"title":421,"path":422,"stem":423},"Resource Endpoint","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-endpoint","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F1.use-cwa-resource-endpoint",{"title":425,"path":426,"stem":427},"Query Model","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-query-bound-model","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F2.use-query-bound-model",{"title":429,"path":430,"stem":431},"Resource Route","\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-cwa-resource-route","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F3.use-cwa-resource-route",{"title":293,"path":433,"stem":434},"\u002Fnuxt-module\u002Fcomposables\u002Futilities\u002Fuse-html-content","5.nuxt-module\u002F06.composables\u002F5.utilities\u002F4.use-html-content",{"title":436,"path":437,"stem":438,"children":439,"page":128},"Auth","\u002Fnuxt-module\u002Fcomposables\u002Fauth","5.nuxt-module\u002F06.composables\u002F6.auth",[440,444,448,452,456],{"title":441,"path":442,"stem":443},"Login","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-login","5.nuxt-module\u002F06.composables\u002F6.auth\u002F1.use-login",{"title":445,"path":446,"stem":447},"Forgot Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-forgot-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F2.use-forgot-password",{"title":449,"path":450,"stem":451},"Reset Password","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-reset-password","5.nuxt-module\u002F06.composables\u002F6.auth\u002F3.use-reset-password",{"title":453,"path":454,"stem":455},"Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F4.use-verify-email",{"title":457,"path":458,"stem":459},"Resend Verify Email","\u002Fnuxt-module\u002Fcomposables\u002Fauth\u002Fuse-resend-verify-email","5.nuxt-module\u002F06.composables\u002F6.auth\u002F5.use-resend-verify-email",{"title":461,"path":462,"stem":463,"children":464,"page":128},"Cwa Api","\u002Fnuxt-module\u002Fcwa-api","5.nuxt-module\u002F07.cwa-api",[465,468,472,476,479,482,486,490],{"title":30,"path":466,"stem":467},"\u002Fnuxt-module\u002Fcwa-api\u002Foverview","5.nuxt-module\u002F07.cwa-api\u002F1.overview",{"title":469,"path":470,"stem":471},"Resources","\u002Fnuxt-module\u002Fcwa-api\u002Fresources","5.nuxt-module\u002F07.cwa-api\u002F2.resources",{"title":473,"path":474,"stem":475},"Resources Manager","\u002Fnuxt-module\u002Fcwa-api\u002Fresources-manager","5.nuxt-module\u002F07.cwa-api\u002F3.resources-manager",{"title":436,"path":477,"stem":478},"\u002Fnuxt-module\u002Fcwa-api\u002Fauth","5.nuxt-module\u002F07.cwa-api\u002F4.auth",{"title":305,"path":480,"stem":481},"\u002Fnuxt-module\u002Fcwa-api\u002Fforms","5.nuxt-module\u002F07.cwa-api\u002F5.forms",{"title":483,"path":484,"stem":485},"Admin","\u002Fnuxt-module\u002Fcwa-api\u002Fadmin","5.nuxt-module\u002F07.cwa-api\u002F6.admin",{"title":487,"path":488,"stem":489},"Site Config","\u002Fnuxt-module\u002Fcwa-api\u002Fsite-config","5.nuxt-module\u002F07.cwa-api\u002F7.site-config",{"title":491,"path":492,"stem":493},"Orphaned Resources","\u002Fnuxt-module\u002Fcwa-api\u002Forphaned-resources","5.nuxt-module\u002F07.cwa-api\u002F8.orphaned-resources",{"title":113,"path":495,"stem":496},"\u002Fnuxt-module\u002Fauthentication","5.nuxt-module\u002F08.authentication",{"title":498,"path":499,"stem":500,"children":501,"page":128},"Cwa Layer","\u002Fnuxt-module\u002Fcwa-layer","5.nuxt-module\u002F9.cwa-layer",[502,505,509,513,517,521],{"title":30,"path":503,"stem":504},"\u002Fnuxt-module\u002Fcwa-layer\u002Foverview","5.nuxt-module\u002F9.cwa-layer\u002F1.overview",{"title":506,"path":507,"stem":508},"Auth Pages","\u002Fnuxt-module\u002Fcwa-layer\u002Fauth-pages","5.nuxt-module\u002F9.cwa-layer\u002F2.auth-pages",{"title":510,"path":511,"stem":512},"Admin Panel","\u002Fnuxt-module\u002Fcwa-layer\u002Fadmin-panel","5.nuxt-module\u002F9.cwa-layer\u002F3.admin-panel",{"title":514,"path":515,"stem":516},"Styling","\u002Fnuxt-module\u002Fcwa-layer\u002Fstyling","5.nuxt-module\u002F9.cwa-layer\u002F4.styling",{"title":518,"path":519,"stem":520},"Date Pickers","\u002Fnuxt-module\u002Fcwa-layer\u002Fdate-pickers","5.nuxt-module\u002F9.cwa-layer\u002F5.date-pickers",{"title":522,"path":523,"stem":524},"Maintenance","\u002Fnuxt-module\u002Fcwa-layer\u002Fmaintenance","5.nuxt-module\u002F9.cwa-layer\u002F6.maintenance",{"title":526,"path":527,"stem":528},"PWA & Offline","\u002Fnuxt-module\u002Fpwa","5.nuxt-module\u002F91.pwa",{"title":530,"path":531,"stem":532},"Page Caching","\u002Fnuxt-module\u002Fpage-caching","5.nuxt-module\u002F92.page-caching",{"title":534,"path":535,"stem":536,"children":537,"collapsed":23},"Deployment & Operations","\u002Fdeployment","6.deployment\u002F0.index",[538,540,544,548,552,556,560,564,568,572,576,580,584,588,592,596,600],{"title":539,"path":535,"stem":536},"Deploying a CWA Site",{"title":541,"path":542,"stem":543},"Docker","\u002Fdeployment\u002Fdocker","6.deployment\u002F1.docker",{"title":545,"path":546,"stem":547},"Deploy to One Server","\u002Fdeployment\u002Fone-server","6.deployment\u002F1.one-server",{"title":549,"path":550,"stem":551},"Kubernetes & Helm","\u002Fdeployment\u002Fkubernetes","6.deployment\u002F2.kubernetes",{"title":553,"path":554,"stem":555},"Moving From One Server to Kubernetes","\u002Fdeployment\u002Fmoving-to-kubernetes","6.deployment\u002F2.moving-to-kubernetes",{"title":557,"path":558,"stem":559},"Scaled Mode","\u002Fdeployment\u002Fscaled-mode","6.deployment\u002F2.scaled-mode",{"title":561,"path":562,"stem":563},"CI\u002FCD","\u002Fdeployment\u002Fci-cd","6.deployment\u002F3.ci-cd",{"title":565,"path":566,"stem":567},"Migrations and Backups","\u002Fdeployment\u002Fmigrations-and-backups","6.deployment\u002F3.migrations-and-backups",{"title":569,"path":570,"stem":571},"CI\u002FCD Variables","\u002Fdeployment\u002Fvariables","6.deployment\u002F4.variables",{"title":573,"path":574,"stem":575},"Cloudflare","\u002Fdeployment\u002Fcloudflare","6.deployment\u002F5.cloudflare",{"title":577,"path":578,"stem":579},"Origin Protection","\u002Fdeployment\u002Forigin-protection","6.deployment\u002F5.origin-protection",{"title":581,"path":582,"stem":583},"Load Testing","\u002Fdeployment\u002Fload-testing","6.deployment\u002F6.load-testing",{"title":585,"path":586,"stem":587},"Performance Audit","\u002Fdeployment\u002Fperformance-audit","6.deployment\u002F6.performance-audit",{"title":589,"path":590,"stem":591},"Reading Lighthouse Scores","\u002Fdeployment\u002Freading-lighthouse-scores","6.deployment\u002F6.reading-lighthouse-scores",{"title":593,"path":594,"stem":595},"Browser Tests","\u002Fdeployment\u002Fbrowser-tests","6.deployment\u002F7.browser-tests",{"title":597,"path":598,"stem":599},"Faster Functional Tests","\u002Fdeployment\u002Ffaster-functional-tests","6.deployment\u002F7.faster-functional-tests",{"title":601,"path":602,"stem":603},"Troubleshooting","\u002Fdeployment\u002Ftroubleshooting","6.deployment\u002F7.troubleshooting",{"id":605,"title":569,"badge":606,"body":607,"description":7767,"extension":7768,"links":606,"meta":7769,"navigation":23,"path":570,"seo":7770,"stem":571,"__hash__":7771},"docs\u002F6.deployment\u002F4.variables.md",null,{"type":608,"value":609,"toc":7727},"minimark",[610,625,644,649,674,677,832,837,841,844,934,952,955,1247,1271,1284,1287,1427,1444,1455,1470,1484,1487,1676,1685,1712,1741,1745,1763,1773,1794,1815,1818,1983,1996,1999,2046,2065,2082,2093,2108,2111,2124,2148,2200,2215,2223,2227,2268,2276,2429,2451,2455,2461,2465,2489,2509,2512,2554,2568,2571,2871,2891,2908,2915,2928,2952,2955,2972,2979,3060,3063,3084,3088,3108,3436,3440,3473,3496,3500,3507,3583,3602,3606,3616,3620,4132,4146,4188,4191,4194,4310,4313,4319,4325,4352,4372,4394,4405,4409,4601,4616,4619,4625,4629,4643,4724,4727,4748,4769,4773,4783,4815,5084,5092,5120,5134,5138,5149,5246,5256,5273,5279,5289,5293,5299,5453,5479,5496,5506,5523,5527,5539,5705,5711,5715,5722,5851,5862,5868,5872,5878,6141,6145,6500,6503,6510,6597,6617,6636,6640,6649,6830,6839,6842,6892,6991,7009,7034,7048,7062,7065,7080,7086,7095,7141,7152,7276,7292,7316,7336,7363,7367,7396,7399,7419,7423,7435,7487,7509,7523,7527,7544,7709,7723],[611,612,613,614,618,619,624],"p",{},"The template's pipeline is configured with CI\u002FCD variables. Set them in ",[615,616,617],"strong",{},"Settings → CI\u002FCD → Variables"," in GitLab. On GitHub, set them as Actions variables, except the ones the workflows map from secrets (see ",[620,621,623],"a",{"href":622},"\u002Fdeployment\u002Fci-cd#github-actions","CI\u002FCD › GitHub Actions",").",[611,626,627,628,632,633,636,637,639,640,643],{},"If you deploy with the template's pipeline, you only set variables. ",[629,630,631],"code",{},"bin\u002Fdevops\u002Fk8s.sh"," turns them into the chart's values and runs Helm. The ",[615,634,635],{},"Helm value"," column below says where each one ends up, so you can read it alongside ",[620,638,549],{"href":550},". A ",[629,641,642],{},"—"," means the variable steers the pipeline and sets no chart value.",[645,646,648],"h2",{"id":647},"minimum-for-a-new-site","Minimum for a new site",[611,650,651,652,655,656,655,659,655,662,665,666,669,670,673],{},"The deploy works out the settings that would otherwise be the same on every site: ",[629,653,654],{},"CWA_CI_INGRESS_ENABLED",", ",[629,657,658],{},"CORS_ALLOW_ORIGIN",[629,660,661],{},"TRUSTED_HOSTS",[629,663,664],{},"MERCURE_CORS_ORIGIN"," and ",[629,667,668],{},"DATABASE_SSL_MODE",". A variable you set still wins. The host-based defaults use the deploy's own hostname, plus ",[629,671,672],{},"KUBE_INGRESS_ALIAS_DOMAINS"," in production, so review apps and staging get their own instead of production's.",[611,675,676],{},"So a new site sets only what is its own:",[678,679,682,714,725,745,772,786,802,814],"ul",{"className":680},[681],"contains-task-list",[683,684,687,691,692,691,695,698,699,665,702,704,705,708,709,713],"li",{"className":685},[686],"task-list-item",[688,689],"input",{"disabled":23,"type":690},"checkbox"," ",[615,693,694],{},"The cluster connection:",[629,696,697],{},"KUBE_CONTEXT"," (GitLab agent context), or on GitHub the ",[629,700,701],{},"KUBECONFIG",[629,703,697],{}," secrets and the ",[629,706,707],{},"KUBE_NAMESPACE_PRODUCTION"," secret. See ",[620,710,712],{"href":711},"#kubernetes-and-hostnames","Kubernetes and hostnames",".",[683,715,717,691,719,691,722,713],{"className":716},[686],[688,718],{"disabled":23,"type":690},[615,720,721],{},"The domain:",[629,723,724],{},"KUBE_INGRESS_BASE_DOMAIN",[683,726,728,691,730,691,733,736,737,740,741,713],{"className":727},[686],[688,729],{"disabled":23,"type":690},[615,731,732],{},"The database:",[629,734,735],{},"DATABASE_URL"," with ",[629,738,739],{},"CWA_CI_POSTGRES_ENABLED=\"false\"",". See ",[620,742,744],{"href":743},"#database","Database",[683,746,748,691,750,691,753,655,756,759,760,763,764,767,768,713],{"className":747},[686],[688,749],{"disabled":23,"type":690},[615,751,752],{},"The media bucket:",[629,754,755],{},"GCLOUD_JSON",[629,757,758],{},"GCLOUD_BUCKET"," and optionally ",[629,761,762],{},"GCLOUD_PUBLIC_URL",", plus ",[629,765,766],{},"GCLOUD_PATH_PREFIX"," if other apps use the bucket. See ",[620,769,771],{"href":770},"#media-storage","Media storage",[683,773,775,691,777,691,780,665,783,713],{"className":774},[686],[688,776],{"disabled":23,"type":690},[615,778,779],{},"The mailer:",[629,781,782],{},"MAILER_DSN",[629,784,785],{},"MAILER_EMAIL",[683,787,789,691,791,794,795,740,798,713],{"className":788},[686],[688,790],{"disabled":23,"type":690},[615,792,793],{},"The first admin:"," at least ",[629,796,797],{},"ADMIN_PASSWORD",[620,799,801],{"href":800},"#application","Application",[683,803,805,691,807,691,810,813],{"className":804},[686],[688,806],{"disabled":23,"type":690},[615,808,809],{},"A trusted certificate:",[629,811,812],{},"CWA_CI_CLUSTER_ISSUER=letsencrypt-prod",", once production's domain points at the cluster.",[683,815,817,819,820,824,825,655,828,831],{"className":816},[686],[688,818],{"disabled":23,"type":690}," Recommended for production: the ",[620,821,823],{"href":822},"#secrets","secrets"," (",[629,826,827],{},"APP_SECRET",[629,829,830],{},"MERCURE_JWT_SECRET"," and the JWT key pair). They're optional: a deploy keeps the release's live values, and generates them only for a new release.",[611,833,834,836],{},[620,835,539],{"href":535}," walks through these in order.",[645,838,840],{"id":839},"naming-rules","Naming rules",[611,842,843],{},"The variables follow three naming rules:",[678,845,846,865,882],{},[683,847,848,853,854,857,858,861,862,713],{},[615,849,850],{},[629,851,852],{},"CWA_CI_*"," are the template's own pipeline and deploy switches, such as ",[629,855,856],{},"CWA_CI_STAGING_ENABLED"," or ",[629,859,860],{},"CWA_CI_PWA_AUTOSCALE_MIN",". The sizing of the php pod is ",[629,863,864],{},"CWA_CI_API_*",[683,866,867,665,872,877,878,713],{},[615,868,869],{},[629,870,871],{},"CWA_API_*",[615,873,874],{},[629,875,876],{},"NUXT_*"," are settings the containers read at runtime, passed through by prefix. See ",[620,879,881],{"href":880},"#site-settings","Site settings",[683,883,884,885,655,887,655,890,655,892,655,895,655,897,655,900,655,903,655,906,655,908,655,910,655,912,655,914,655,917,655,920,655,923,926,927,665,930,933],{},"Site values, secrets and names set by the platform keep their own names: ",[629,886,735],{},[629,888,889],{},"POSTGRES_*",[629,891,827],{},[629,893,894],{},"JWT_*",[629,896,830],{},[629,898,899],{},"MAILER_*",[629,901,902],{},"GCLOUD_*",[629,904,905],{},"ADMIN_*",[629,907,658],{},[629,909,661],{},[629,911,664],{},[629,913,668],{},[629,915,916],{},"CADDY_CACHE_CDN_CONFIG",[629,918,919],{},"GITHUB_TOKEN",[629,921,922],{},"KUBERNETES_VERSION",[629,924,925],{},"HELM_VERSION",", and the ",[629,928,929],{},"KUBE_*",[629,931,932],{},"CI_*"," variables.",[935,936,938],"callout",{"icon":937},"i-heroicons-information-circle",[611,939,940,943,944,947,948,951],{},[615,941,942],{},"GitLab environment scopes:"," the staging job runs under ",[629,945,946],{},"environment: production",", so a variable scoped to ",[629,949,950],{},"production"," reaches staging too, as well as canary and production.",[645,953,712],{"id":954},"kubernetes-and-hostnames",[956,957,958,976],"table",{},[959,960,961],"thead",{},[962,963,964,968,971,973],"tr",{},[965,966,967],"th",{},"Variable",[965,969,970],{},"Default",[965,972,635],{},[965,974,975],{},"Notes",[977,978,979,1003,1022,1055,1080,1103,1128,1148,1165,1188,1215,1231],"tbody",{},[962,980,981,986,991,993],{},[982,983,984],"td",{},[629,985,697],{},[982,987,988],{},[615,989,990],{},"required",[982,992,642],{},[982,994,995,996,999,1000,1002],{},"GitLab agent context, e.g. ",[629,997,998],{},"my-group\u002Fmy-project:my-agent",". On GitHub it's a secret, used with the ",[629,1001,701],{}," secret",[962,1004,1005,1010,1015,1017],{},[982,1006,1007],{},[629,1008,1009],{},"KUBE_NAMESPACE",[982,1011,1012],{},[629,1013,1014],{},"$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG",[982,1016,642],{},[982,1018,1019,1020,1002],{},"The pre-created namespace for this environment. On GitHub, staging and production use the ",[629,1021,707],{},[962,1023,1024,1028,1032,1041],{},[982,1025,1026],{},[629,1027,724],{},[982,1029,1030],{},[615,1031,990],{},[982,1033,1034,1037,1038],{},[629,1035,1036],{},"ingress.hosts",", through ",[629,1039,1040],{},"DOMAIN",[982,1042,1043,1044,1047,1048,1051,1052],{},"Base domain for ingress URLs, e.g. ",[629,1045,1046],{},"k8s.example.com",". Production is served at it, staging at ",[629,1049,1050],{},"staging.\u003Cit>"," and review apps at ",[629,1053,1054],{},"\u003Cbranch>-review.\u003Cit>",[962,1056,1057,1062,1065,1071],{},[982,1058,1059],{},[629,1060,1061],{},"CI_ENVIRONMENT_URL",[982,1063,1064],{},"set by GitLab",[982,1066,1067,1037,1069],{},[629,1068,1036],{},[629,1070,1040],{},[982,1072,1073,1074,1076,1077,1079],{},"Full URL of this environment. ",[629,1075,1040],{}," is its host. Required unless ",[629,1078,1040],{}," is set; without either, the deploy fails",[962,1081,1082,1087,1092,1100],{},[982,1083,1084],{},[629,1085,1086],{},"CWA_CI_CLUSTER_ISSUER",[982,1088,1089],{},[629,1090,1091],{},"letsencrypt-staging",[982,1093,1094,691,1097],{},[629,1095,1096],{},"ingress.annotations",[629,1098,1099],{},"cert-manager.io\u002Fcluster-issuer",[982,1101,1102],{},"The cert-manager ClusterIssuer. See below",[962,1104,1105,1109,1111,1121],{},[982,1106,1107],{},[629,1108,672],{},[982,1110,642],{},[982,1112,1113,691,1115,655,1118],{},[629,1114,1096],{},[629,1116,1117],{},"server-alias",[629,1119,1120],{},"ingress.tls",[982,1122,1123,1124],{},"Comma-separated extra domains for production. Production's certificate covers them. See ",[620,1125,1127],{"href":1126},"\u002Fdeployment\u002Fci-cd#changing-a-live-sites-hostnames","Changing a live site's hostnames",[962,1129,1130,1135,1140,1145],{},[982,1131,1132],{},[629,1133,1134],{},"CWA_CI_TLS_SECRET_NAME",[982,1136,1137],{},[629,1138,1139],{},"letsencrypt-cert",[982,1141,1142],{},[629,1143,1144],{},"ingress.tls[].secretName",[982,1146,1147],{},"Prefix of the ingress's TLS secret. You don't need to change it when hostnames change",[962,1149,1150,1155,1160,1162],{},[982,1151,1152],{},[629,1153,1154],{},"CWA_CI_TLS_CERTIFICATE_TIMEOUT",[982,1156,1157],{},[629,1158,1159],{},"600s",[982,1161,642],{},[982,1163,1164],{},"How long a deploy waits for a new certificate when production's hostnames change",[962,1166,1167,1171,1176,1181],{},[982,1168,1169],{},[629,1170,654],{},[982,1172,1173],{},[629,1174,1175],{},"\"true\"",[982,1177,1178],{},[629,1179,1180],{},"ingress.enabled",[982,1182,1183,1184,1187],{},"Set ",[629,1185,1186],{},"\"false\""," to deploy without an ingress",[962,1189,1190,1195,1199,1206],{},[982,1191,1192],{},[629,1193,1194],{},"CWA_CI_WWW_REDIRECT",[982,1196,1197],{},[629,1198,1186],{},[982,1200,1201,691,1203],{},[629,1202,1096],{},[629,1204,1205],{},"from-to-www-redirect",[982,1207,1208,1209,665,1212],{},"Sets ingress-nginx's annotation, which redirects between ",[629,1210,1211],{},"example.com",[629,1213,1214],{},"www.example.com",[962,1216,1217,1221,1226,1228],{},[982,1218,1219],{},[629,1220,922],{},[982,1222,1223],{},[629,1224,1225],{},"1.31.0",[982,1227,642],{},[982,1229,1230],{},"The kubectl version the deploy jobs install",[962,1232,1233,1237,1242,1244],{},[982,1234,1235],{},[629,1236,925],{},[982,1238,1239],{},[629,1240,1241],{},"3.19.0",[982,1243,642],{},[982,1245,1246],{},"The Helm version the deploy jobs install",[611,1248,1249,1253,1254,1256,1257,1260,1261,1267,1268,624],{},[615,1250,1251],{},[629,1252,1086],{}," defaults to ",[629,1255,1091],{}," (set by ",[629,1258,1259],{},"setup.sh","), whose certificates browsers don't trust. ",[615,1262,1183,1263,1266],{},[629,1264,1265],{},"letsencrypt-prod"," for production"," once the domain points at the cluster. Until then, a misconfigured domain fails against the staging issuer's much higher limits instead of using up Let's Encrypt's production limits. An empty value turns cert-manager off. Keep the variable on every production site. Both issuers must exist in the cluster under these names (see ",[620,1269,539],{"href":1270},"\u002Fdeployment#prepare-the-cluster",[611,1272,1273,1274,665,1277,1280,1281,1283],{},"On GitHub, the workflows' setup actions read ",[629,1275,1276],{},"vars.KUBERNETES_VERSION",[629,1278,1279],{},"vars.HELM_VERSION",". On GitLab, ",[629,1282,1259],{}," sets the defaults only when no CI variable sets them.",[645,1285,1286],{"id":823},"Secrets",[956,1288,1289,1301],{},[959,1290,1291],{},[962,1292,1293,1295,1297,1299],{},[965,1294,967],{},[965,1296,970],{},[965,1298,635],{},[965,1300,975],{},[977,1302,1303,1320,1337,1357,1381,1400],{},[962,1304,1305,1309,1312,1317],{},[982,1306,1307],{},[629,1308,827],{},[982,1310,1311],{},"kept, else generated",[982,1313,1314],{},[629,1315,1316],{},"php.appSecret",[982,1318,1319],{},"Symfony's secret, which signs URLs and other tokens",[962,1321,1322,1327,1329,1334],{},[982,1323,1324],{},[629,1325,1326],{},"JWT_SECRET_KEY",[982,1328,1311],{},[982,1330,1331],{},[629,1332,1333],{},"php.jwt.secret",[982,1335,1336],{},"The private key's contents",[962,1338,1339,1344,1346,1351],{},[982,1340,1341],{},[629,1342,1343],{},"JWT_PASSPHRASE",[982,1345,1311],{},[982,1347,1348],{},[629,1349,1350],{},"php.jwt.passphrase",[982,1352,1353,1354,1356],{},"The private key's passphrase. If you set ",[629,1355,1326],{},", set this too, or the deploy fails",[962,1358,1359,1364,1367,1372],{},[982,1360,1361],{},[629,1362,1363],{},"JWT_PUBLIC_KEY",[982,1365,1366],{},"derived",[982,1368,1369],{},[629,1370,1371],{},"php.jwt.public",[982,1373,1374,1375,1377,1378,1380],{},"When you supply ",[629,1376,1326],{}," and leave this empty, it's derived from the key with ",[629,1379,1343],{},", and the deploy fails if the passphrase doesn't decrypt the key",[962,1382,1383,1387,1389,1397],{},[982,1384,1385],{},[629,1386,830],{},[982,1388,1311],{},[982,1390,1391,655,1394],{},[629,1392,1393],{},"mercure.jwtKey.publisher.key",[629,1395,1396],{},"mercure.jwtKey.subscriber.key",[982,1398,1399],{},"Signs the Mercure publisher and subscriber tokens",[962,1401,1402,1407,1409,1416],{},[982,1403,1404],{},[629,1405,1406],{},"NUXT_APP_SECRET",[982,1408,1311],{},[982,1410,1411,1412,1415],{},"none (the ",[629,1413,1414],{},"\u003Crelease>-pwa-env"," Secret)",[982,1417,1418,1419,1422,1423],{},"Nuxt's ",[629,1420,1421],{},"appSecret",", which signs OG image URLs. At least 32 characters, or the deploy fails. See ",[620,1424,1426],{"href":1425},"\u002Fnuxt-module\u002Fconfiguration\u002Fsite-config-and-seo#the-og-image-secret","The OG image secret",[611,1428,1429,1430,1433,1434,1437,1438,713],{},"All of them are optional. For each one that isn't a CI variable, ",[629,1431,1432],{},"deploy_secrets"," reads the value from the release's live Secret, and generates a new one only when the release doesn't have one yet. The JWT key is kept together with its own passphrase, and the public key is derived from them. So logins, signed URLs and Mercure cookies survive a deploy, and pods on either side of a rollout agree. They're passed to Helm with ",[629,1435,1436],{},"--set",", so they win over a project's ",[620,1439,1441],{"href":1440},"\u002Fdeployment\u002Fci-cd#project-hooks",[629,1442,1443],{},"project_values",[611,1445,1446,1447,1450,1451,1454],{},"Setting them as ",[615,1448,1449],{},"protected, masked"," CI variables (GitHub: secrets) is still worth it for production. A release that is uninstalled, for example with ",[629,1452,1453],{},"CWA_CI_HELM_UNINSTALL"," or a stopped review app, loses its Secret, and its next deploy generates new values. A CI variable also lets you rotate a value on purpose.",[611,1456,1457,1458,665,1460,1462,1463,1465,1466,1469],{},"To rotate a value, set a new one as the CI variable and deploy. Removing the variable doesn't rotate it, because the deploy then keeps the live value. For the JWT key pair, set ",[629,1459,1326],{},[629,1461,1343],{},", and leave ",[629,1464,1363],{}," empty to have it derived. ",[615,1467,1468],{},"A new JWT key pair signs every user out"," when it's deployed, because tokens signed with the old key no longer verify.",[611,1471,1472,1473,1475,1476,1479,1480,624],{},"The ",[629,1474,894],{}," values hold the keys' contents, for the deploy. The ",[629,1477,1478],{},"functional tests"," job unsets them, so they never reach the tests (see ",[620,1481,1483],{"href":1482},"\u002Fdeployment\u002Fci-cd#test","CI\u002FCD › Test",[645,1485,801],{"id":1486},"application",[956,1488,1489,1501],{},[959,1490,1491],{},[962,1492,1493,1495,1497,1499],{},[965,1494,967],{},[965,1496,970],{},[965,1498,635],{},[965,1500,975],{},[977,1502,1503,1522,1545,1565,1585,1614,1634,1655],{},[962,1504,1505,1509,1514,1519],{},[982,1506,1507],{},[629,1508,658],{},[982,1510,1511],{},[629,1512,1513],{},"^https:\u002F\u002F(?:\u003Chosts>)$",[982,1515,1516],{},[629,1517,1518],{},"php.corsAllowOrigin",[982,1520,1521],{},"Regex. Set it only when another origin calls the API from a browser",[962,1523,1524,1528,1533,1538],{},[982,1525,1526],{},[629,1527,661],{},[982,1529,1530],{},[629,1531,1532],{},"^(?:\u003Chosts>|localhost)$",[982,1534,1535],{},[629,1536,1537],{},"php.trustedHosts",[982,1539,1540,1541,1544],{},"Symfony trusted hosts regex. Wrap the alternatives in one anchored group, ",[629,1542,1543],{},"^(?:www\\.example\\.com|php)$",", see the warning below",[962,1546,1547,1551,1557,1562],{},[982,1548,1549],{},[629,1550,664],{},[982,1552,1553,1556],{},[629,1554,1555],{},"https:\u002F\u002F\u003Chost>"," for each host",[982,1558,1559],{},[629,1560,1561],{},"mercure.corsOrigin",[982,1563,1564],{},"Space-separated origins the Mercure hub accepts",[962,1566,1567,1572,1577,1582],{},[982,1568,1569],{},[629,1570,1571],{},"ADMIN_USERNAME",[982,1573,1574],{},[629,1575,1576],{},"admin",[982,1578,1579],{},[629,1580,1581],{},"php.admin.username",[982,1583,1584],{},"The super admin's username. Fixture loads create this user",[962,1586,1587,1591,1595,1600],{},[982,1588,1589],{},[629,1590,797],{},[982,1592,1593],{},[629,1594,1576],{},[982,1596,1597],{},[629,1598,1599],{},"php.admin.password",[982,1601,1602,1603,1606,1607,1609,1610],{},"The super admin's password. ",[615,1604,1605],{},"Set it to a long random value, masked (a secret on GitHub)."," Every fixture load resets the admin's password to it. Unset or ",[629,1608,1576],{},", the deploy and each fixture load warn, and still run: see ",[620,1611,1613],{"href":1612},"#the-admin-password","The admin password",[962,1615,1616,1621,1626,1631],{},[982,1617,1618],{},[629,1619,1620],{},"ADMIN_EMAIL",[982,1622,1623],{},[629,1624,1625],{},"admin@example.com",[982,1627,1628],{},[629,1629,1630],{},"php.admin.email",[982,1632,1633],{},"The super admin's email address. Every fixture load resets the admin's email to it, so set it to a real address",[962,1635,1636,1640,1647,1652],{},[982,1637,1638],{},[629,1639,782],{},[982,1641,1642,1643,1646],{},"— (",[629,1644,1645],{},"null:\u002F\u002Fnull",")",[982,1648,1649],{},[629,1650,1651],{},"php.mailer.dsn",[982,1653,1654],{},"SMTP or SES DSN for transactional email. Unset, nothing is sent",[962,1656,1657,1661,1663,1668],{},[982,1658,1659],{},[629,1660,785],{},[982,1662,642],{},[982,1664,1665],{},[629,1666,1667],{},"php.mailer.email",[982,1669,1670,1671,1675],{},"From address for outgoing email. Production's ",[620,1672,1674],{"href":1673},"#orphan-scan","orphan scan"," also emails it",[611,1677,1678,1681,1682,1684],{},[629,1679,1680],{},"\u003Chosts>"," is the deploy's own hostname, plus ",[629,1683,672],{}," in production, with dots escaped.",[611,1686,1687,1688,1691,1692,665,1695,1698,1699,1701,1702,1705,1706,665,1708,624],{},"On a single server, ",[629,1689,1690],{},"compose.prod.yaml"," takes the hosts from ",[629,1693,1694],{},"SITE_HOST",[629,1696,1697],{},"SITE_ALIASES",", and php's entrypoint builds the same three defaults from them. It also requires ",[629,1700,782],{},", and stores media on the server unless you set ",[629,1703,1704],{},"MEDIA_STORAGE=gcloud"," (see ",[620,1707,771],{"href":770},[620,1709,1711],{"href":1710},"\u002Fdeployment\u002Fdocker#production-docker-compose","Docker › Production Docker Compose",[935,1713,1716],{"icon":1714,"color":1715},"i-heroicons-exclamation-triangle","warning",[611,1717,1718,1724,1725,1728,1729,1732,1733,1736,1737,1740],{},[615,1719,1720,1721,1723],{},"Anchor every ",[629,1722,661],{}," alternative."," Symfony doesn't anchor the pattern as a whole, so in ",[629,1726,1727],{},"^localhost|caddy|example\\.com$"," only the first alternative is tied to the start and only the last to the end. The middle one matches anywhere in a hostname, so ",[629,1730,1731],{},"caddy"," would trust ",[629,1734,1735],{},"evil-caddy.attacker.net",". Group the alternatives, ",[629,1738,1739],{},"^(?:localhost|caddy|example\\.com)$",". The template's own defaults use this form. Check any value you set yourself.",[1742,1743,1613],"h3",{"id":1744},"the-admin-password",[611,1746,1747,1749,1750,1752,1753,1755,1756,1758,1759,1762],{},[629,1748,1576],{}," \u002F ",[629,1751,1576],{}," is the development login. On a deployed site it lets anyone sign in as super admin. A deploy (every track, review apps included) checks ",[629,1754,797],{},", and so does a fixture load, which checks the API pod's own value because that's the one it writes. When it's unset or ",[629,1757,1576],{},", each prints this banner, and on GitHub Actions also a ",[615,1760,1761],{},"Public admin password"," warning annotation:",[1764,1765,1771],"pre",{"className":1766,"code":1768,"language":1769,"meta":1770},[1767],"language-text","WARNING: the super-admin login on \u003Cdomain> is admin\u002Fadmin.\nADMIN_PASSWORD is unset, so anyone can sign in as super admin, and every\nfixture load resets the admin password to it. Set ADMIN_PASSWORD to a\nlong random value (a masked CI variable or secret), then deploy.\n","text","",[629,1772,1768],{"__ignoreMap":1770},[611,1774,1775,1776,1779,1780,1783,1784,1786,1787,1790,1791,1793],{},"The second line says ",[629,1777,1778],{},"'admin'"," instead of ",[629,1781,1782],{},"unset"," when the value is ",[629,1785,1576],{},", and ",[629,1788,1789],{},"unset or 'admin' in the API pod"," for a fixture load. The deploy still runs, and the default stays ",[629,1792,1576],{},": the chart's admin Secret needs a value. Set the variable, deploy, then load fixtures (or sign in and change the password, which the next fixture load undoes).",[611,1795,1796,1798,1799,1801,1802,665,1804,1806,1807,1809,1810,1749,1812,1814],{},[629,1797,1690],{}," requires ",[629,1800,797],{},", and passes ",[629,1803,1571],{},[629,1805,1620],{}," when you set them (see ",[620,1808,1711],{"href":1710},"). The development stack keeps ",[629,1811,1576],{},[629,1813,1576],{}," with no warning.",[645,1816,771],{"id":1817},"media-storage",[956,1819,1820,1832],{},[959,1821,1822],{},[962,1823,1824,1826,1828,1830],{},[965,1825,967],{},[965,1827,970],{},[965,1829,635],{},[965,1831,975],{},[977,1833,1834,1873,1895,1916,1948],{},[962,1835,1836,1841,1851,1853],{},[982,1837,1838],{},[629,1839,1840],{},"MEDIA_STORAGE",[982,1842,1843,1846,1847,1850],{},[629,1844,1845],{},"gcloud"," in the production image, ",[629,1848,1849],{},"local"," in dev and tests",[982,1852,642],{},[982,1854,1855,1856,1858,1859,1861,1862,1786,1864,1866,1867,740,1869],{},"Where php stores uploads: ",[629,1857,1845],{}," (a Cloud Storage bucket) or ",[629,1860,1849],{}," (the server's disk). It's read at runtime, so one image works with either. Kubernetes deploys leave it at ",[629,1863,1845],{},[629,1865,1690],{}," sets ",[629,1868,1849],{},[620,1870,1872],{"href":1871},"#the-two-storages","The two storages",[962,1874,1875,1879,1884,1889],{},[982,1876,1877],{},[629,1878,755],{},[982,1880,1881],{},[629,1882,1883],{},"{}",[982,1885,1886],{},[629,1887,1888],{},"php.gcloud.jsonKey",[982,1890,1891,1892,1894],{},"Service-account key JSON for the bucket. Store it as a secret. ",[629,1893,1845],{}," only",[962,1896,1897,1901,1906,1911],{},[982,1898,1899],{},[629,1900,758],{},[982,1902,1903],{},[629,1904,1905],{},"no-gcloud-bucket",[982,1907,1908],{},[629,1909,1910],{},"php.gcloud.bucket",[982,1912,1913,1914,1894],{},"Google Cloud Storage bucket for uploads. ",[629,1915,1845],{},[962,1917,1918,1922,1925,1930],{},[982,1919,1920],{},[629,1921,762],{},[982,1923,1924],{},"bucket URL",[982,1926,1927],{},[629,1928,1929],{},"php.gcloud.publicUrl",[982,1931,1932,1933,1936,1937,1940,1941,1943,1944],{},"Base URL for media links, e.g. ",[629,1934,1935],{},"https:\u002F\u002Fcdn.example.com\u002F",". Must end in a slash. If empty, uses ",[629,1938,1939],{},"https:\u002F\u002Fstorage.googleapis.com\u002F\u003Cbucket>\u002F",". ",[629,1942,1845],{}," only. See ",[620,1945,1947],{"href":1946},"\u002Fdeployment\u002Fkubernetes#media-urls","Kubernetes › Media URLs",[962,1949,1950,1954,1957,1962],{},[982,1951,1952],{},[629,1953,766],{},[982,1955,1956],{},"empty",[982,1958,1959],{},[629,1960,1961],{},"php.gcloud.pathPrefix",[982,1963,1964,1965,1968,1969,736,1972,1974,1975,1978,1979],{},"Path for this app's files, e.g. ",[629,1966,1967],{},"site-a\u002F",": inside the bucket, or inside ",[629,1970,1971],{},"public\u002Fuploads",[629,1973,1849],{},". Empty stores at the root. Review apps add ",[629,1976,1977],{},"review\u002F\u003Cslug>\u002F"," to it. See ",[620,1980,1982],{"href":1981},"#storage-path-prefix","Storage path prefix",[611,1984,1985,1986,1988,1989,1992,1993,713],{},"On GitHub, set ",[629,1987,755],{}," as a ",[615,1990,1991],{},"secret"," and the others as ",[615,1994,1995],{},"variables",[1742,1997,1872],{"id":1998},"the-two-storages",[678,2000,2001,2033],{},[683,2002,2003,2007,2008,2010,2011,2013,2014,2017,2018,2021,2022,2025,2026,2028,2029,624],{},[615,2004,2005],{},[629,2006,1849],{}," stores files under ",[629,2009,1971],{}," in the php container, plus ",[629,2012,766],{},", and serves them at ",[629,2015,2016],{},"https:\u002F\u002F\u003CBROWSER_SERVER_NAME>\u002Fuploads\u002F…",". The development stack uses it, so its files are in ",[629,2019,2020],{},"api\u002Fpublic\u002Fuploads"," and its media URLs are ",[629,2023,2024],{},"https:\u002F\u002Flocalhost\u002Fuploads\u002F…",", originals and variants alike. On one server, ",[629,2027,1690],{}," keeps them in a volume (see ",[620,2030,2032],{"href":2031},"\u002Fdeployment\u002Fdocker#media-on-one-server","Docker › Media on one server",[683,2034,2035,2039,2040,2042,2043,2045],{},[615,2036,2037],{},[629,2038,1845],{}," stores files in ",[629,2041,758],{}," and serves them from ",[629,2044,762],{},", or the bucket's own URL. Every Kubernetes deploy uses it.",[611,2047,2048,2049,2052,2053,2056,2057,665,2061,2064],{},"Caddy serves ",[629,2050,2051],{},"\u002Fuploads\u002F"," as plain files, never through PHP, so a stored file named ",[629,2054,2055],{},".php"," is sent, not run. The ",[620,2058,2060],{"href":2059},"\u002Fapi\u002Forphaned-data#orphaned-files","orphaned files scan",[629,2062,2063],{},"delete-stored-files"," work the same with both.",[935,2066,2067],{"icon":1714,"color":1715},[611,2068,2069,2075,2076,2078,2079,2081],{},[615,2070,2071,2072,2074],{},"Don't use ",[629,2073,1849],{}," on Kubernetes."," The chart gives php no volume for ",[629,2077,1971],{},", so every upload would be lost when the pod is replaced, and with ",[620,2080,557],{"href":558}," each pod would see only its own files.",[611,2083,2084,2085,857,2087,2089,2090],{},"Any value other than ",[629,2086,1849],{},[629,2088,1845],{}," (in any case) is an error: ",[629,2091,2092],{},"MEDIA_STORAGE must be \"local\" or \"gcloud\": \"\u003Cvalue>\".",[611,2094,2095,2096,2099,2100,2103,2104,2107],{},"php also reads an optional ",[629,2097,2098],{},"GCLOUD_API_ENDPOINT",": a Cloud Storage emulator to use instead of Google, with no credentials. Only the ",[620,2101,2102],{"href":594},"browser tests","' stack sets it, and uses it with ",[629,2105,2106],{},"CWA_CI_E2E_STORAGE=\"gcloud\"",". Leave it unset on every deploy, where empty means Google.",[1742,2109,1982],{"id":2110},"storage-path-prefix",[611,2112,2113,2115,2116,736,2118,2120,2121,2123],{},[629,2114,766],{}," keeps an app's objects in one path of the bucket, or one folder of ",[629,2117,1971],{},[629,2119,1849],{}," storage. Uploads and their image variants are stored under it, and the ",[620,2122,2060],{"href":2059}," only lists that path. Unset, the app stores at the bucket root.",[611,2125,2126,2127,2129,2130,736,2133,2135,2136,665,2138,2140,2141,2144,2145,2147],{},"Media URLs include the prefix once: ",[629,2128,762],{}," (or the bucket's own URL, or ",[629,2131,2132],{},"https:\u002F\u002F\u003CBROWSER_SERVER_NAME>\u002Fuploads\u002F",[629,2134,1849],{},"), then the prefix. With ",[629,2137,1935],{},[629,2139,1967],{},", files are served from ",[629,2142,2143],{},"https:\u002F\u002Fcdn.example.com\u002Fsite-a\u002F…",". So don't add the prefix to ",[629,2146,762],{}," as well.",[611,2149,2150,2151,665,2154,2157,2158,2160,2161,2163,2164,2167,2168,857,2170,2173,2174,2177,2178,857,2181,2184,2185,2187,2188,2192,2193,665,2196,2199],{},"php and the deploy tidy the value the same way: surrounding whitespace is trimmed, doubled slashes are squeezed, leading and trailing slashes are removed and one trailing slash is added. So ",[629,2152,2153],{},"\u002Fsite-a",[629,2155,2156],{},"site-a\u002F\u002F"," both become ",[629,2159,1967],{},", and a review app of ",[629,2162,2156],{}," stores under ",[629,2165,2166],{},"site-a\u002Freview\u002F\u003Cslug>\u002F",". The deploy fails before it changes anything in the cluster when the prefix has a ",[629,2169,713],{},[629,2171,2172],{},".."," segment, or a whole ",[629,2175,2176],{},"review"," segment, such as ",[629,2179,2180],{},"review\u002F",[629,2182,2183],{},"site\u002Freview",": ",[629,2186,2180],{}," is where review apps store, and the lifecycle rule deletes objects there (see ",[620,2189,2191],{"href":2190},"#review-app-paths","Review app paths","). ",[629,2194,2195],{},"reviews\u002F",[629,2197,2198],{},"my-review\u002F"," are fine.",[611,2201,2202,2203,2207,2208,2211,2212,2214],{},"Set it as its own CI variable. It's reserved (see ",[620,2204,2206],{"href":2205},"#reserved-names","Reserved names","), so ",[629,2209,2210],{},"CWA_API_GCLOUD_PATH_PREFIX"," fails the deploy. Local development sets no prefix, so it stores uploads in ",[629,2213,2020],{}," itself.",[935,2216,2217],{"icon":1714,"color":1715},[611,2218,2219,2222],{},[615,2220,2221],{},"Give every app on a shared bucket its own prefix."," An app at the bucket root scans the whole bucket, so the other apps' files are listed in its orphaned files report. They have the bundle's stored names, so they count as orphaned, and deleting every orphaned file removes them.",[2224,2225,2191],"h4",{"id":2226},"review-app-paths",[611,2228,2229,2230,2233,2234,2237,2238,2241,2242,2245,2246,2163,2249,2252,2253,2256,2257,2260,2261,2264,2265,2267],{},"A review app always stores under ",[629,2231,2232],{},"\u003CGCLOUD_PATH_PREFIX>review\u002F\u003Cslug>\u002F",", so a review app never writes into production's path. ",[629,2235,2236],{},"\u003Cslug>"," is the environment slug, ",[629,2239,2240],{},"CI_ENVIRONMENT_SLUG",", without its leading ",[629,2243,2244],{},"review-",", so a branch ",[629,2247,2248],{},"feat-x",[629,2250,2251],{},"review\u002Ffeat-x\u002F",", or ",[629,2254,2255],{},"site-a\u002Freview\u002Ffeat-x\u002F"," with a prefix. On GitHub the slug is ",[629,2258,2259],{},"review-\u003Cbranch slug>",". On GitLab it's GitLab's slug for ",[629,2262,2263],{},"review\u002F\u003Cbranch>",", which GitLab shortens and may give a random suffix. A review deploy without ",[629,2266,2240],{}," fails.",[611,2269,2270,2271,2275],{},"Stopping a review app deletes its stored files (",[620,2272,2274],{"href":2273},"\u002Fdeployment\u002Fci-cd#review-apps","CI\u002FCD › Review Apps","). That step can fail, for example when the API pod isn't running, so add a bucket lifecycle rule as a backstop. It deletes objects under the review path some time after they were created:",[1764,2277,2281],{"className":2278,"code":2279,"language":2280,"meta":1770,"style":1770},"language-json shiki shiki-themes github-light github-dark material-theme-palenight","{\n  \"rule\": [\n    {\n      \"action\": { \"type\": \"Delete\" },\n      \"condition\": { \"age\": 90, \"matchesPrefix\": [\"review\u002F\"] }\n    }\n  ]\n}\n","json",[629,2282,2283,2292,2312,2318,2358,2411,2417,2423],{"__ignoreMap":1770},[2284,2285,2288],"span",{"class":2286,"line":2287},"line",1,[2284,2289,2291],{"class":2290},"sOvfz","{\n",[2284,2293,2295,2299,2303,2306,2309],{"class":2286,"line":2294},2,[2284,2296,2298],{"class":2297},"sBtbT","  \"",[2284,2300,2302],{"class":2301},"sphPO","rule",[2284,2304,2305],{"class":2297},"\"",[2284,2307,2308],{"class":2290},":",[2284,2310,2311],{"class":2290}," [\n",[2284,2313,2315],{"class":2286,"line":2314},3,[2284,2316,2317],{"class":2290},"    {\n",[2284,2319,2321,2324,2328,2330,2332,2335,2338,2342,2344,2346,2349,2353,2355],{"class":2286,"line":2320},4,[2284,2322,2323],{"class":2297},"      \"",[2284,2325,2327],{"class":2326},"sbW4m","action",[2284,2329,2305],{"class":2297},[2284,2331,2308],{"class":2290},[2284,2333,2334],{"class":2290}," {",[2284,2336,2337],{"class":2297}," \"",[2284,2339,2341],{"class":2340},"scSvc","type",[2284,2343,2305],{"class":2297},[2284,2345,2308],{"class":2290},[2284,2347,2337],{"class":2348},"seSrl",[2284,2350,2352],{"class":2351},"sLL54","Delete",[2284,2354,2305],{"class":2348},[2284,2356,2357],{"class":2290}," },\n",[2284,2359,2361,2363,2366,2368,2370,2372,2374,2377,2379,2381,2384,2387,2389,2392,2394,2396,2399,2401,2403,2405,2408],{"class":2286,"line":2360},5,[2284,2362,2323],{"class":2297},[2284,2364,2365],{"class":2326},"condition",[2284,2367,2305],{"class":2297},[2284,2369,2308],{"class":2290},[2284,2371,2334],{"class":2290},[2284,2373,2337],{"class":2297},[2284,2375,2376],{"class":2340},"age",[2284,2378,2305],{"class":2297},[2284,2380,2308],{"class":2290},[2284,2382,2383],{"class":2340}," 90",[2284,2385,2386],{"class":2290},",",[2284,2388,2337],{"class":2297},[2284,2390,2391],{"class":2340},"matchesPrefix",[2284,2393,2305],{"class":2297},[2284,2395,2308],{"class":2290},[2284,2397,2398],{"class":2290}," [",[2284,2400,2305],{"class":2348},[2284,2402,2180],{"class":2351},[2284,2404,2305],{"class":2348},[2284,2406,2407],{"class":2290},"]",[2284,2409,2410],{"class":2290}," }\n",[2284,2412,2414],{"class":2286,"line":2413},6,[2284,2415,2416],{"class":2290},"    }\n",[2284,2418,2420],{"class":2286,"line":2419},7,[2284,2421,2422],{"class":2290},"  ]\n",[2284,2424,2426],{"class":2286,"line":2425},8,[2284,2427,2428],{"class":2290},"}\n",[611,2430,2431,2432,2434,2435,2438,2439,2442,2443,2446,2447,2450],{},"With a ",[629,2433,766],{},", the review path is inside it, so add one rule per prefixed app on the bucket, with ",[629,2436,2437],{},"matchesPrefix: [\"\u003CGCLOUD_PATH_PREFIX>review\u002F\"]",", such as ",[629,2440,2441],{},"[\"site-a\u002Freview\u002F\"]",". Use ",[629,2444,2445],{},"[\"review\u002F\"]"," only for an app with no prefix. ",[615,2448,2449],{},"The age counts from each object's creation, not from when the review app stops",", so a review app that lives longer than that loses its older uploads while it's still running. Pick an age longer than your review apps live. Setting a bucket's lifecycle configuration replaces its existing rules, so add this one to any you have.",[2224,2452,2454],{"id":2453},"changing-a-live-apps-prefix","Changing a live app's prefix",[611,2456,2457,2458,2460],{},"The paths stored in the database are relative to the prefix. Before you set or change ",[629,2459,766],{}," on a live app, copy its objects under the new prefix, keeping each object's path below it. Then deploy. The old objects stay where they were until you remove them.",[1742,2462,2464],{"id":2463},"how-long-media-is-cached","How long media is cached",[611,2466,2467,2468,2471,2472,2475,2476,2479,2480,2482,2483,2485,2486,2488],{},"Every file written to the bucket gets its own ",[629,2469,2470],{},"Cache-Control",". Originals get ",[629,2473,2474],{},"public, max-age=31536000, immutable",", because a stored name is never reused. Image variants get ",[629,2477,2478],{},"public, max-age=2592000",", because a regenerated variant keeps its path. Files stored with ",[629,2481,1849],{}," get no ",[629,2484,2470],{},": Caddy serves ",[629,2487,2051],{}," without one.",[935,2490,2491],{"icon":1714,"color":1715},[611,2492,2493,2496,2497,2500,2501,2504,2505,2508],{},[615,2494,2495],{},"Google Cloud CDN caps these unless you raise its TTLs."," A backend bucket sends browsers at most its ",[615,2498,2499],{},"client TTL"," and keeps its own copy for at most its ",[615,2502,2503],{},"max TTL",". The defaults are one hour and one day, so a CDN created with them serves every file with ",[629,2506,2507],{},"max-age=3600"," whatever the bucket says. Raising a cap never lengthens a file's own TTL, so a year is safe for both. Every site behind Cloud CDN should raise them.",[611,2510,2511],{},"Create the CDN's backend bucket with both caps at a year, or raise them on an existing one:",[1764,2513,2517],{"className":2514,"code":2515,"language":2516,"meta":1770,"style":1770},"language-bash shiki shiki-themes github-light github-dark material-theme-palenight","gcloud compute backend-buckets update \u003Cbackend-bucket> --max-ttl=31536000 --client-ttl=31536000\n","bash",[629,2518,2519],{"__ignoreMap":1770},[2284,2520,2521,2524,2527,2530,2533,2537,2540,2544,2547,2551],{"class":2286,"line":2287},[2284,2522,1845],{"class":2523},"sRCss",[2284,2525,2526],{"class":2351}," compute",[2284,2528,2529],{"class":2351}," backend-buckets",[2284,2531,2532],{"class":2351}," update",[2284,2534,2536],{"class":2535},"sVlFx"," \u003C",[2284,2538,2539],{"class":2351},"backend-bucke",[2284,2541,2543],{"class":2542},"sPB8G","t",[2284,2545,2546],{"class":2535},">",[2284,2548,2550],{"class":2549},"szhYu"," --max-ttl=31536000",[2284,2552,2553],{"class":2549}," --client-ttl=31536000\n",[611,2555,2556,2557,2560,2561,2564,2565,713],{},"Changes reach the CDN's edges within a few minutes. Invalidate the CDN host afterwards so files it already holds pick up the new headers: ",[629,2558,2559],{},"gcloud compute url-maps invalidate-cdn-cache \u003Curl-map> --host \u003Ccdn host> --path \"\u002F*\"",". To check, ",[629,2562,2563],{},"curl -sI"," a variant through the CDN: it should send ",[629,2566,2567],{},"max-age=2592000",[645,2569,744],{"id":2570},"database",[956,2572,2573,2585],{},[959,2574,2575],{},[962,2576,2577,2579,2581,2583],{},[965,2578,967],{},[965,2580,970],{},[965,2582,635],{},[965,2584,975],{},[977,2586,2587,2609,2632,2670,2687,2710,2734,2754,2774,2799,2819,2847],{},[962,2588,2589,2594,2599,2604],{},[982,2590,2591],{},[629,2592,2593],{},"CWA_CI_POSTGRES_ENABLED",[982,2595,2596],{},[629,2597,2598],{},"true",[982,2600,2601],{},[629,2602,2603],{},"postgresql.enabled",[982,2605,1183,2606,2608],{},[629,2607,1186],{}," to use an external database, which turns off the in-cluster PostgreSQL",[962,2610,2611,2615,2617,2622],{},[982,2612,2613],{},[629,2614,735],{},[982,2616,642],{},[982,2618,2619],{},[629,2620,2621],{},"postgresql.url",[982,2623,2624,2625,2627,2628,624],{},"Connection string when using an external PostgreSQL database, the only kind the template supports. On GitLab, scope production's to the ",[629,2626,950],{}," environment, or give review apps their own: a review deploy on production's database is refused (see ",[620,2629,2631],{"href":2630},"\u002Fdeployment\u002Fmigrations-and-backups#a-track-on-productions-database","Migrations and Backups › A track on production's database",[962,2633,2634,2638,2652,2657],{},[982,2635,2636],{},[629,2637,668],{},[982,2639,2640,2252,2643,2646,2647,2649,2650],{},[629,2641,2642],{},"prefer",[629,2644,2645],{},"require"," when ",[629,2648,2593],{}," is ",[629,2651,1186],{},[982,2653,2654],{},[629,2655,2656],{},"php.databaseSSL.mode",[982,2658,2659,655,2662,655,2664,655,2666,2669],{},[629,2660,2661],{},"disable",[629,2663,2642],{},[629,2665,2645],{},[629,2667,2668],{},"verify-full",". An external database is reached over the network, so it gets TLS by default",[962,2671,2672,2677,2679,2684],{},[982,2673,2674],{},[629,2675,2676],{},"DATABASE_CA_CERT",[982,2678,642],{},[982,2680,2681],{},[629,2682,2683],{},"php.databaseSSL.ca",[982,2685,2686],{},"CA cert PEM for SSL verification",[962,2688,2689,2697,2699,2707],{},[982,2690,2691,1749,2694],{},[629,2692,2693],{},"DATABASE_CLIENT_CERT",[629,2695,2696],{},"DATABASE_CLIENT_KEY",[982,2698,642],{},[982,2700,2701,1749,2704],{},[629,2702,2703],{},"php.databaseSSL.cert",[629,2705,2706],{},"php.databaseSSL.key",[982,2708,2709],{},"A client certificate and key, for a database that asks for one",[962,2711,2712,2717,2722,2727],{},[982,2713,2714],{},[629,2715,2716],{},"POSTGRES_USER",[982,2718,2719],{},[629,2720,2721],{},"example",[982,2723,2724],{},[629,2725,2726],{},"postgresql.auth.username",[982,2728,2729,2730,2733],{},"In-cluster PostgreSQL only: the database user. The name the postgres image, compose and the test jobs use. ",[629,2731,2732],{},"POSTGRES_USERNAME"," is not read",[962,2735,2736,2741,2746,2751],{},[982,2737,2738],{},[629,2739,2740],{},"POSTGRES_PASSWORD",[982,2742,2743],{},[629,2744,2745],{},"!ChangeMe!",[982,2747,2748],{},[629,2749,2750],{},"postgresql.auth.password",[982,2752,2753],{},"In-cluster PostgreSQL only: the user's password. Any character is safe",[962,2755,2756,2761,2766,2771],{},[982,2757,2758],{},[629,2759,2760],{},"POSTGRES_DB",[982,2762,2763],{},[629,2764,2765],{},"api",[982,2767,2768],{},[629,2769,2770],{},"postgresql.auth.database",[982,2772,2773],{},"In-cluster PostgreSQL only: the database name",[962,2775,2776,2781,2784,2789],{},[982,2777,2778],{},[629,2779,2780],{},"CWA_CI_POSTGRES_PERSISTENCE",[982,2782,2783],{},"unset (off)",[982,2785,2786],{},[629,2787,2788],{},"postgresql.persistence.enabled",[982,2790,2791,2792,2794,2795,2798],{},"In-cluster PostgreSQL only: ",[629,2793,2598],{}," keeps its data on a volume, ",[629,2796,2797],{},"false"," turns that off. Any other value fails the deploy",[962,2800,2801,2806,2811,2816],{},[982,2802,2803],{},[629,2804,2805],{},"CWA_CI_POSTGRES_PERSISTENCE_SIZE",[982,2807,2808],{},[629,2809,2810],{},"1Gi",[982,2812,2813],{},[629,2814,2815],{},"postgresql.persistence.size",[982,2817,2818],{},"The volume's size",[962,2820,2821,2826,2831,2836],{},[982,2822,2823],{},[629,2824,2825],{},"CWA_CI_POSTGRES_STORAGE_CLASS",[982,2827,2828],{},[629,2829,2830],{},"standard",[982,2832,2833],{},[629,2834,2835],{},"postgresql.persistence.storageClass",[982,2837,2838,2839,2842,2843,2846],{},"The volume's storage class. ",[629,2840,2841],{},"-"," sets none (",[629,2844,2845],{},"storageClassName: \"\"","), which turns off dynamic provisioning",[962,2848,2849,2854,2859,2864],{},[982,2850,2851],{},[629,2852,2853],{},"CWA_CI_POSTGRES_IMAGE_TAG",[982,2855,2856],{},[629,2857,2858],{},"18-alpine",[982,2860,2861],{},[629,2862,2863],{},"postgresql.image.tag",[982,2865,2866,2867,2870],{},"The in-cluster PostgreSQL's tag of the official ",[629,2868,2869],{},"postgres"," image",[611,2872,2873,2874,2876,2877,2879,2880,2882,2883,2886,2887,2890],{},"With the in-cluster PostgreSQL (",[629,2875,2593],{}," not ",[629,2878,1186],{},"), the deploy writes the three ",[629,2881,889],{}," variables that are set to the chart's ",[629,2884,2885],{},"postgresql.auth",". The PostgreSQL pod creates the database from those values, and the chart builds the API's ",[629,2888,2889],{},"database-url"," from them, percent-encoding each part. An unset variable keeps the chart's default.",[935,2892,2893],{"icon":1714,"color":1715},[611,2894,2895,2898,2899,736,2901,2903,2904,624],{},[615,2896,2897],{},"By default the in-cluster PostgreSQL has no volume."," Its data lives in the pod and is lost whenever the pod is replaced, for example after a node restart or a credentials change. Use an external database (",[629,2900,735],{},[629,2902,739],{},") for a real site, or turn on persistence (see ",[620,2905,2907],{"href":2906},"#in-cluster-postgresql-persistence","In-cluster PostgreSQL persistence",[611,2909,2910,2911,2914],{},"PostgreSQL creates the user and database only when it starts on an empty data directory. Changing the user, password or database name restarts the PostgreSQL pod. Without persistence, ",[615,2912,2913],{},"it restarts empty",", so the content has to be loaded again.",[611,2916,2917,2918,857,2921,2924,2925,2927],{},"With persistence, the volume keeps the user, password and database it was created with, and a deploy doesn't change them, so the API can't connect with the new values. Change them in PostgreSQL first, with ",[629,2919,2920],{},"ALTER USER",[629,2922,2923],{},"ALTER DATABASE",", then set the new values and deploy. The official image makes the database user the superuser. PostgreSQL won't rename the user you're connected as, or the database you're connected to, so rename from another superuser and from the ",[629,2926,2869],{}," database.",[611,2929,2930,2931,2933,2934,2937,2938,2940,2941,2944,2945,2944,2948,2951],{},"On GitLab, a ",[629,2932,889],{}," variable with the ",[615,2935,2936],{},"All"," environment scope also reaches the ",[629,2939,1478],{}," job, where it replaces the job's own ",[629,2942,2943],{},"runner","\u002F",[629,2946,2947],{},"password",[629,2949,2950],{},"test",", for both the test database and its URL. Scope these variables to your deploy environments. On GitHub, the deploy jobs don't map them from secrets, so set them as Actions variables; the test job sets its own and ignores them.",[1742,2953,2907],{"id":2954},"in-cluster-postgresql-persistence",[611,2956,2957,2958,2961,2962,2965,2966,2968,2969,2971],{},"The three ",[629,2959,2960],{},"CWA_CI_POSTGRES_*"," persistence variables go to the chart's ",[629,2963,2964],{},"postgresql.persistence",", and only the ones that are set. Unset, the chart's defaults apply: no volume, and ",[629,2967,2830],{}," at ",[629,2970,2810],{}," once it's turned on.",[611,2973,2974,2975,2978],{},"Kubernetes can't change a StatefulSet's volume once it exists, so Helm would fail part way through. Before anything changes, the deploy compares the variables with the live PostgreSQL and ",[615,2976,2977],{},"stops when they differ",", with the commands to run and what happens to the data. It never deletes anything itself:",[956,2980,2981,2991],{},[959,2982,2983],{},[962,2984,2985,2988],{},[965,2986,2987],{},"Change on a live release",[965,2989,2990],{},"What the deploy tells you to do",[977,2992,2993,3008,3027,3043],{},[962,2994,2995,2998],{},[982,2996,2997],{},"Turn persistence on",[982,2999,3000,3003,3004,3007],{},[629,3001,3002],{},"pg_dump"," the data if you need it, run ",[629,3005,3006],{},"kubectl delete statefulset \u003Crelease>-postgresql -n \u003Cnamespace> --cascade=orphan",", and deploy again. The new pod starts with an empty volume, so load fixtures or restore the dump",[962,3009,3010,3013],{},[982,3011,3012],{},"Turn persistence off",[982,3014,3015,3016,3019,3020,3023,3024],{},"The same ",[629,3017,3018],{},"kubectl delete statefulset … --cascade=orphan",", then deploy. The new pod starts empty. The volume ",[629,3021,3022],{},"data-\u003Crelease>-postgresql-0"," keeps its data until you delete it with ",[629,3025,3026],{},"kubectl delete pvc",[962,3028,3029,3032],{},[982,3030,3031],{},"Change the size",[982,3033,3034,3035,3038,3039,3042],{},"Set it back, or delete the StatefulSet with ",[629,3036,3037],{},"--cascade=orphan"," and deploy: the volume and its data are kept at the old size. To grow it, if the storage class allows expansion, patch the volume with the ",[629,3040,3041],{},"kubectl patch pvc"," command the log prints",[962,3044,3045,3048],{},[982,3046,3047],{},"Change the storage class",[982,3049,3050,3051,3053,3054,3056,3057,3059],{},"Set it back, or ",[629,3052,3002],{},", delete the StatefulSet with ",[629,3055,3037],{}," and the volume with ",[629,3058,3026],{},", deploy, and restore the dump",[611,3061,3062],{},"A first deploy, with no PostgreSQL yet, can use any settings. If the CI can't read the StatefulSet, the deploy prints a warning and skips the check.",[611,3064,3065,3066,3069,3070,3073,3074,3077,3078,3080,3081,3083],{},"Helm leaves volumes behind when it uninstalls a release. ",[615,3067,3068],{},"Stopping a review app also deletes its PostgreSQL volume",", so a branch deployed again starts with fresh data and credentials. Staging and canary keep theirs: a persistent staging volume outlives ",[629,3071,3072],{},"delete staging",", so delete it by hand with ",[629,3075,3076],{},"kubectl delete pvc data-\u003Crelease>-staging-postgresql-0",". On GitLab, a ",[629,3079,2780],{}," scoped to ",[629,3082,950],{}," reaches staging and canary too.",[645,3085,3087],{"id":3086},"migrations-and-backups","Migrations and backups",[611,3089,3090,3091,2649,3094,655,3097,655,3100,857,3103,740,3106,713],{},"The database step before a deploy rolls out. ",[629,3092,3093],{},"\u003CTRACK>",[629,3095,3096],{},"PRODUCTION",[629,3098,3099],{},"STAGING",[629,3101,3102],{},"CANARY",[629,3104,3105],{},"REVIEW",[620,3107,565],{"href":566},[956,3109,3110,3122],{},[959,3111,3112],{},[962,3113,3114,3116,3118,3120],{},[965,3115,967],{},[965,3117,970],{},[965,3119,635],{},[965,3121,975],{},[977,3123,3124,3150,3171,3198,3222,3245,3265,3286,3313,3333,3355,3375,3396,3412],{},[962,3125,3126,3131,3134,3136],{},[982,3127,3128],{},[629,3129,3130],{},"CWA_CI_MIGRATIONS_PLAN_PRODUCTION",[982,3132,3133],{},"on",[982,3135,642],{},[982,3137,3138,3139,3141,3142,3145,3146,3149],{},"GitLab only. ",[629,3140,1186],{}," removes the read-only ",[615,3143,3144],{},"migrations plan production"," job. On GitHub the plan is the Production workflow's ",[629,3147,3148],{},"migrations-plan"," action",[962,3151,3152,3157,3160,3162],{},[982,3153,3154],{},[629,3155,3156],{},"CWA_CI_DATABASE_BACKUP_\u003CTRACK>",[982,3158,3159],{},"off",[982,3161,642],{},[982,3163,3164,3166,3167,3170],{},[629,3165,1175],{}," dumps the track's database to ",[629,3168,3169],{},"CWA_CI_DATABASE_BACKUP_BUCKET"," before a deploy that has migrations pending. PostgreSQL only. A failed backup stops the deploy",[962,3172,3173,3177,3179,3184],{},[982,3174,3175],{},[629,3176,3169],{},[982,3178,642],{},[982,3180,3181],{},[629,3182,3183],{},"databaseJob.backup.bucket",[982,3185,3186,3187,3190,3191,3193,3194],{},"Required with a backup switch, and for the on-demand backup job. A ",[615,3188,3189],{},"private"," bucket, never ",[629,3192,758],{},": the deploy refuses the media bucket, and a backup anyone can read is deleted again and fails. Several projects can share one bucket, each in its own folder. See ",[620,3195,3197],{"href":3196},"\u002Fdeployment\u002Fmigrations-and-backups#the-backup-bucket","Migrations and Backups › The backup bucket",[962,3199,3200,3205,3207,3212],{},[982,3201,3202],{},[629,3203,3204],{},"CWA_CI_DATABASE_BACKUP_GCLOUD_JSON",[982,3206,642],{},[982,3208,3209],{},[629,3210,3211],{},"databaseJob.backup.gcloudJsonB64",[982,3213,3214,3215,3217,3218,3221],{},"Required with a backup switch. The backup service account's key: the key JSON base64-encoded on one line (maskable on GitLab), or the JSON itself. Never the media key: the deploy refuses a key with ",[629,3216,755],{},"'s service account. On GitLab, mask it, and don't make it ",[615,3219,3220],{},"Protected"," unless the branch that deploys is protected, or the job never gets it. On GitHub, a repository secret.",[962,3223,3224,3229,3232,3234],{},[982,3225,3226],{},[629,3227,3228],{},"CWA_CI_DATABASE_BACKUP_FOLDER",[982,3230,3231],{},"the project path slug",[982,3233,642],{},[982,3235,3236,3237,3240,3241,3244],{},"The project's folder in the backup bucket. Defaults to GitLab's ",[629,3238,3239],{},"CI_PROJECT_PATH_SLUG",", or on GitHub ",[629,3242,3243],{},"owner\u002Frepo"," lower-cased with every other character a dash. Set it if the folder you created has another name",[962,3246,3247,3252,3257,3262],{},[982,3248,3249],{},[629,3250,3251],{},"CWA_CI_DATABASE_BACKUP_KEEP",[982,3253,3254],{},[629,3255,3256],{},"5",[982,3258,3259],{},[629,3260,3261],{},"databaseJob.backup.keep",[982,3263,3264],{},"How many backups to keep for each track and release",[962,3266,3267,3272,3277,3282],{},[982,3268,3269],{},[629,3270,3271],{},"CWA_CI_DATABASE_BACKUP_IMAGE",[982,3273,3274],{},[629,3275,3276],{},"postgres:\u003Cserver major>-alpine",[982,3278,3279],{},[629,3280,3281],{},"databaseJob.backup.image",[982,3283,1472,3284,2870],{},[629,3285,3002],{},[962,3287,3288,3293,3298,3303],{},[982,3289,3290],{},[629,3291,3292],{},"CWA_CI_DATABASE_BACKUP_STORAGE",[982,3294,3295],{},[629,3296,3297],{},"5Gi",[982,3299,3300],{},[629,3301,3302],{},"databaseJob.backup.storage",[982,3304,3305,3306,691,3309,3312],{},"The backup Job's space for the dump (its ",[629,3307,3308],{},"emptyDir",[629,3310,3311],{},"sizeLimit","). A dump bigger than this gets the Job evicted, which fails the backup, instead of filling the node's disk under other pods. Raise it for a large database.",[962,3314,3315,3320,3322,3324],{},[982,3316,3317],{},[629,3318,3319],{},"CWA_CI_MIGRATIONS_JOB_DISABLED",[982,3321,642],{},[982,3323,642],{},[982,3325,3326,3328,3329,713],{},[629,3327,1175],{}," stops production's deploy planning and migrating in a Job before Helm, so the new pods migrate as they start. Only for a cluster where the Job can't run. A maintenance window still migrates in a Job. See ",[620,3330,3332],{"href":3331},"\u002Fdeployment\u002Fmigrations-and-backups#migrating-in-a-job-before-the-rollout","Migrations and Backups › Migrating in a Job before the rollout",[962,3334,3335,3346,3348,3350],{},[982,3336,3337,655,3340,655,3343],{},[629,3338,3339],{},"CWA_CI_MIGRATIONS_JOB_STAGING",[629,3341,3342],{},"_CANARY",[629,3344,3345],{},"_REVIEW",[982,3347,3159],{},[982,3349,642],{},[982,3351,3352,3354],{},[629,3353,1175],{}," migrates that track in a Job before Helm, as production always does, so a failed migration stops the deploy before the rollout. Production's Job is always on, so there's no production switch.",[962,3356,3357,3362,3367,3372],{},[982,3358,3359],{},[629,3360,3361],{},"CWA_CI_DATABASE_JOB_TIMEOUT",[982,3363,3364],{},[629,3365,3366],{},"1800",[982,3368,3369],{},[629,3370,3371],{},"databaseJob.activeDeadlineSeconds",[982,3373,3374],{},"Seconds each database Job (plan, backup, migrate) may run",[962,3376,3377,3382,3386,3388],{},[982,3378,3379],{},[629,3380,3381],{},"RELEASE_PRODUCTION",[982,3383,3384],{},[629,3385,950],{},[982,3387,642],{},[982,3389,3390,3391,3393,3394,713],{},"GitLab. Production's Helm release name, which a review deploy with an external ",[629,3392,735],{}," reads to check it isn't production's database. Set it if production's release has another name. See ",[620,3395,2631],{"href":2630},[962,3397,3398,3402,3407,3409],{},[982,3399,3400],{},[629,3401,707],{},[982,3403,3404],{},[629,3405,3406],{},"\u003Cproject name>-production",[982,3408,642],{},[982,3410,3411],{},"GitLab. Production's namespace, for the same review check. Set it if production's namespace has another name; without access to it, the check only warns. (On GitHub it's the secret that names production's namespace, and review apps always use the in-cluster PostgreSQL.)",[962,3413,3414,3419,3421,3423],{},[982,3415,3416],{},[629,3417,3418],{},"CWA_CI_MAINTENANCE_DURING_MIGRATIONS",[982,3420,642],{},[982,3422,642],{},[982,3424,3425,3427,3428,3431,3432,3435],{},[629,3426,1175],{}," for ",[615,3429,3430],{},"one production pipeline only",", never a project variable: maintenance on while pending migrations run. On GitHub, the Production workflow's ",[629,3433,3434],{},"maintenance_during_migrations"," input sets it, and a repository variable of this name is never used. Needs bundle 2.0.0-alpha.33 on the live site",[645,3437,3439],{"id":3438},"composer","Composer",[956,3441,3442,3454],{},[959,3443,3444],{},[962,3445,3446,3448,3450,3452],{},[965,3447,967],{},[965,3449,970],{},[965,3451,635],{},[965,3453,975],{},[977,3455,3456],{},[962,3457,3458,3462,3464,3466],{},[982,3459,3460],{},[629,3461,919],{},[982,3463,1782],{},[982,3465,642],{},[982,3467,3468,3469,3472],{},"Optional. Authenticates composer's downloads from github.com, in the API image build and the GitLab test jobs, so installs don't hit GitHub's anonymous limit of 60 requests an hour per IP. A fine-grained token with ",[615,3470,3471],{},"no permissions"," is enough. Store it as a masked variable",[611,3474,3475,3477,3478,665,3481,3484,3485,2192,3489,3492,3493,713],{},[629,3476,1259],{}," turns the token into ",[629,3479,3480],{},"COMPOSER_AUTH",[629,3482,3483],{},"build_api"," passes that to the build as a secret (see ",[620,3486,3488],{"href":3487},"\u002Fdeployment\u002Fci-cd#build","CI\u002FCD › Build",[615,3490,3491],{},"Leave the variable unset rather than empty:"," an empty token is rejected outright, which is worse than anonymous. GitHub Actions needs nothing: the build step maps the workflow's own ",[629,3494,3495],{},"secrets.GITHUB_TOKEN",[645,3497,3499],{"id":3498},"php-image","php image",[611,3501,3502,3503,624],{},"Which base the php image builds on (see ",[620,3504,3506],{"href":3505},"\u002Fdeployment\u002Fdocker#where-the-base-comes-from","Docker › Where the base comes from",[956,3508,3509,3521],{},[959,3510,3511],{},[962,3512,3513,3515,3517,3519],{},[965,3514,967],{},[965,3516,970],{},[965,3518,635],{},[965,3520,975],{},[977,3522,3523,3554],{},[962,3524,3525,3530,3532,3534],{},[982,3526,3527],{},[629,3528,3529],{},"CWA_CI_PHP_IMAGE_FROM_SOURCE",[982,3531,1782],{},[982,3533,642],{},[982,3535,3536,3538,3539,3542,3543,3546,3547,3550,3551],{},[629,3537,1175],{}," builds the base from source (",[629,3540,3541],{},"--build-arg CWA_PHP_BASE=cwa_php_base",") instead of pulling ",[629,3544,3545],{},"cwa-php-base",". Set it when your project changes the Souin or storages patches, ",[629,3548,3549],{},"queryallowlist"," or the Caddy modules. Don't make it Protected, or review branches' builds pull the release's base without your changes. It wins over ",[629,3552,3553],{},"CWA_CI_PHP_BASE_IMAGE",[962,3555,3556,3560,3562,3564],{},[982,3557,3558],{},[629,3559,3553],{},[982,3561,1782],{},[982,3563,642],{},[982,3565,3566,3567,3570,3571,3574,3575,3578,3579,3582],{},"A base image to build on instead of the Dockerfile's default, such as a weekly rebuild ",[629,3568,3569],{},"ghcr.io\u002Fcomponents-web-app\u002Fcwa-php-base:\u003Cversion>-rYYYYMMDD",", or a digest pin ",[629,3572,3573],{},"…@sha256:…",". Passed as the ",[629,3576,3577],{},"CWA_PHP_BASE"," build arg, so you don't need to edit ",[629,3580,3581],{},"api\u002FDockerfile",". A value that isn't an image reference fails the build",[611,3584,3585,3586,3588,3589,3591,3592,3595,3596,3598,3599,713],{},"Unset, the build uses ",[629,3587,3581],{},"'s default: the ",[629,3590,3545],{}," image of your template's release, such as ",[629,3593,3594],{},"ghcr.io\u002Fcomponents-web-app\u002Fcwa-php-base:\u003Crelease>",", so the build pulls the base and compiles no Go and installs no PHP extensions. Locally the same choice is ",[629,3597,3577],{}," in the root ",[629,3600,3601],{},".env",[645,3603,3605],{"id":3604},"autoscaling-and-sizing","Autoscaling and sizing",[611,3607,3608,3609,3611,3612,3615],{},"The php pod and the Nuxt pod scale separately. The ",[629,3610,864],{}," variables control php and the ",[629,3613,3614],{},"CWA_CI_PWA_*"," variables control Nuxt.",[1742,3617,3619],{"id":3618},"php-api","php (API)",[956,3621,3622,3634],{},[959,3623,3624],{},[962,3625,3626,3628,3630,3632],{},[965,3627,967],{},[965,3629,970],{},[965,3631,635],{},[965,3633,975],{},[977,3635,3636,3656,3678,3704,3734,3753,3772,3792,3816,3839,3881,3921,3948,3979,4007,4025,4045,4062,4088,4112],{},[962,3637,3638,3643,3648,3653],{},[982,3639,3640],{},[629,3641,3642],{},"CWA_CI_API_REPLICA_COUNT",[982,3644,3645],{},[629,3646,3647],{},"1",[982,3649,3650],{},[629,3651,3652],{},"replicaCount",[982,3654,3655],{},"Used only when autoscaling is off",[962,3657,3658,3663,3667,3672],{},[982,3659,3660],{},[629,3661,3662],{},"CWA_CI_API_AUTOSCALE",[982,3664,3665],{},[629,3666,2598],{},[982,3668,3669],{},[629,3670,3671],{},"autoscaling.enabled",[982,3673,3674,3675,3677],{},"Must stay ",[629,3676,2598],{}," in scaled mode, or the deploy refuses",[962,3679,3680,3685,3689,3694],{},[982,3681,3682],{},[629,3683,3684],{},"CWA_CI_API_AUTOSCALE_MIN",[982,3686,3687],{},[629,3688,3647],{},[982,3690,3691],{},[629,3692,3693],{},"autoscaling.minReplicas",[982,3695,3696,3697,3699,3700,3703],{},"Also the number of pods while the max is ",[629,3698,3647],{},", when the chart renders no HPA. ",[629,3701,3702],{},"2"," in scaled mode.",[962,3705,3706,3711,3715,3720],{},[982,3707,3708],{},[629,3709,3710],{},"CWA_CI_API_AUTOSCALE_MAX",[982,3712,3713],{},[629,3714,3647],{},[982,3716,3717],{},[629,3718,3719],{},"autoscaling.maxReplicas",[982,3721,3722,3724,3725,3727,3728,3730,3731,3703],{},[629,3723,3647],{}," on purpose, see below. At ",[629,3726,3647],{}," there is no php HPA; above ",[629,3729,3647],{}," the chart adds one. ",[629,3732,3733],{},"3",[962,3735,3736,3741,3746,3751],{},[982,3737,3738],{},[629,3739,3740],{},"CWA_CI_API_AUTOSCALE_CPU_PERCENT",[982,3742,3743],{},[629,3744,3745],{},"90",[982,3747,3748],{},[629,3749,3750],{},"autoscaling.targetCPUUtilizationPercentage",[982,3752],{},[962,3754,3755,3760,3764,3769],{},[982,3756,3757],{},[629,3758,3759],{},"CWA_CI_API_AUTOSCALE_MEMORY_PERCENT",[982,3761,3762],{},[629,3763,3745],{},[982,3765,3766],{},[629,3767,3768],{},"autoscaling.targetMemoryUtilizationPercentage",[982,3770,3771],{},"Ignored in scaled mode, whose HPA scales on CPU only.",[962,3773,3774,3779,3785,3790],{},[982,3775,3776],{},[629,3777,3778],{},"CWA_CI_API_CPU_REQUEST",[982,3780,3781,3784],{},[629,3782,3783],{},"100m"," on every track",[982,3786,3787],{},[629,3788,3789],{},"php.resources.requests.cpu",[982,3791],{},[962,3793,3794,3799,3809,3814],{},[982,3795,3796],{},[629,3797,3798],{},"CWA_CI_API_MEMORY_REQUEST",[982,3800,3801,3804,3805,3808],{},[629,3802,3803],{},"350Mi"," for production and canary, ",[629,3806,3807],{},"256Mi"," otherwise",[982,3810,3811],{},[629,3812,3813],{},"php.resources.requests.memory",[982,3815],{},[962,3817,3818,3823,3827,3832],{},[982,3819,3820],{},[629,3821,3822],{},"CWA_CI_API_MEMORY_LIMIT",[982,3824,3825,3784],{},[629,3826,2810],{},[982,3828,3829],{},[629,3830,3831],{},"php.resources.limits.memory",[982,3833,1472,3834,3838],{},[620,3835,3837],{"href":3836},"#php-memory-budget","php memory budget"," must fit it",[962,3840,3841,3846,3848,3853],{},[982,3842,3843],{},[629,3844,3845],{},"CWA_CI_API_GOMEMLIMIT",[982,3847,1956],{},[982,3849,3850],{},[629,3851,3852],{},"php.goMemLimit",[982,3854,3855,3856,3858,3859,3862,3863,857,3866,3869,3870,3873,3874,3876,3877,3880],{},"Empty: what the php memory budget leaves of ",[629,3857,3822],{},", at least ",[629,3860,3861],{},"64MiB",", when the limit is in ",[629,3864,3865],{},"Mi",[629,3867,3868],{},"Gi",". A Go value such as ",[629,3871,3872],{},"800MiB"," overrides it, and ",[629,3875,3159],{}," leaves ",[629,3878,3879],{},"GOMEMLIMIT"," unset",[962,3882,3883,3895,3909,3914],{},[982,3884,3885,655,3888,655,3890,655,3893],{},[629,3886,3887],{},"CWA_CI_API_WARM_HTTP_CACHE_MAX_TIME_PRODUCTION",[629,3889,3342],{},[629,3891,3892],{},"_STAGING",[629,3894,3345],{},[982,3896,3897,3898,3901,3902,3905,3906],{},"production and canary: ",[629,3899,3900],{},"CWA_CI_API_WARM_HTTP_CACHE_MAX_TIME",", else ",[629,3903,3904],{},"60","; staging and review: ",[629,3907,3908],{},"0",[982,3910,3911],{},[629,3912,3913],{},"php.warmHttpCacheMaxTime",[982,3915,3916,3917,3920],{},"Seconds for each pass of the ",[629,3918,3919],{},"\u002F_api"," warm on that track. Staging and review apps don't warm unless you set theirs, since visitors don't use them.",[962,3922,3923,3927,3931,3935],{},[982,3924,3925],{},[629,3926,3900],{},[982,3928,3929],{},[629,3930,3904],{},[982,3932,3933],{},[629,3934,3913],{},[982,3936,3916,3937,3939,3940,3942,3943,3947],{},[629,3938,3919],{}," warm on production and canary, when their own setting above isn't set: a new php pod runs two (gzip, then br) before its probes start, and the deploy runs the same two after a full flush. ",[629,3941,3908],{}," turns both warms off. See ",[620,3944,3946],{"href":3945},"\u002Fdeployment\u002Fkubernetes#warming-the-api-cache-on-start","Kubernetes › Warming the API cache on start",". In scaled mode a new pod skips its own warm, and the deploy's warm goes through the API Service.",[962,3949,3950,3961,3966,3971],{},[982,3951,3952,655,3955,655,3957,655,3959],{},[629,3953,3954],{},"CWA_CI_API_SCALED_PRODUCTION",[629,3956,3892],{},[629,3958,3342],{},[629,3960,3345],{},[982,3962,3963,3964],{},"off unless ",[629,3965,1175],{},[982,3967,3968],{},[629,3969,3970],{},"scaled.enabled",[982,3972,3973,3974,3976,3977,713],{},"Turns scaled mode on for that track: two or more php pods sharing one Redis cache and one Mercure hub. One name per track, because GitLab's staging job runs under the ",[629,3975,950],{}," environment. See ",[620,3978,557],{"href":558},[962,3980,3981,3986,3990,3995],{},[982,3982,3983],{},[629,3984,3985],{},"CWA_CI_API_REDIS_MEMORY",[982,3987,3988],{},[629,3989,3807],{},[982,3991,3992],{},[629,3993,3994],{},"scaled.redis.maxMemoryMi",[982,3996,3997,3998,857,4000,4002,4003,713],{},"Scaled mode only. How much the cache may hold, in ",[629,3999,3865],{},[629,4001,3868],{},"; any other form fails the deploy. The Redis container gets about 1.4 times this plus 16Mi. See ",[620,4004,4006],{"href":4005},"\u002Fdeployment\u002Fscaled-mode#the-shared-cache","Scaled Mode › The shared cache",[962,4008,4009,4014,4017,4022],{},[982,4010,4011],{},[629,4012,4013],{},"REDIS_PASSWORD",[982,4015,4016],{},"the deployed one, else generated",[982,4018,4019],{},[629,4020,4021],{},"scaled.redis.password",[982,4023,4024],{},"Scaled mode only. Optional: a deploy keeps the live Redis password, or generates one on the first scaled deploy.",[962,4026,4027,4032,4036,4038],{},[982,4028,4029],{},[629,4030,4031],{},"CWA_CI_API_WARM_SURGE",[982,4033,4034],{},[629,4035,3908],{},[982,4037,642],{},[982,4039,4040,4041,713],{},"Scaled mode only. Extra php pods for a deploy's flush and warm: the deploy raises the HPA's minimum (and maximum when needed), then puts both back. See ",[620,4042,4044],{"href":4043},"\u002Fdeployment\u002Fscaled-mode#more-pods-for-the-warm","Scaled Mode › More pods for the warm",[962,4046,4047,4052,4057,4059],{},[982,4048,4049],{},[629,4050,4051],{},"CWA_CI_API_WARM_SURGE_TIMEOUT",[982,4053,4054],{},[629,4055,4056],{},"300",[982,4058,642],{},[982,4060,4061],{},"Scaled mode only. Seconds the deploy waits for the surge's pods before it warms on the pods there are.",[962,4063,4064,4069,4073,4075],{},[982,4065,4066],{},[629,4067,4068],{},"CWA_CI_API_WARM_CONCURRENCY",[982,4070,4071],{},[629,4072,3702],{},[982,4074,642],{},[982,4076,4077,4078,4081,4082,740,4084,713],{},"Scaled mode only. Requests the deploy's warm sends at once ",[615,4079,4080],{},"for each php pod",", multiplied by the pods during the warm (the minimum plus a surge's); at least ",[629,4083,3647],{},[620,4085,4087],{"href":4086},"\u002Fdeployment\u002Fscaled-mode#every-deploy-flushes-and-warms","Scaled Mode › Every deploy flushes and warms",[962,4089,4090,4095,4097,4102],{},[982,4091,4092],{},[629,4093,4094],{},"CWA_CI_API_REDIS_WATCH",[982,4096,3133],{},[982,4098,4099],{},[629,4100,4101],{},"scaled.redisWatch.enabled",[982,4103,4104,4105,4107,4108,713],{},"Scaled mode only. ",[629,4106,1186],{}," turns off the Redis watch CronJob. See ",[620,4109,4111],{"href":4110},"\u002Fdeployment\u002Fscaled-mode#the-redis-watch","Scaled Mode › The Redis watch",[962,4113,4114,4119,4124,4129],{},[982,4115,4116],{},[629,4117,4118],{},"CWA_CI_API_REDIS_WATCH_SCHEDULE",[982,4120,4121],{},[629,4122,4123],{},"*\u002F2 * * * *",[982,4125,4126],{},[629,4127,4128],{},"scaled.redisWatch.schedule",[982,4130,4131],{},"Scaled mode only. How often the Redis watch runs.",[611,4133,4134,2649,4136,4138,4139,4143,4144,624],{},[629,4135,3710],{},[629,4137,3647],{}," on purpose. The php pod keeps its cache and its Mercure data in memory, so a second pod would not see purges made by the first. See ",[620,4140,4142],{"href":4141},"\u002Fdeployment\u002Fkubernetes#autoscaling","Kubernetes › Autoscaling",". Scaled mode moves both out of the pod (see ",[620,4145,557],{"href":558},[611,4147,4148,4151,4152,857,4154,4156,4157,4159,4160,4156,4162,736,4164,4167,4168,4171,4172,4175,4176,4178,4179,4183,4184,713],{},[615,4149,4150],{},"The deploy refuses more than one API pod."," A ",[629,4153,3684],{},[629,4155,3710],{}," above ",[629,4158,3647],{}," while the autoscaler is on, or a ",[629,4161,3642],{},[629,4163,3647],{},[629,4165,4166],{},"CWA_CI_API_AUTOSCALE=false",", fails it before anything changes in the cluster, and so does a ",[629,4169,4170],{},"CWA_API_CADDY_CACHE_STORAGE"," that names ",[629,4173,4174],{},"redis",". As in the chart, the setting that has no effect is ignored: a ",[629,4177,3642],{}," beside the autoscaler isn't refused. See ",[620,4180,4182],{"href":4181},"\u002Fdeployment\u002Fkubernetes#php-stays-at-one-replica","Kubernetes & Helm › php stays at one replica",". Scaled mode refuses a few more settings; see ",[620,4185,4187],{"href":4186},"\u002Fdeployment\u002Fscaled-mode#what-the-deploy-refuses","Scaled Mode › What the deploy refuses",[1742,4189,3837],{"id":4190},"php-memory-budget",[611,4192,4193],{},"These size the php container's memory:",[956,4195,4196,4208],{},[959,4197,4198],{},[962,4199,4200,4202,4204,4206],{},[965,4201,967],{},[965,4203,970],{},[965,4205,635],{},[965,4207,975],{},[977,4209,4210,4233,4263,4290],{},[962,4211,4212,4217,4222,4227],{},[982,4213,4214],{},[629,4215,4216],{},"CWA_CI_API_WORKERS",[982,4218,4219],{},[629,4220,4221],{},"4",[982,4223,4224],{},[629,4225,4226],{},"php.workers",[982,4228,4229,4230,1646],{},"FrankenPHP's worker threads (",[629,4231,4232],{},"FRANKENPHP_WORKER_NUM",[962,4234,4235,4240,4245,4250],{},[982,4236,4237],{},[629,4238,4239],{},"CWA_CI_API_PHP_MEMORY_LIMIT",[982,4241,4242],{},[629,4243,4244],{},"128Mi",[982,4246,4247],{},[629,4248,4249],{},"php.memoryLimit",[982,4251,4252,4253,824,4256,4259,4260,1646],{},"Each request's PHP ",[629,4254,4255],{},"memory_limit",[629,4257,4258],{},"FRANKENPHP_MEMORY_LIMIT",", as ",[629,4261,4262],{},"128M",[962,4264,4265,4270,4275,4280],{},[982,4266,4267],{},[629,4268,4269],{},"CWA_CI_API_IMAGINE_MEMORY_LIMIT",[982,4271,4272],{},[629,4273,4274],{},"320Mi",[982,4276,4277],{},[629,4278,4279],{},"php.imagineMemoryLimit",[982,4281,4282,4283,4286,4287],{},"The limit the bundle raises a request to while it generates an imagine filter. Keep it equal to the bundle's ",[629,4284,4285],{},"imagine.memory_limit",", whose default is ",[629,4288,4289],{},"320M",[962,4291,4292,4297,4302,4307],{},[982,4293,4294],{},[629,4295,4296],{},"CWA_CI_API_RESERVED_MEMORY",[982,4298,4299],{},[629,4300,4301],{},"160Mi",[982,4303,4304],{},[629,4305,4306],{},"php.reservedMemory",[982,4308,4309],{},"opcache, APCu, the binaries and a console command run in the pod",[611,4311,4312],{},"The budget is:",[1764,4314,4317],{"className":4315,"code":4316,"language":1769},[1767],"workers × PHP memory limit + (imagine limit − PHP memory limit) + reserved + Go ≤ CWA_CI_API_MEMORY_LIMIT\n",[629,4318,4316],{"__ignoreMap":1770},[611,4320,4321,4322,4324],{},"Go (Caddy, Souin and Mercure) gets what is left, as ",[629,4323,3879],{},": with the defaults, 4 × 128 + 192 + 160 = 864Mi, which leaves Go 160Mi of 1Gi. The imagine limit counts once: the budget allows for one request generating a filter at a time.",[611,4326,4327,4330,4331,2944,4333,4335,4336,4338,4339,4342,4343,4345,4346,4348,4349,4351],{},[615,4328,4329],{},"The deploy fails before it changes anything in the cluster"," when the budget leaves Go under 64Mi, or when a value isn't a whole number or an ",[629,4332,3865],{},[629,4334,3868],{}," quantity. The log shows the sum and what to change. With a ",[629,4337,3822],{}," in another form, such as ",[629,4340,4341],{},"1G",", the check is skipped and ",[629,4344,3879],{}," is left unset unless ",[629,4347,3845],{}," sets it. So when you raise the workers or the PHP memory limit, raise ",[629,4350,3822],{}," too.",[611,4353,4354,4355,4358,4359,4361,4362,4364,4365,4368,4369,713],{},"Console commands, such as the orphan scan or a ",[629,4356,4357],{},"kubectl exec"," into the pod, don't use ",[629,4360,4258],{},". They run at the ",[629,4363,4255],{}," of ",[629,4366,4367],{},"256M"," in ",[629,4370,4371],{},"api\u002Ffrankenphp\u002Fconf.d\u002F10-app.ini",[935,4373,4374],{"icon":1714,"color":1715},[611,4375,4376,691,4385,665,4387,4389,4390,4393],{},[615,4377,4378,4379,4381,4382,713],{},"Set the worker count with ",[629,4380,4216],{},", not ",[629,4383,4384],{},"CWA_API_FRANKENPHP_WORKER_NUM",[629,4386,4232],{},[629,4388,4258],{}," are ",[620,4391,4392],{"href":2205},"reserved names",", so the deploy refuses them as site settings.",[611,4395,4396,4397,4400,4401,713],{},"The template's ",[629,4398,4399],{},"Image"," entity accepts uploads up to 30 megapixels: what the 320M imagine limit can thumbnail with the template's libvips driver. If you switch the driver back to GD, lower it to 20 megapixels. See ",[620,4402,4404],{"href":4403},"\u002Fapi\u002Fcomponents\u002Fannotations\u002Fuploadable#capping-uploads-to-fit-the-budget","Capping Uploads to Fit the Budget",[1742,4406,4408],{"id":4407},"nuxt-ssr","Nuxt (SSR)",[956,4410,4411,4423],{},[959,4412,4413],{},[962,4414,4415,4417,4419,4421],{},[965,4416,967],{},[965,4418,970],{},[965,4420,635],{},[965,4422,975],{},[977,4424,4425,4443,4461,4483,4504,4528,4545,4572],{},[962,4426,4427,4432,4436,4441],{},[982,4428,4429],{},[629,4430,4431],{},"CWA_CI_PWA_REPLICA_COUNT",[982,4433,4434],{},[629,4435,3647],{},[982,4437,4438],{},[629,4439,4440],{},"pwa.replicaCount",[982,4442,3655],{},[962,4444,4445,4450,4454,4459],{},[982,4446,4447],{},[629,4448,4449],{},"CWA_CI_PWA_AUTOSCALE",[982,4451,4452],{},[629,4453,2598],{},[982,4455,4456],{},[629,4457,4458],{},"pwa.autoscaling.enabled",[982,4460],{},[962,4462,4463,4467,4475,4480],{},[982,4464,4465],{},[629,4466,860],{},[982,4468,4469,4471,4472,4474],{},[629,4470,3702],{}," for production, ",[629,4473,3647],{}," for canary and every other track",[982,4476,4477],{},[629,4478,4479],{},"pwa.autoscaling.minReplicas",[982,4481,4482],{},"A canary runs beside production, which keeps serving, so one pod is enough",[962,4484,4485,4490,4497,4502],{},[982,4486,4487],{},[629,4488,4489],{},"CWA_CI_PWA_AUTOSCALE_MAX",[982,4491,4492,3804,4495,3808],{},[629,4493,4494],{},"6",[629,4496,3702],{},[982,4498,4499],{},[629,4500,4501],{},"pwa.autoscaling.maxReplicas",[982,4503],{},[962,4505,4506,4511,4517,4522],{},[982,4507,4508],{},[629,4509,4510],{},"CWA_CI_PWA_AUTOSCALE_CPU_PERCENT",[982,4512,4513,4516],{},[629,4514,4515],{},"175"," (of the CPU request)",[982,4518,4519],{},[629,4520,4521],{},"pwa.autoscaling.targetCPUUtilizationPercentage",[982,4523,4524,4525],{},"Change it together with ",[629,4526,4527],{},"CWA_CI_PWA_CPU_REQUEST",[962,4529,4530,4535,4538,4543],{},[982,4531,4532],{},[629,4533,4534],{},"CWA_CI_PWA_AUTOSCALE_MEMORY_PERCENT",[982,4536,4537],{},"unset (no memory target)",[982,4539,4540],{},[629,4541,4542],{},"pwa.autoscaling.targetMemoryUtilizationPercentage",[982,4544],{},[962,4546,4547,4554,4562,4570],{},[982,4548,4549,1749,4551],{},[629,4550,4527],{},[629,4552,4553],{},"CWA_CI_PWA_CPU_LIMIT",[982,4555,4556,4558,4559],{},[629,4557,3783],{}," on every track \u002F ",[629,4560,4561],{},"1000m",[982,4563,4564,1749,4567],{},[629,4565,4566],{},"pwa.resources.requests.cpu",[629,4568,4569],{},"pwa.resources.limits.cpu",[982,4571],{},[962,4573,4574,4582,4591,4599],{},[982,4575,4576,1749,4579],{},[629,4577,4578],{},"CWA_CI_PWA_MEMORY_REQUEST",[629,4580,4581],{},"CWA_CI_PWA_MEMORY_LIMIT",[982,4583,4584,3804,4586,4588,4589],{},[629,4585,4301],{},[629,4587,4244],{}," otherwise \u002F ",[629,4590,2810],{},[982,4592,4593,1749,4596],{},[629,4594,4595],{},"pwa.resources.requests.memory",[629,4597,4598],{},"pwa.resources.limits.memory",[982,4600],{},[611,4602,4603,4611,4612,713],{},[615,4604,4605,4606,665,4608,4610],{},"Change ",[629,4607,4527],{},[629,4609,4510],{}," together."," The target is a percentage of the request, and request × target should stay near 175m, about one render a second. See ",[620,4613,4615],{"href":4614},"\u002Fdeployment\u002Fkubernetes#nuxt-scales-out","Kubernetes › Nuxt scales out",[611,4617,4618],{},"The minimums, maximums and memory requests depend on the environment, so review apps and staging don't reserve as much as production. Limits are the same everywhere.",[611,4620,4621,4622,4624],{},"The Nuxt HPA scales on CPU only. Server rendering is CPU-bound, and an extra pod doesn't relieve memory, because each pod keeps its own caches. Node also rarely hands memory back, and an HPA only scales down when every metric is under its target, so a memory target would add pods under normal load and then keep them. Set ",[629,4623,4534],{}," if you want one anyway. If you set a variable, your value is used in every environment.",[645,4626,4628],{"id":4627},"orphan-scan","Orphan scan",[611,4630,1472,4631,4635,4636,4639,4640,4642],{},[620,4632,4634],{"href":4633},"\u002Fdeployment\u002Fkubernetes#daily-orphan-scan","daily orphan scan"," is a CronJob that runs ",[629,4637,4638],{},"scan-orphaned"," and emails ",[629,4641,785],{}," when the orphans change.",[956,4644,4645,4657],{},[959,4646,4647],{},[962,4648,4649,4651,4653,4655],{},[965,4650,967],{},[965,4652,970],{},[965,4654,635],{},[965,4656,975],{},[977,4658,4659,4684,4704],{},[962,4660,4661,4666,4672,4677],{},[982,4662,4663],{},[629,4664,4665],{},"CWA_CI_ORPHAN_SCAN",[982,4667,4668,4471,4670,3808],{},[629,4669,1175],{},[629,4671,1186],{},[982,4673,4674],{},[629,4675,4676],{},"cronjobs.orphanScan.enabled",[982,4678,4679,857,4681,4683],{},[629,4680,1175],{},[629,4682,1186],{},". Your value is used on every track",[962,4685,4686,4691,4696,4701],{},[982,4687,4688],{},[629,4689,4690],{},"CWA_CI_ORPHAN_SCAN_SCHEDULE",[982,4692,4693],{},[629,4694,4695],{},"0 3 * * *",[982,4697,4698],{},[629,4699,4700],{},"cronjobs.orphanScan.schedule",[982,4702,4703],{},"Cron schedule",[962,4705,4706,4711,4716,4721],{},[982,4707,4708],{},[629,4709,4710],{},"CWA_CI_ORPHAN_SCAN_TIMEZONE",[982,4712,4713],{},[629,4714,4715],{},"Europe\u002FLondon",[982,4717,4718],{},[629,4719,4720],{},"cronjobs.orphanScan.timeZone",[982,4722,4723],{},"Time zone for the schedule. Needs Kubernetes 1.27+; older clusters use UTC",[611,4725,4726],{},"Staging and canary are off by default because they can share production's database, so their scans would repeat production's alert. Review apps are short-lived.",[611,4728,4729,4730,4732,4733,4368,4736,4739,4740,4743,4744,713],{},"The email goes to ",[629,4731,785],{},", the same address the site sends from (",[629,4734,4735],{},"orphaned_resources.notify.recipients",[629,4737,4738],{},"api\u002Fconfig\u002Fpackages\u002Fsilverback_api_components.yaml","). Both a bare address and the ",[629,4741,4742],{},"Name \u003Caddress>"," form work; see ",[620,4745,4747],{"href":4746},"\u002Fapi\u002Forphaned-data#emailing-when-orphans-change","Emailing when orphans change",[611,4749,4750,4751,4753,4754,4756,4757,4759,4760,4762,4763,4765,4766,933],{},"An empty ",[629,4752,785],{}," means no email. An unset ",[629,4755,782],{}," renders as ",[629,4758,1645],{},", so nothing is sent. The GitHub deploy jobs pass ",[629,4761,782],{}," (a secret), ",[629,4764,785],{}," and the ",[629,4767,4768],{},"CWA_CI_ORPHAN_SCAN*",[645,4770,4772],{"id":4771},"origin-protection","Origin protection",[611,4774,4775,4776,857,4779,4782],{},"These protect php and Nuxt from requests that get past the cache. Every one is optional: leave it unset, or empty, and the default in ",[629,4777,4778],{},"api\u002Ffrankenphp\u002FCaddyfile",[629,4780,4781],{},"worker.Caddyfile"," applies. On GitHub, set them as Actions variables and they reach the deploy without a workflow change.",[611,4784,4785,4786,4789,4790,4793,4794,4797,4798,4801,4802,4805,4806,4810,4811,4814],{},"The Caddy settings are ",[620,4787,4788],{"href":880},"site settings",": set ",[629,4791,4792],{},"CWA_API_\u003CNAME>"," in CI and the php container gets ",[629,4795,4796],{},"\u003CNAME>",", so ",[629,4799,4800],{},"CWA_API_RATE_LIMIT_EVENTS"," arrives as ",[629,4803,4804],{},"RATE_LIMIT_EVENTS",". The local compose stack uses the bare names (see ",[620,4807,4809],{"href":4808},"\u002Fdeployment\u002Fdocker#origin-protection-settings","Docker › Origin protection settings","). The ",[629,4812,4813],{},"CWA_CI_INGRESS_*"," ones are deploy switches, which the deploy turns into ingress annotations.",[956,4816,4817,4829],{},[959,4818,4819],{},[962,4820,4821,4823,4825,4827],{},[965,4822,967],{},[965,4824,970],{},[965,4826,635],{},[965,4828,975],{},[977,4830,4831,4866,4886,4908,4930,4956,4984,5016,5042,5064],{},[962,4832,4833,4838,4843,4848],{},[982,4834,4835],{},[629,4836,4837],{},"CWA_API_CACHE_QUERY_ALLOWLIST",[982,4839,4840],{},[629,4841,4842],{},"page search order",[982,4844,4845],{},[629,4846,4847],{},"siteEnv.apiSecret",[982,4849,4850,4851,4854,4855,1940,4858,4861,4862],{},"Query parameters that pages keep, space-separated. A name also keeps ",[629,4852,4853],{},"name[...]",". Add your pages' own parameters, but never ",[629,4856,4857],{},"cwa_force",[629,4859,4860],{},"*"," turns it off. See ",[620,4863,4865],{"href":4864},"\u002Fdeployment\u002Forigin-protection#query-parameters-on-pages","Query parameters on pages",[962,4867,4868,4873,4877,4881],{},[982,4869,4870],{},[629,4871,4872],{},"CWA_API_RATE_LIMIT_ENABLED",[982,4874,4875],{},[629,4876,2598],{},[982,4878,4879],{},[629,4880,4847],{},[982,4882,4883,4885],{},[629,4884,2797],{}," turns off the per-visitor limit on cache misses",[962,4887,4888,4892,4897,4901],{},[982,4889,4890],{},[629,4891,4800],{},[982,4893,4894],{},[629,4895,4896],{},"3000",[982,4898,4899],{},[629,4900,4847],{},[982,4902,4903,4904,4907],{},"Requests per window per visitor. Cache hits, ",[629,4905,4906],{},"\u002F_nuxt",", Mercure and private addresses aren't counted",[962,4909,4910,4915,4920,4924],{},[982,4911,4912],{},[629,4913,4914],{},"CWA_API_RATE_LIMIT_WINDOW",[982,4916,4917],{},[629,4918,4919],{},"1m",[982,4921,4922],{},[629,4923,4847],{},[982,4925,4926,4927],{},"Window length, as a Go duration such as ",[629,4928,4929],{},"10s",[962,4931,4932,4937,4942,4946],{},[982,4933,4934],{},[629,4935,4936],{},"CWA_API_CADDY_TRUSTED_PROXIES",[982,4938,4939],{},[629,4940,4941],{},"private_ranges",[982,4943,4944],{},[629,4945,4847],{},[982,4947,4948,4949,4952,4953],{},"Proxies whose ",[629,4950,4951],{},"X-Forwarded-For"," is believed. Add a public proxy's ranges, e.g. a Google load balancer: ",[629,4954,4955],{},"private_ranges 35.191.0.0\u002F16 130.211.0.0\u002F22",[962,4957,4958,4963,4966,4970],{},[982,4959,4960],{},[629,4961,4962],{},"CWA_API_CLOUDFLARE_IP_RANGES",[982,4964,4965],{},"Cloudflare's published ranges",[982,4967,4968],{},[629,4969,4847],{},[982,4971,4972,4973,4976,4977,4983],{},"Only requests from these addresses are counted by ",[629,4974,4975],{},"CF-Connecting-IP",". Update it if Cloudflare changes ",[620,4978,4982],{"href":4979,"rel":4980},"https:\u002F\u002Fwww.cloudflare.com\u002Fips\u002F",[4981],"nofollow","its ranges"," before a template release does",[962,4985,4986,4991,4996,5000],{},[982,4987,4988],{},[629,4989,4990],{},"CWA_API_FRANKENPHP_MAX_WAIT_TIME",[982,4992,4993],{},[629,4994,4995],{},"5s",[982,4997,4998],{},[629,4999,4847],{},[982,5001,5002,5003,5006,5007,1940,5010,4861,5012],{},"A request that waits this long for a free php worker gets a ",[629,5004,5005],{},"503"," at once instead of queueing into a ",[629,5008,5009],{},"504",[629,5011,3908],{},[620,5013,5015],{"href":5014},"\u002Fdeployment\u002Forigin-protection#load-shedding","Origin Protection › Load shedding",[962,5017,5018,5023,5025,5032],{},[982,5019,5020],{},[629,5021,5022],{},"CWA_CI_INGRESS_RATE_LIMIT_RPS",[982,5024,2783],{},[982,5026,5027,691,5029],{},[629,5028,1096],{},[629,5030,5031],{},"limit-rps",[982,5033,5034,5035,740,5037,5041],{},"ingress-nginx ",[629,5036,5031],{},[620,5038,5040],{"href":5039},"\u002Fdeployment\u002Forigin-protection#ingress-rate-limits","Origin Protection › Ingress rate limits"," before you set it",[962,5043,5044,5049,5052,5059],{},[982,5045,5046],{},[629,5047,5048],{},"CWA_CI_INGRESS_RATE_LIMIT_BURST_MULTIPLIER",[982,5050,5051],{},"unset (nginx's default, 5)",[982,5053,5054,691,5056],{},[629,5055,1096],{},[629,5057,5058],{},"limit-burst-multiplier",[982,5060,5061,5062],{},"Used only with ",[629,5063,5022],{},[962,5065,5066,5071,5073,5080],{},[982,5067,5068],{},[629,5069,5070],{},"CWA_CI_INGRESS_RATE_LIMIT_CONNECTIONS",[982,5072,1782],{},[982,5074,5075,691,5077],{},[629,5076,1096],{},[629,5078,5079],{},"limit-connections",[982,5081,5061,5082],{},[629,5083,5022],{},[611,5085,5086,5087,5091],{},"See ",[620,5088,5090],{"href":5089},"\u002Fdeployment\u002Forigin-protection#rate-limit-on-cache-misses","Rate limit on cache misses"," for what is counted and how the visitor's address is found.",[935,5093,5094],{"icon":1714,"color":1715},[611,5095,5096,5099,5100,5103,5104,5107,5108,5113,5114,5117,5118,713],{},[615,5097,5098],{},"The per-visitor rate limit needs the cluster's ingress controller to keep the client address."," Set ",[629,5101,5102],{},"externalTrafficPolicy: Local"," on the ingress controller's own Service, ideally with 2 replicas and a PodDisruptionBudget, as the template's own cluster runs it. With the default ",[629,5105,5106],{},"Cluster",", every request seems to come from a node's private address, so every visitor looks internal and the limit never applies (",[620,5109,5112],{"href":5110,"rel":5111},"https:\u002F\u002Fgithub.com\u002Fcomponents-web-app\u002Fcomponents-web-app\u002Fissues\u002F135",[4981],"#135","). Check ",[629,5115,5116],{},"visitor_ip"," in php's access log. See ",[620,5119,5040],{"href":5039},[935,5121,5122],{"icon":937},[611,5123,5124,5127,5128,5130,5131,5133],{},[629,5125,5126],{},"CADDY_EDGE"," isn't a deploy variable. Only ",[629,5129,1690],{}," sets it, and the chart doesn't pass it, so on Kubernetes it stays ",[629,5132,2797],{},". That's right there, because Caddy sits behind the ingress.",[645,5135,5137],{"id":5136},"http-cache","HTTP cache",[611,5139,5140,5141,5144,5145,713],{},"Souin's store and how long it waits for php or Nuxt, in the Caddyfile's ",[629,5142,5143],{},"cache"," block. See ",[620,5146,5148],{"href":5147},"\u002Fdeployment\u002Fdocker#the-cache-store-and-backend-timeout","Docker › The cache store and backend timeout",[956,5150,5151,5163],{},[959,5152,5153],{},[962,5154,5155,5157,5159,5161],{},[965,5156,967],{},[965,5158,970],{},[965,5160,635],{},[965,5162,975],{},[977,5164,5165,5194,5222],{},[962,5166,5167,5172,5177,5181],{},[982,5168,5169],{},[629,5170,5171],{},"CWA_API_CADDY_CACHE_BACKEND_TIMEOUT",[982,5173,5174],{},[629,5175,5176],{},"30s",[982,5178,5179],{},[629,5180,4847],{},[982,5182,5183,5184,5186,5187,857,5190,5193],{},"How long Souin waits for a response before it answers ",[629,5185,5009],{},". A Go duration, such as ",[629,5188,5189],{},"45s",[629,5191,5192],{},"1m30s","; the deploy refuses anything else, because Souin would ignore it and wait its own 10 seconds",[962,5195,5196,5200,5205,5209],{},[982,5197,5198],{},[629,5199,4170],{},[982,5201,5202],{},[629,5203,5204],{},"otter",[982,5206,5207],{},[629,5208,4847],{},[982,5210,5211,5212,5216,5217,5219,5220,624],{},"The store, one of those built into the image (see ",[620,5213,5215],{"href":5214},"\u002Fdeployment\u002Fdocker#php-dockerfile-stages","Docker › PHP Dockerfile stages","). The deploy doesn't check it, so a store the image doesn't have only shows up when Caddy starts. Check the image's build first. A store that names ",[629,5218,4174],{}," fails the deploy: a shared Redis is part of scaled mode. In scaled mode it must be unset, because the store is the release's own Redis, and a value fails the deploy (see ",[620,5221,4187],{"href":4186},[962,5223,5224,5229,5231,5236],{},[982,5225,5226],{},[629,5227,5228],{},"CADDY_CACHE_EXTRA_CONFIG",[982,5230,1956],{},[982,5232,5233],{},[629,5234,5235],{},"php.caddy.extraConfig",[982,5237,5238,5239,5241,5242,5245],{},"Other Souin directives for the ",[629,5240,5143],{}," block, one per line. Never a store or a ",[629,5243,5244],{},"timeout"," block: the deploy refuses both and names the variable to set instead",[611,5247,5248,5249,665,5252,5255],{},"Locally, compose passes the bare ",[629,5250,5251],{},"CADDY_CACHE_BACKEND_TIMEOUT",[629,5253,5254],{},"CADDY_CACHE_STORAGE"," through when they're set. The store is otter everywhere, on the dev stack, in the browser tests and on every deploy.",[611,5257,5258,5265,5266,5268,5269,5272],{},[615,5259,5260,5261,4368,5263,713],{},"Why the deploy refuses a store or ",[629,5262,5244],{},[629,5264,5228],{}," Souin accepts both without an error, and the result isn't what the Caddyfile says. A second store line leaves two stores configured, and a second ",[629,5267,5244],{}," block replaces the first one whole, so ",[629,5270,5271],{},"timeout { cache 1s }"," alone puts the backend timeout back to 10 seconds. The check reads the first word of each line, and fails like this:",[1764,5274,5277],{"className":5275,"code":5276,"language":1769},[1767],"The Souin cache settings would be ignored or replaced:\n  'otter' is a store: set CWA_API_CADDY_CACHE_STORAGE instead (default otter)\nCADDY_CACHE_EXTRA_CONFIG is for other Souin directives only; delete it if it only restated these.\n",[629,5278,5276],{"__ignoreMap":1770},[611,5280,5281,5282,5284,5285,5288],{},"The chart writes ",[629,5283,5228],{}," to its Secret key ",[629,5286,5287],{},"caddy-cache-extra-config"," only when the variable is set.",[645,5290,5292],{"id":5291},"cdn-purging","CDN purging",[611,5294,5295,5296,713],{},"Only needed when a CDN caches your pages. See ",[620,5297,573],{"href":5298},"\u002Fdeployment\u002Fcloudflare#purges-must-reach-cloudflare",[956,5300,5301,5313],{},[959,5302,5303],{},[962,5304,5305,5307,5309,5311],{},[965,5306,967],{},[965,5308,970],{},[965,5310,635],{},[965,5312,975],{},[977,5314,5315,5338,5359,5396,5417,5437],{},[962,5316,5317,5321,5326,5331],{},[982,5318,5319],{},[629,5320,916],{},[982,5322,5323],{},[629,5324,5325],{},"strategy hard",[982,5327,5328],{},[629,5329,5330],{},"php.caddy.cdnConfig",[982,5332,5333,5334,5337],{},"Souin's ",[629,5335,5336],{},"cdn"," block, one setting per line. Leave it unset without a CDN. See below",[962,5339,5340,5345,5347,5349],{},[982,5341,5342],{},[629,5343,5344],{},"CLOUDFLARE_API_TOKEN",[982,5346,642],{},[982,5348,642],{},[982,5350,5351,5352,5354,5355,5358],{},"GitLab only, and a name you choose: a masked variable holding a Cloudflare API token scoped to Zone → Cache Purge for the site's zone. ",[629,5353,916],{}," refers to it, so turn on ",[615,5356,5357],{},"Expand variable reference"," for that variable. The template never reads it directly",[962,5360,5361,5366,5371,5375],{},[982,5362,5363],{},[629,5364,5365],{},"CWA_API_CLOUDFLARE_PURGE_PLAN",[982,5367,5368],{},[629,5369,5370],{},"free",[982,5372,5373],{},[629,5374,4847],{},[982,5376,5377,5378,655,5380,655,5383,857,5386,5389,5390,740,5392],{},"Cloudflare only. ",[629,5379,5370],{},[629,5381,5382],{},"pro",[629,5384,5385],{},"business",[629,5387,5388],{},"enterprise",": paces purges to that plan's limit. An unknown value logs an error and uses ",[629,5391,5370],{},[620,5393,5395],{"href":5394},"\u002Fdeployment\u002Fcloudflare#queued-and-paced-purges","Queued and paced purges",[962,5397,5398,5403,5406,5410],{},[982,5399,5400],{},[629,5401,5402],{},"CWA_API_CLOUDFLARE_PURGE_REQUESTS",[982,5404,5405],{},"from the plan",[982,5407,5408],{},[629,5409,4847],{},[982,5411,5412,5413,5416],{},"Purge requests per ",[629,5414,5415],{},"CWA_API_CLOUDFLARE_PURGE_WINDOW",". Set it lower when staging and production, or several sites, share a Cloudflare account",[962,5418,5419,5423,5425,5429],{},[982,5420,5421],{},[629,5422,5415],{},[982,5424,5405],{},[982,5426,5427],{},[629,5428,4847],{},[982,5430,5431,5432,857,5434],{},"A Go duration, such as ",[629,5433,4919],{},[629,5435,5436],{},"1s",[962,5438,5439,5444,5446,5450],{},[982,5440,5441],{},[629,5442,5443],{},"CWA_API_CLOUDFLARE_PURGE_BURST",[982,5445,5405],{},[982,5447,5448],{},[629,5449,4847],{},[982,5451,5452],{},"Purge requests that can be sent at once",[611,5454,5455,5459,5460,5462,5463,655,5465,655,5468,665,5471,5474,5475,5478],{},[615,5456,5457,2308],{},[629,5458,916],{}," setting it replaces the default, so repeat ",[629,5461,5325],{},". For Cloudflare: ",[629,5464,5325],{},[629,5466,5467],{},"provider cloudflare",[629,5469,5470],{},"zone_id \u003Czone ID>",[629,5472,5473],{},"api_key $CLOUDFLARE_API_TOKEN",", with no ",[629,5476,5477],{},"email"," line. It is stored in the release's Secret.",[611,5480,1472,5481,5484,5485,5488,5489,5492,5493,5495],{},[629,5482,5483],{},"CWA_API_CLOUDFLARE_PURGE_*"," settings reach php as ",[629,5486,5487],{},"CLOUDFLARE_PURGE_*",", like any ",[620,5490,5491],{"href":880},"site setting",". Locally, compose passes the bare names through when they're set. ",[629,5494,916],{}," keeps its own name, because the deploy checks it before anything changes.",[611,5497,5498,5499,5501,5502,5505],{},"On GitHub, a variable can't refer to another one. Put the token directly in ",[629,5500,916],{},", store that as a secret, and map it in the deploy jobs' ",[629,5503,5504],{},"env:",", because the template's workflows don't map it.",[611,5507,5508,5509,5511,5512,5516,5517,5519,5520,713],{},"On GitLab, scope both variables to ",[629,5510,950],{},", and protect the token only if the deploy branch is protected: see ",[620,5513,5515],{"href":5514},"\u002Fdeployment\u002Fcloudflare#secrets","Cloudflare › Secrets",". A deploy fails before rolling anything out if a ",[629,5518,5336],{}," setting has no value or an unexpanded ",[629,5521,5522],{},"$NAME",[645,5524,5526],{"id":5525},"image-variants","Image variants",[611,5528,5529,5530,5534,5535,624],{},"These control ",[620,5531,5533],{"href":5532},"\u002Fdeployment\u002Fci-cd#generating-image-variants-after-a-deploy","generating image variants after a deploy",", in one-off Kubernetes Jobs (see ",[620,5536,5538],{"href":5537},"\u002Fdeployment\u002Fkubernetes#image-variants-jobs","Kubernetes › Image Variants Jobs",[956,5540,5541,5553],{},[959,5542,5543],{},[962,5544,5545,5547,5549,5551],{},[965,5546,967],{},[965,5548,970],{},[965,5550,635],{},[965,5552,975],{},[977,5554,5555,5571,5587,5603,5619,5643,5667,5684],{},[962,5556,5557,5562,5564,5566],{},[982,5558,5559],{},[629,5560,5561],{},"CWA_CI_IMAGE_VARIANTS_PRODUCTION",[982,5563,3133],{},[982,5565,642],{},[982,5567,1183,5568,5570],{},[629,5569,1186],{}," to stop generating variants after production deploys",[962,5572,5573,5578,5580,5582],{},[982,5574,5575],{},[629,5576,5577],{},"CWA_CI_IMAGE_VARIANTS_STAGING",[982,5579,3159],{},[982,5581,642],{},[982,5583,1183,5584,5586],{},[629,5585,1175],{}," to run it after staging deploys",[962,5588,5589,5594,5596,5598],{},[982,5590,5591],{},[629,5592,5593],{},"CWA_CI_IMAGE_VARIANTS_CANARY",[982,5595,3159],{},[982,5597,642],{},[982,5599,1183,5600,5602],{},[629,5601,1175],{}," to run it after canary deploys",[962,5604,5605,5610,5612,5614],{},[982,5606,5607],{},[629,5608,5609],{},"CWA_CI_IMAGE_VARIANTS_REVIEW",[982,5611,3159],{},[982,5613,642],{},[982,5615,1183,5616,5618],{},[629,5617,1175],{}," to run it after review deploys",[962,5620,5621,5626,5631,5637],{},[982,5622,5623],{},[629,5624,5625],{},"CWA_CI_IMAGE_VARIANTS_MEMORY",[982,5627,5628],{},[629,5629,5630],{},"1536Mi",[982,5632,5633,5636],{},[629,5634,5635],{},"cronjobs.imageVariants.resources"," (memory request and limit)",[982,5638,5639,5640,1646],{},"Each Job's memory. Raise it if a Job is OOM-killed (exit ",[629,5641,5642],{},"137",[962,5644,5645,5650,5654,5659],{},[982,5646,5647],{},[629,5648,5649],{},"CWA_CI_IMAGE_VARIANTS_MAX_MEMORY",[982,5651,5652],{},[629,5653,4341],{},[982,5655,5656],{},[629,5657,5658],{},"cronjobs.imageVariants.maxMemory",[982,5660,5661,5664,5665],{},[629,5662,5663],{},"imagine:generate --max-memory",": the resident memory at which a Job stops and the next one carries on. Keep it well under ",[629,5666,5625],{},[962,5668,5669,5674,5679,5681],{},[982,5670,5671],{},[629,5672,5673],{},"CWA_CI_IMAGE_VARIANTS_MAX_RUNS",[982,5675,5676],{},[629,5677,5678],{},"50",[982,5680,642],{},[982,5682,5683],{},"The most Jobs one deploy creates. Variants still missing after that are left to the next deploy",[962,5685,5686,5691,5696,5698],{},[982,5687,5688],{},[629,5689,5690],{},"CWA_CI_IMAGE_VARIANTS_TIMEOUT",[982,5692,5693],{},[629,5694,5695],{},"1900",[982,5697,642],{},[982,5699,5700,5701,5704],{},"Seconds the pipeline waits for each Job. The Job itself is stopped after ",[629,5702,5703],{},"cronjobs.imageVariants.activeDeadlineSeconds"," (1800)",[611,5706,5707,5708,5710],{},"As with the cache warm, there's one variable per track, because the staging job runs under ",[629,5709,946],{},". On GitHub, set them as Actions variables.",[645,5712,5714],{"id":5713},"cache-warm","Cache warm",[611,5716,5717,5718,624],{},"These control the cache warm after a deploy (see ",[620,5719,5721],{"href":5720},"\u002Fdeployment\u002Fci-cd#warming-the-cache-after-a-deploy","CI\u002FCD › Warming the cache after a deploy",[956,5723,5724,5736],{},[959,5725,5726],{},[962,5727,5728,5730,5732,5734],{},[965,5729,967],{},[965,5731,970],{},[965,5733,635],{},[965,5735,975],{},[977,5737,5738,5754,5770,5786,5802,5818,5834],{},[962,5739,5740,5745,5747,5749],{},[982,5741,5742],{},[629,5743,5744],{},"CWA_CI_WARM_CACHE_PRODUCTION",[982,5746,3133],{},[982,5748,642],{},[982,5750,1183,5751,5753],{},[629,5752,1186],{}," to stop warming production",[962,5755,5756,5761,5763,5765],{},[982,5757,5758],{},[629,5759,5760],{},"CWA_CI_WARM_CACHE_STAGING",[982,5762,3159],{},[982,5764,642],{},[982,5766,1183,5767,5769],{},[629,5768,1175],{}," to warm staging",[962,5771,5772,5777,5779,5781],{},[982,5773,5774],{},[629,5775,5776],{},"CWA_CI_WARM_CACHE_CANARY",[982,5778,3159],{},[982,5780,642],{},[982,5782,1183,5783,5785],{},[629,5784,1175],{}," to warm canary",[962,5787,5788,5793,5795,5797],{},[982,5789,5790],{},[629,5791,5792],{},"CWA_CI_WARM_CACHE_REVIEW",[982,5794,3159],{},[982,5796,642],{},[982,5798,1183,5799,5801],{},[629,5800,1175],{}," to warm review apps",[962,5803,5804,5809,5813,5815],{},[982,5805,5806],{},[629,5807,5808],{},"CWA_CI_WARM_CACHE_CONCURRENCY",[982,5810,5811],{},[629,5812,3733],{},[982,5814,642],{},[982,5816,5817],{},"Pages requested at once",[962,5819,5820,5825,5827,5829],{},[982,5821,5822],{},[629,5823,5824],{},"CWA_CI_WARM_CACHE_INSECURE",[982,5826,642],{},[982,5828,642],{},[982,5830,1183,5831,5833],{},[629,5832,1175],{}," to skip TLS verification, e.g. for an environment with a self-signed certificate",[962,5835,5836,5841,5843,5845],{},[982,5837,5838],{},[629,5839,5840],{},"CWA_CI_WARM_CACHE_CURL_OPTS",[982,5842,642],{},[982,5844,642],{},[982,5846,5847,5848],{},"Extra curl options, space-separated, added to the sitemap fetch and every page request, such as ",[629,5849,5850],{},"--resolve www.example.com:443:203.0.113.10",[611,5852,5853,5854,5857,5858,624],{},"On GitLab, the warm and the image variants share one job per track, ",[629,5855,5856],{},"image variants and warm \u003Ctrack>",", which runs when either track switch is on and skips the half that's off (see ",[620,5859,5861],{"href":5860},"\u002Fdeployment\u002Fci-cd#one-job-for-variants-and-the-warm","One job for variants and the warm",[611,5863,5864,5865,5867],{},"Set these as GitLab CI\u002FCD variables or GitHub repository variables. There's one variable per track, not one environment-scoped variable, because the template's staging deploy job uses ",[629,5866,946],{},". An environment scope therefore can't tell staging and production apart.",[645,5869,5871],{"id":5870},"performance-audit","Performance audit",[611,5873,5874,5875,713],{},"These control the manual ",[620,5876,5877],{"href":586},"performance audit",[956,5879,5880,5892],{},[959,5881,5882],{},[962,5883,5884,5886,5888,5890],{},[965,5885,967],{},[965,5887,970],{},[965,5889,635],{},[965,5891,975],{},[977,5893,5894,5914,5935,5953,5972,5997,6013,6035,6052,6072,6091,6120],{},[962,5895,5896,5905,5907,5909],{},[982,5897,5898,655,5901,655,5903],{},[629,5899,5900],{},"CWA_CI_PERFORMANCE_AUDIT_PRODUCTION",[629,5902,3892],{},[629,5904,3345],{},[982,5906,3133],{},[982,5908,642],{},[982,5910,1183,5911,5913],{},[629,5912,1186],{}," to remove that environment's audit job (GitLab) or disable it in the workflow (GitHub)",[962,5915,5916,5921,5923,5925],{},[982,5917,5918],{},[629,5919,5920],{},"CWA_CI_PERFORMANCE_AUDIT_CANARY",[982,5922,3159],{},[982,5924,642],{},[982,5926,5927,5928,5930,5931,5934],{},"GitLab only. Set ",[629,5929,1175],{}," to add ",[629,5932,5933],{},"performance audit canary",". Canary shares production's hostname and would measure whichever pod answers",[962,5936,5937,5942,5944,5946],{},[982,5938,5939],{},[629,5940,5941],{},"CWA_CI_PERFORMANCE_AUDIT_URLS",[982,5943,642],{},[982,5945,642],{},[982,5947,5948,5949,5952],{},"Pages to audit instead of the sitemap. Paths or full URLs, comma or space separated. A path such as ",[629,5950,5951],{},"\u002Fblog"," is joined to the environment URL",[962,5954,5955,5960,5964,5966],{},[982,5956,5957],{},[629,5958,5959],{},"CWA_CI_PERFORMANCE_AUDIT_MAX_PAGES",[982,5961,5962],{},[629,5963,3733],{},[982,5965,642],{},[982,5967,5968,5969,5971],{},"How many sitemap pages to audit when ",[629,5970,5941],{}," is unset",[962,5973,5974,5979,5984,5986],{},[982,5975,5976],{},[629,5977,5978],{},"CWA_CI_PERFORMANCE_AUDIT_FORM_FACTORS",[982,5980,5981],{},[629,5982,5983],{},"mobile",[982,5985,642],{},[982,5987,5988,655,5990,857,5993,5996],{},[629,5989,5983],{},[629,5991,5992],{},"desktop",[629,5994,5995],{},"mobile,desktop",". On GitHub you choose this when you start the workflow",[962,5998,5999,6004,6008,6010],{},[982,6000,6001],{},[629,6002,6003],{},"CWA_CI_PERFORMANCE_AUDIT_RUNS",[982,6005,6006],{},[629,6007,3256],{},[982,6009,642],{},[982,6011,6012],{},"Runs per page. Budgets use the median run",[962,6014,6015,6020,6025,6027],{},[982,6016,6017],{},[629,6018,6019],{},"CWA_CI_PERFORMANCE_AUDIT_THROTTLING",[982,6021,6022],{},[629,6023,6024],{},"devtools",[982,6026,642],{},[982,6028,6029,6031,6032],{},[629,6030,6024],{}," (real throttling) or ",[629,6033,6034],{},"simulate",[962,6036,6037,6042,6047,6049],{},[982,6038,6039],{},[629,6040,6041],{},"CWA_CI_PERFORMANCE_AUDIT_CONFIG",[982,6043,6044],{},[629,6045,6046],{},"bin\u002Fdevops\u002Flighthouserc.json",[982,6048,642],{},[982,6050,6051],{},"The Lighthouse CI config. It holds only the budgets; the collect settings are passed on the command line",[962,6053,6054,6059,6064,6066],{},[982,6055,6056],{},[629,6057,6058],{},"CWA_CI_PERFORMANCE_AUDIT_LHCI_VERSION",[982,6060,6061],{},[629,6062,6063],{},"0.15.1",[982,6065,642],{},[982,6067,1472,6068,6071],{},[629,6069,6070],{},"@lhci\u002Fcli"," version",[962,6073,6074,6079,6081,6083],{},[982,6075,6076],{},[629,6077,6078],{},"CWA_CI_PERFORMANCE_AUDIT_INSECURE",[982,6080,642],{},[982,6082,642],{},[982,6084,1183,6085,6087,6088,6090],{},[629,6086,1175],{}," to skip TLS verification, for the sitemap fetch and in Chrome, e.g. for an environment with a ",[629,6089,1091],{}," or self-signed certificate",[962,6092,6093,6098,6103,6105],{},[982,6094,6095],{},[629,6096,6097],{},"CWA_CI_PERFORMANCE_AUDIT_OUTPUT",[982,6099,6100],{},[629,6101,6102],{},"performance-report",[982,6104,642],{},[982,6106,6107,6108,6111,6112,6115,6116,6119],{},"The directory the reports and ",[629,6109,6110],{},"summary.md"," are written to. The CI jobs collect ",[629,6113,6114],{},"performance-report\u002F"," as their artifact, so change it only when running ",[629,6117,6118],{},"performance_audit"," outside the pipeline",[962,6121,6122,6127,6133,6135],{},[982,6123,6124],{},[629,6125,6126],{},"CWA_CI_PERFORMANCE_AUDIT_IMAGE",[982,6128,6129,6130,2870],{},"a pinned ",[629,6131,6132],{},"cypress\u002Fbrowsers",[982,6134,642],{},[982,6136,6137,6138],{},"GitLab only. The job image, which must have Node and Chrome. Set in ",[629,6139,6140],{},".gitlab-ci.yml",[645,6142,6144],{"id":6143},"pipeline-flags","Pipeline flags",[956,6146,6147,6159],{},[959,6148,6149],{},[962,6150,6151,6153,6155,6157],{},[965,6152,967],{},[965,6154,970],{},[965,6156,635],{},[965,6158,975],{},[977,6160,6161,6177,6197,6213,6232,6255,6278,6308,6324,6340,6356,6375,6397,6425,6446,6467,6480],{},[962,6162,6163,6168,6172,6174],{},[982,6164,6165],{},[629,6166,6167],{},"CWA_CI_BUILD_DISABLED",[982,6169,6170],{},[629,6171,2797],{},[982,6173,642],{},[982,6175,6176],{},"Skip the build stage entirely",[962,6178,6179,6184,6186,6188],{},[982,6180,6181],{},[629,6182,6183],{},"CWA_CI_BUILD_FORCE",[982,6185,642],{},[982,6187,642],{},[982,6189,1183,6190,6192,6193],{},[629,6191,1175],{}," to build both images even when an image built from the same inputs exists. The new digest restarts their pods on the next deploy. See ",[620,6194,6196],{"href":6195},"\u002Fdeployment\u002Fci-cd#an-unchanged-image-isnt-built-again","CI\u002FCD › An unchanged image isn't built again",[962,6198,6199,6204,6208,6210],{},[982,6200,6201],{},[629,6202,6203],{},"CWA_CI_TEST_DISABLED",[982,6205,6206],{},[629,6207,2797],{},[982,6209,642],{},[982,6211,6212],{},"Skip the test stage, browser tests included",[962,6214,6215,6220,6222,6224],{},[982,6216,6217],{},[629,6218,6219],{},"CWA_CI_E2E_ENABLED",[982,6221,642],{},[982,6223,642],{},[982,6225,1183,6226,6228,6229,6231],{},[629,6227,1175],{}," to run the ",[620,6230,2102],{"href":594},". Every deploy then waits for them, and doesn't run if they fail.",[962,6233,6234,6239,6241,6243],{},[982,6235,6236],{},[629,6237,6238],{},"CWA_CI_E2E_SCALED",[982,6240,642],{},[982,6242,642],{},[982,6244,6245,6246,1940,6248,6250,6251,713],{},"Part of ",[620,6247,557],{"href":558},[629,6249,1175],{}," runs the browser tests against two php replicas sharing one Redis cache and one Mercure hub. See ",[620,6252,6254],{"href":6253},"\u002Fdeployment\u002Fbrowser-tests#running-them-locally","Browser Tests › Running them locally",[962,6256,6257,6262,6266,6268],{},[982,6258,6259],{},[629,6260,6261],{},"CWA_CI_E2E_STORAGE",[982,6263,6264],{},[629,6265,1849],{},[982,6267,642],{},[982,6269,6270,6273,6274,713],{},[629,6271,6272],{},"\"gcloud\""," runs the browser tests with media in a Cloud Storage emulator instead of on the server's disk. See ",[620,6275,6277],{"href":6276},"\u002Fdeployment\u002Fbrowser-tests#media-storage-in-the-tests","Browser Tests › Media storage in the tests",[962,6279,6280,6284,6289,6291],{},[982,6281,6282],{},[629,6283,856],{},[982,6285,6286,6288],{},[629,6287,2598],{}," on GitLab, unset (off) on GitHub",[982,6290,642],{},[982,6292,6293,6294,1940,6297,6299,6300,1940,6302,6305,6306],{},"Auto-deploy staging on merge to ",[629,6295,6296],{},"main",[629,6298,6140],{}," defaults it to ",[629,6301,1175],{},[629,6303,6304],{},"ci.yml"," has no default, so on GitHub staging runs only once you set it to ",[629,6307,2598],{},[962,6309,6310,6315,6319,6321],{},[982,6311,6312],{},[629,6313,6314],{},"CWA_CI_CANARY_ENABLED",[982,6316,6317],{},[629,6318,2598],{},[982,6320,642],{},[982,6322,6323],{},"GitLab only. Show the canary job. On GitHub, canary is an option of the manual production workflow",[962,6325,6326,6331,6333,6335],{},[982,6327,6328],{},[629,6329,6330],{},"CWA_CI_REVIEW_DISABLED",[982,6332,642],{},[982,6334,642],{},[982,6336,1183,6337,6339],{},[629,6338,1175],{}," to disable review apps on branches",[962,6341,6342,6347,6349,6351],{},[982,6343,6344],{},[629,6345,6346],{},"CWA_CI_PRODUCTION_DISABLED",[982,6348,642],{},[982,6350,642],{},[982,6352,5927,6353,6355],{},[629,6354,1175],{}," to remove the production job, with its fixture, image variants and warm, and performance audit jobs",[962,6357,6358,6363,6365,6367],{},[982,6359,6360],{},[629,6361,6362],{},"CWA_CI_DISABLED",[982,6364,642],{},[982,6366,642],{},[982,6368,6369,6370,6372,6373,1646],{},"GitHub only. Set ",[629,6371,1175],{}," to stop the workflows running at all (see ",[620,6374,623],{"href":622},[962,6376,6377,6382,6386,6388],{},[982,6378,6379],{},[629,6380,6381],{},"CWA_CI_FIXTURES_ENABLED",[982,6383,6384],{},[629,6385,2797],{},[982,6387,642],{},[982,6389,1183,6390,6392,6393],{},[629,6391,1175],{}," to load fixtures after deploying: a manual job on GitLab, an automatic step on GitHub. See ",[620,6394,6396],{"href":6395},"\u002Fdeployment\u002Fci-cd#fixtures-on-a-deploy","Fixtures on a deploy",[962,6398,6399,6404,6408,6410],{},[982,6400,6401],{},[629,6402,6403],{},"CWA_CI_FIXTURES_PURGE",[982,6405,6406],{},[629,6407,2797],{},[982,6409,642],{},[982,6411,6412,6413,857,6415,6418,6419,740,6421],{},"Makes the fixture job empty the database before loading. Review apps: ",[629,6414,1175],{},[629,6416,6417],{},"\"force\"",". Production: only ",[629,6420,6417],{},[620,6422,6424],{"href":6423},"\u002Fdeployment\u002Fci-cd#purging-before-a-load","Purging before a load",[962,6426,6427,6432,6436,6441],{},[982,6428,6429],{},[629,6430,6431],{},"CWA_CI_RESET_DATABASE",[982,6433,6434],{},[629,6435,2797],{},[982,6437,6438],{},[629,6439,6440],{},"php.resetDatabase",[982,6442,1183,6443,6445],{},[629,6444,1175],{}," to make the php pod drop the whole schema before it runs migrations, every time it starts. Never leave it on",[962,6447,6448,6453,6458,6460],{},[982,6449,6450],{},[629,6451,6452],{},"CWA_CI_DEPLOY_ROLLOUT_TIMEOUT",[982,6454,6455],{},[629,6456,6457],{},"900",[982,6459,642],{},[982,6461,6462,6463,713],{},"Seconds the deploy waits for the Nuxt rollout, then the same again for the API rollout. A rollout that doesn't finish fails the deploy, and the track's image tag stays on the last good build. See ",[620,6464,6466],{"href":6465},"\u002Fdeployment\u002Fci-cd#which-build-a-deploy-runs","CI\u002FCD › Which build a deploy runs",[962,6468,6469,6473,6475,6477],{},[982,6470,6471],{},[629,6472,1453],{},[982,6474,642],{},[982,6476,642],{},[982,6478,6479],{},"Any value uninstalls the release before the deploy installs it again",[962,6481,6482,6487,6489,6491],{},[982,6483,6484],{},[629,6485,6486],{},"CWA_CI_TRACE",[982,6488,642],{},[982,6490,642],{},[982,6492,6493,6494,6496,6497,1646],{},"Any value makes ",[629,6495,1259],{}," print each command it runs (",[629,6498,6499],{},"set -x",[645,6501,881],{"id":6502},"site-settings",[611,6504,6505,6506,6509],{},"A site's own settings, such as an analytics ID for Nuxt or an API key for a Symfony service, are CI variables. The Kubernetes deploy passes them to the containers by their prefix, so adding one needs no change to ",[629,6507,6508],{},"k8s.sh",", the chart or a workflow.",[956,6511,6512,6528],{},[959,6513,6514],{},[962,6515,6516,6519,6522,6525],{},[965,6517,6518],{},"CI variable",[965,6520,6521],{},"Reaches",[965,6523,6524],{},"As",[965,6526,6527],{},"Stored in",[977,6529,6530,6554,6576],{},[962,6531,6532,6537,6540,6545],{},[982,6533,6534],{},[629,6535,6536],{},"NUXT_PUBLIC_\u003CNAME>",[982,6538,6539],{},"The Nuxt pods",[982,6541,6542,6544],{},[629,6543,6536],{},", unchanged",[982,6546,6547,6548,824,6551,1646],{},"The ConfigMap ",[629,6549,6550],{},"\u003Cfullname>-pwa-env",[629,6552,6553],{},"siteEnv.pwa",[962,6555,6556,6562,6564,6568],{},[982,6557,6558,6559],{},"Any other ",[629,6560,6561],{},"NUXT_\u003CNAME>",[982,6563,6539],{},[982,6565,6566,6544],{},[629,6567,6561],{},[982,6569,6570,6571,824,6573,1646],{},"The Secret ",[629,6572,6550],{},[629,6574,6575],{},"siteEnv.pwaSecret",[962,6577,6578,6582,6585,6590],{},[982,6579,6580],{},[629,6581,4792],{},[982,6583,6584],{},"The php pod and the orphan scan CronJob",[982,6586,6587,6589],{},[629,6588,4796],{},", without the prefix",[982,6591,6570,6592,824,6595,1646],{},[629,6593,6594],{},"\u003Cfullname>-api-env",[629,6596,4847],{},[611,6598,6599,6600,1705,6603,6607,6608,6611,6612,857,6614,6616],{},"The chart adds them to the containers with ",[629,6601,6602],{},"envFrom",[620,6604,6606],{"href":6605},"\u002Fdeployment\u002Fkubernetes#site-settings","Kubernetes › Site Settings","). The API's settings need the ",[629,6609,6610],{},"CWA_API_"," prefix because its own names, such as ",[629,6613,735],{},[629,6615,827],{},", are too generic to pass through whole.",[611,6618,6619,6624,6625,6628,6629,6632,6633,6635],{},[615,6620,4750,6621,6623],{},[629,6622,6610],{}," value is left out",", and the log says so (",[629,6626,6627],{},"api: \u003CNAME> left unset, CWA_API_\u003CNAME> is empty","). An empty variable would replace the Caddyfile's ",[629,6630,6631],{},"{$NAME:default}"," and Symfony's defaults, so leaving it out keeps them. An empty ",[629,6634,876],{}," value is still delivered, because Nuxt applies an empty value on purpose.",[1742,6637,6639],{"id":6638},"the-apis-optional-settings","The API's optional settings",[611,6641,6642,6643,6645,6646,6648],{},"The template's own optional API settings go through the same passthrough. Set them as ",[629,6644,4792],{}," in CI; the local compose stack uses the bare ",[629,6647,4796],{},". Unset, the default in the Caddyfile or in Symfony applies.",[956,6650,6651,6660],{},[959,6652,6653],{},[962,6654,6655,6657],{},[965,6656,6518],{},[965,6658,6659],{},"What it sets",[977,6661,6662,6686,6707,6721,6734,6763,6776,6803],{},[962,6663,6664,6680],{},[982,6665,6666,655,6668,655,6670,655,6672,655,6674,655,6676,655,6678],{},[629,6667,4837],{},[629,6669,4872],{},[629,6671,4800],{},[629,6673,4914],{},[629,6675,4936],{},[629,6677,4962],{},[629,6679,4990],{},[982,6681,6682,6683],{},"Origin protection. See ",[620,6684,4772],{"href":6685},"#origin-protection",[962,6687,6688,6701],{},[982,6689,6690,655,6692,655,6695,655,6698],{},[629,6691,5365],{},[629,6693,6694],{},"_REQUESTS",[629,6696,6697],{},"_WINDOW",[629,6699,6700],{},"_BURST",[982,6702,6703,6704],{},"Cloudflare purge pacing. See ",[620,6705,5292],{"href":6706},"#cdn-purging",[962,6708,6709,6715],{},[982,6710,6711,655,6713],{},[629,6712,5171],{},[629,6714,4170],{},[982,6716,6717,6718],{},"Souin's backend timeout and store. See ",[620,6719,5137],{"href":6720},"#http-cache",[962,6722,6723,6728],{},[982,6724,6725],{},[629,6726,6727],{},"CWA_API_CADDY_GLOBAL_CONFIG",[982,6729,6730,6731],{},"Extra lines for the Caddyfile's global options block, such as ",[629,6732,6733],{},"debug",[962,6735,6736,6741],{},[982,6737,6738],{},[629,6739,6740],{},"CWA_API_JWT_COOKIE_SAMESITE",[982,6742,1472,6743,6746,6747,6750,6751,6754,6755,6758,6759],{},[629,6744,6745],{},"SameSite"," attribute of the login (",[629,6748,6749],{},"api_component","), session and Mercure cookies. Default ",[629,6752,6753],{},"strict",". Set ",[629,6756,6757],{},"none"," when the front end and the API are on different registrable domains. See ",[620,6760,6762],{"href":6761},"\u002Fapi\u002Fusers\u002Fauthentication#the-cookies-samesite","API Authentication › The cookie's SameSite",[962,6764,6765,6770],{},[982,6766,6767],{},[629,6768,6769],{},"CWA_API_MERCURE_JWT_ALGORITHM",[982,6771,6772,6773],{},"The algorithm Symfony signs Mercure tokens with. Default ",[629,6774,6775],{},"hmac.sha256",[962,6777,6778,6783],{},[982,6779,6780],{},[629,6781,6782],{},"CWA_API_ERROR_MAIL_TO",[982,6784,6785,6786,6789,6790,6793,6794,665,6796,6798,6799],{},"An address that gets one email per unique ",[629,6787,6788],{},"5xx"," per hour, subject ",[629,6791,6792],{},"\u003CWEBSITE_NAME> API error",". Needs a working ",[629,6795,782],{},[629,6797,785],{},". Empty sends none. See ",[620,6800,6802],{"href":6801},"\u002Fdeployment\u002Fkubernetes#error-mail","Kubernetes › Error mail",[962,6804,6805,6810],{},[982,6806,6807],{},[629,6808,6809],{},"CWA_API_WEBSITE_NAME",[982,6811,6812,6813,6816,6817,6820,6821,6824,6825],{},"The site's name in emails (the bundle's ",[629,6814,6815],{},"website_name","). Default ",[629,6818,6819],{},"New Website",", from ",[629,6822,6823],{},"api\u002F.env",". A multi-site project can export it per site from ",[620,6826,6827],{"href":1440},[629,6828,6829],{},"project.sh",[611,6831,6832,6833,857,6835,6838],{},"The deploy doesn't read their bare names, such as ",[629,6834,4804],{},[629,6836,6837],{},"CLOUDFLARE_PURGE_PLAN",", as CI variables.",[611,6840,6841],{},"Each side reads them as it reads any environment variable:",[678,6843,6844,6872],{},[683,6845,6846,6849,6850,6852,6853,4368,6856,1940,6859,1866,6862,1786,6865,6868,6869,713],{},[615,6847,6848],{},"Nuxt"," uses a ",[629,6851,876],{}," variable only for a key you declare in ",[629,6854,6855],{},"runtimeConfig",[629,6857,6858],{},"app\u002Fnuxt.config.ts",[629,6860,6861],{},"NUXT_PUBLIC_ANALYTICS_ID",[629,6863,6864],{},"runtimeConfig.public.analyticsId",[629,6866,6867],{},"NUXT_MAPS_KEY"," sets the server-only ",[629,6870,6871],{},"runtimeConfig.mapsKey",[683,6873,6874,6877,6878,6881,6882,1037,6885,6888,6889,6891],{},[615,6875,6876],{},"Symfony"," reads ",[629,6879,6880],{},"CWA_API_MAPS_KEY"," as ",[629,6883,6884],{},"MAPS_KEY",[629,6886,6887],{},"'%env(MAPS_KEY)%'",". Give it a default in ",[629,6890,6823],{}," so the local stack still boots.",[1764,6893,6897],{"className":6894,"code":6895,"language":6896,"meta":1770,"style":1770},"language-ts shiki shiki-themes github-light github-dark material-theme-palenight","\u002F\u002F app\u002Fnuxt.config.ts\nexport default defineNuxtConfig({\n    runtimeConfig: {\n        mapsKey: '',          \u002F\u002F NUXT_MAPS_KEY, server only\n        public: {\n            analyticsId: '',  \u002F\u002F NUXT_PUBLIC_ANALYTICS_ID, sent to browsers\n        },\n    },\n})\n","ts",[629,6898,6899,6905,6923,6934,6949,6958,6972,6977,6982],{"__ignoreMap":1770},[2284,6900,6901],{"class":2286,"line":2287},[2284,6902,6904],{"class":6903},"sTBSN","\u002F\u002F app\u002Fnuxt.config.ts\n",[2284,6906,6907,6911,6914,6918,6921],{"class":2286,"line":2294},[2284,6908,6910],{"class":6909},"sm4w6","export",[2284,6912,6913],{"class":6909}," default",[2284,6915,6917],{"class":6916},"sKpYG"," defineNuxtConfig",[2284,6919,6920],{"class":2542},"(",[2284,6922,2291],{"class":2290},[2284,6924,6925,6929,6931],{"class":2286,"line":2314},[2284,6926,6928],{"class":6927},"sDHlG","    runtimeConfig",[2284,6930,2308],{"class":2290},[2284,6932,6933],{"class":2290}," {\n",[2284,6935,6936,6939,6941,6944,6946],{"class":2286,"line":2320},[2284,6937,6938],{"class":6927},"        mapsKey",[2284,6940,2308],{"class":2290},[2284,6942,6943],{"class":2348}," ''",[2284,6945,2386],{"class":2290},[2284,6947,6948],{"class":6903},"          \u002F\u002F NUXT_MAPS_KEY, server only\n",[2284,6950,6951,6954,6956],{"class":2286,"line":2360},[2284,6952,6953],{"class":6927},"        public",[2284,6955,2308],{"class":2290},[2284,6957,6933],{"class":2290},[2284,6959,6960,6963,6965,6967,6969],{"class":2286,"line":2413},[2284,6961,6962],{"class":6927},"            analyticsId",[2284,6964,2308],{"class":2290},[2284,6966,6943],{"class":2348},[2284,6968,2386],{"class":2290},[2284,6970,6971],{"class":6903},"  \u002F\u002F NUXT_PUBLIC_ANALYTICS_ID, sent to browsers\n",[2284,6973,6974],{"class":2286,"line":2419},[2284,6975,6976],{"class":2290},"        },\n",[2284,6978,6979],{"class":2286,"line":2425},[2284,6980,6981],{"class":2290},"    },\n",[2284,6983,6985,6988],{"class":2286,"line":6984},9,[2284,6986,6987],{"class":2290},"}",[2284,6989,6990],{"class":2542},")\n",[935,6992,6993],{"icon":1714,"color":1715},[611,6994,6995,7001,7002,7004,7005,7008],{},[615,6996,6997,7000],{},[629,6998,6999],{},"NUXT_PUBLIC_*"," values aren't secret."," Nuxt sends public runtime config to every browser, in the page HTML. Put keys and tokens in a ",[629,7003,876],{}," name without ",[629,7006,7007],{},"PUBLIC_",", which stays on the server.",[935,7010,7011],{"icon":937},[611,7012,7013,7016,7017,665,7019,7022,7023,7026,7027,7029,7030,7033],{},[615,7014,7015],{},"Site settings are runtime only, never build args."," The values reach the containers when they start. The image builds get only what ",[629,7018,3483],{},[629,7020,7021],{},"build_app"," name, so one image serves every environment. Don't add a ",[629,7024,7025],{},"--build-arg"," or build secret for a site setting, and don't read one in a Dockerfile: read it at runtime, through ",[629,7028,6855],{}," in Nuxt or ",[629,7031,7032],{},"%env()%"," in Symfony.",[611,7035,7036,7037,7039,7040,7042,7043,7047],{},"On GitLab, every CI variable already reaches the deploy job. Remember that staging runs under ",[629,7038,946],{},", so a setting scoped to ",[629,7041,950],{}," reaches staging too. Use ",[620,7044,7046],{"href":7045},"#the-environment-name","the environment name"," where the two must behave differently.",[611,7049,7050,7051,7054,7055,857,7058,7061],{},"The deploy log prints ",[629,7052,7053],{},"Environment name: \u003Cname>"," and one line for each setting, such as ",[629,7056,7057],{},"pwa config NUXT_PUBLIC_ANALYTICS_ID (from NUXT_PUBLIC_ANALYTICS_ID)",[629,7059,7060],{},"api secret MAPS_KEY (from CWA_API_MAPS_KEY)",". It never prints a value.",[1742,7063,2206],{"id":7064},"reserved-names",[611,7066,7067,7068,7070,7071,7073,7074,7076,7077,7079],{},"The deploy fails, before it changes anything in the cluster, when a site setting uses a name the chart or its image already sets on that container. The chart's own ",[629,7069,5504],{}," entry would win over ",[629,7072,6602],{},", so the value would be ignored without a word. It also fails on a name that isn't usable, such as ",[629,7075,6610],{}," with nothing after it, or a ",[629,7078,4796],{}," that starts with a digit. The log lists every problem:",[1764,7081,7084],{"className":7082,"code":7083,"language":1769},[1767],"❌ Site environment variables (NUXT_*, CWA_API_*) that can't be passed through:\n  CWA_API_DATABASE_URL: DATABASE_URL is reserved on the api container: the chart or its image sets it (use its own CI variable, if it has one, or delete this one)\n",[629,7085,7083],{"__ignoreMap":1770},[611,7087,7088,7089,4368,7092,7094],{},"Reserved on the Nuxt container (",[629,7090,7091],{},"SITE_ENV_RESERVED_PWA",[629,7093,6508],{},"):",[611,7096,7097,655,7100,655,7103,655,7106,665,7109,7112,7113,7115,7116,7119,7120,7123,7124,7126,7127,2192,7135,7137,7138,7140],{},[629,7098,7099],{},"NUXT_CWA_API_URL",[629,7101,7102],{},"NUXT_PUBLIC_CWA_API_URL",[629,7104,7105],{},"NUXT_PUBLIC_CWA_API_URL_BROWSER",[629,7107,7108],{},"NUXT_SITE_URL",[629,7110,7111],{},"NUXT_PUBLIC_CWA_ENVIRONMENT",". The deploy sets ",[629,7114,7108],{}," to ",[629,7117,7118],{},"https:\u002F\u002F$DOMAIN",", so cached pages' canonical URL and ",[629,7121,7122],{},"og:url"," use the site's own host, not whichever host rendered the page. A multi-site project gives each site its host through ",[629,7125,1040],{},". The chart sets the API URLs itself (see ",[620,7128,7130,7131,7134],{"href":7129},"\u002Fdeployment\u002Fkubernetes#apiurl-should-stay-unset","Kubernetes › ",[629,7132,7133],{},"apiUrl"," should stay unset",[629,7136,7102],{}," is the deprecated name of ",[629,7139,7099],{},", and as public config it would put the internal API URL in every page.",[611,7142,7143,7144,7147,7148,4368,7150,2308],{},"Reserved on the php container (",[629,7145,7146],{},"SITE_ENV_RESERVED_API","), as the ",[629,7149,4796],{},[629,7151,4792],{},[611,7153,7154,655,7156,655,7158,655,7160,655,7163,655,7166,655,7168,655,7171,655,7174,655,7177,655,7179,655,7181,655,7183,655,7186,655,7189,655,7191,655,7193,655,7195,655,7197,655,7199,655,7202,655,7204,655,7206,655,7208,655,7210,655,7212,655,7214,655,7216,655,7218,655,7220,655,7222,655,7224,655,7226,655,7228,655,7231,655,7234,655,7236,655,7239,655,7242,655,7245,655,7248,655,7251,655,7254,655,7257,655,7260,655,7263,665,7265,7268,7269,7271,7272,7275],{},[629,7155,1620],{},[629,7157,797],{},[629,7159,1571],{},[629,7161,7162],{},"APP_DEBUG",[629,7164,7165],{},"APP_ENV",[629,7167,827],{},[629,7169,7170],{},"APP_UPSTREAM",[629,7172,7173],{},"BROWSER_SERVER_NAME",[629,7175,7176],{},"CACHE_URL",[629,7178,916],{},[629,7180,5228],{},[629,7182,658],{},[629,7184,7185],{},"CWA_ENVIRONMENT",[629,7187,7188],{},"DATABASE_BACKUP_GCLOUD_JSON",[629,7190,2676],{},[629,7192,2693],{},[629,7194,2696],{},[629,7196,668],{},[629,7198,735],{},[629,7200,7201],{},"FRANKENPHP_CONFIG",[629,7203,4258],{},[629,7205,4232],{},[629,7207,758],{},[629,7209,755],{},[629,7211,766],{},[629,7213,762],{},[629,7215,3879],{},[629,7217,1343],{},[629,7219,1363],{},[629,7221,1326],{},[629,7223,782],{},[629,7225,785],{},[629,7227,664],{},[629,7229,7230],{},"MERCURE_EXTRA_DIRECTIVES",[629,7232,7233],{},"MERCURE_HUB_UPSTREAM",[629,7235,830],{},[629,7237,7238],{},"MERCURE_PUBLIC_URL",[629,7240,7241],{},"MERCURE_PUBLISHER_JWT_ALG",[629,7243,7244],{},"MERCURE_PUBLISHER_JWT_KEY",[629,7246,7247],{},"MERCURE_SUBSCRIBER_JWT_ALG",[629,7249,7250],{},"MERCURE_SUBSCRIBER_JWT_KEY",[629,7252,7253],{},"MERCURE_URL",[629,7255,7256],{},"RESET_DATABASE",[629,7258,7259],{},"SERVER_NAME",[629,7261,7262],{},"SKIP_MIGRATIONS",[629,7264,661],{},[629,7266,7267],{},"TRUSTED_PROXIES",". Reserving ",[629,7270,7188],{}," means a ",[629,7273,7274],{},"CWA_API_DATABASE_BACKUP_GCLOUD_JSON"," site setting can't put the backup key into the API pods.",[611,7277,7278,665,7281,7284,7285,7288,7289,7291],{},[629,7279,7280],{},"CWA_API_CADDY_CACHE_CDN_CONFIG",[629,7282,7283],{},"CWA_API_CADDY_CACHE_EXTRA_CONFIG"," fail with ",[629,7286,7287],{},"set the CI variable CADDY_CACHE_CDN_CONFIG instead"," (or ",[629,7290,5228],{},"): the chart writes both from the plain CI variables.",[611,7293,7294,7295,857,7297,7288,7299,3427,7301,655,7303,3427,7305,655,7307,3427,7309,1786,7311,3427,7313,7315],{},"Most of these have their own CI variable, such as ",[629,7296,782],{},[629,7298,658],{},[629,7300,3845],{},[629,7302,3879],{},[629,7304,4216],{},[629,7306,4232],{},[629,7308,4239],{},[629,7310,4258],{},[629,7312,6431],{},[629,7314,7256],{},"), so set that one instead.",[611,7317,7318,7319,7321,7322,4789,7324,857,7327,7330,7331,7335],{},"If your own chart values add ",[629,7320,5504],{}," entries to a container, reserve their names too, without editing ",[629,7323,6508],{},[629,7325,7326],{},"SITE_ENV_RESERVED_API_EXTRA",[629,7328,7329],{},"SITE_ENV_RESERVED_PWA_EXTRA"," to a space-separated list in ",[620,7332,7333],{"href":1440},[629,7334,6829],{}," or as a CI variable.",[1764,7337,7342],{"className":7338,"code":7339,"filename":7340,"language":7341,"meta":1770,"style":1770},"language-sh shiki shiki-themes github-light github-dark material-theme-palenight","export SITE_ENV_RESERVED_API_EXTRA=\"MAPS_REGION\"\n","bin\u002Fdevops\u002Fproject.sh","sh",[629,7343,7344],{"__ignoreMap":1770},[2284,7345,7346,7349,7352,7355,7357,7360],{"class":2286,"line":2287},[2284,7347,6910],{"class":7348},"swB56",[2284,7350,7351],{"class":2542}," SITE_ENV_RESERVED_API_EXTRA",[2284,7353,7354],{"class":2535},"=",[2284,7356,2305],{"class":2348},[2284,7358,7359],{"class":2351},"MAPS_REGION",[2284,7361,7362],{"class":2348},"\"\n",[1742,7364,7366],{"id":7365},"on-github","On GitHub",[611,7368,7369,7370,857,7372,7374,7375,7378,7379,7382,7383,7387,7388,665,7390,7392,7393,713],{},"Variables named ",[629,7371,876],{},[629,7373,871],{}," reach the deploy through ",[629,7376,7377],{},"github-export-vars.sh",", like every other variable. GitHub can't list secrets, so each deploy job (review, staging, canary and production) also runs ",[629,7380,7381],{},"bin\u002Fdevops\u002Fgithub-export-secrets.sh",", in the step ",[7384,7385,7386],"em",{},"Expose NUXT_* and CWA_API_* secrets to the deploy",". It receives every secret and exports only the ",[629,7389,876],{},[629,7391,871],{}," ones to the later steps, masked, including each line of a multi-line value. It runs after the kubectl and Helm setup actions, so those never see the values, and no build job runs it. So a site secret needs no workflow edit: add it under ",[615,7394,7395],{},"Settings → Secrets and variables → Actions → Secrets",[611,7397,7398],{},"The step adds a warning to the run in two cases:",[678,7400,7401,7410],{},[683,7402,7403,7406,7407,7409],{},[615,7404,7405],{},"Public value stored as a secret:"," a ",[629,7408,6999],{}," secret. It is still exported, but browsers receive it, so make it a variable.",[683,7411,7412,7415,7416,7418],{},[615,7413,7414],{},"Secret shadowed:"," a variable, or the job's ",[629,7417,5504],{},", already sets the name, so the secret isn't used. Delete one of the two.",[1742,7420,7422],{"id":7421},"the-environment-name","The environment name",[611,7424,7425,7426,7428,7429,7431,7432,7094],{},"The deploy names the environment it serves, from its track, and sets it as ",[629,7427,7185],{}," on the php pod and the orphan scan, and as ",[629,7430,7111],{}," on the Nuxt pods (Helm value ",[629,7433,7434],{},"cwaEnvironment",[956,7436,7437,7447],{},[959,7438,7439],{},[962,7440,7441,7444],{},[965,7442,7443],{},"Track",[965,7445,7446],{},"Name",[977,7448,7449,7458,7468,7478],{},[962,7450,7451,7454],{},[982,7452,7453],{},"Production",[982,7455,7456],{},[629,7457,950],{},[962,7459,7460,7463],{},[982,7461,7462],{},"Staging",[982,7464,7465],{},[629,7466,7467],{},"staging",[962,7469,7470,7473],{},[982,7471,7472],{},"Canary",[982,7474,7475],{},[629,7476,7477],{},"canary",[962,7479,7480,7483],{},[982,7481,7482],{},"Review apps",[982,7484,7485],{},[629,7486,2176],{},[611,7488,7489,7490,639,7492,7494,7495,7497,7498,655,7500,665,7503,7505,7506],{},"So staging is told apart from production, although its GitLab job runs as ",[629,7491,946],{},[629,7493,7185],{}," CI variable replaces the name on every track it reaches; on GitLab, a value scoped to ",[629,7496,950],{}," reaches staging and canary too. It may hold only letters, digits, ",[629,7499,713],{},[629,7501,7502],{},"_",[629,7504,2841],{},", or the deploy fails with ",[629,7507,7508],{},"CWA_ENVIRONMENT must be letters, digits, '.', '_' or '-', not '\u003Cvalue>'.",[611,7510,7511,7512,7515,7516,7519,7520,7522],{},"Nuxt sees the name only once you declare it, as ",[629,7513,7514],{},"runtimeConfig.public.cwaEnvironment",". Symfony reads it with ",[629,7517,7518],{},"'%env(CWA_ENVIRONMENT)%'","; give it a default in ",[629,7521,6823],{}," for the local stack.",[645,7524,7526],{"id":7525},"single-server-scripts","Single-server scripts",[611,7528,7529,7530,7533,7534,7536,7537,7540,7541,713],{},"These steer the template's ",[629,7531,7532],{},"bin\u002Fserver\u002F"," scripts on a site run on one server (see ",[620,7535,545],{"href":546},"). They aren't CI variables, and the scripts don't read them from ",[629,7538,7539],{},".env.prod",": set them in the shell, or in the crontab line, when you run a script, such as ",[629,7542,7543],{},"CWA_SERVER_BACKUP_KEEP=10 bin\u002Fserver\u002Fbackup.sh",[956,7545,7546,7559],{},[959,7547,7548],{},[962,7549,7550,7552,7554,7557],{},[965,7551,967],{},[965,7553,970],{},[965,7555,7556],{},"Script",[965,7558,975],{},[977,7560,7561,7582,7607,7628,7650,7668,7691],{},[962,7562,7563,7568,7572,7575],{},[982,7564,7565],{},[629,7566,7567],{},"CWA_SERVER_ENV_FILE",[982,7569,7570],{},[629,7571,7539],{},[982,7573,7574],{},"all",[982,7576,7577,7578,7581],{},"The site's settings file, which ",[629,7579,7580],{},"init"," writes and the others read",[962,7583,7584,7589,7593,7598],{},[982,7585,7586],{},[629,7587,7588],{},"CWA_SERVER_WARM_HTTP_CACHE_MAX_TIME",[982,7590,7591],{},[629,7592,3904],{},[982,7594,7595],{},[629,7596,7597],{},"deploy.sh",[982,7599,7600,7601,7603,7604,7606],{},"Seconds each of the two ",[629,7602,3919],{}," warms may take. ",[629,7605,3908],{}," skips them",[962,7608,7609,7614,7619,7623],{},[982,7610,7611],{},[629,7612,7613],{},"CWA_SERVER_IMAGE_VARIANTS_MEMORY",[982,7615,7616],{},[629,7617,7618],{},"1536m",[982,7620,7621],{},[629,7622,7597],{},[982,7624,7625,7626,1646],{},"The memory limit of each one-off container that generates image variants. Raise it if a container runs out of memory (exit ",[629,7627,5642],{},[962,7629,7630,7635,7639,7643],{},[982,7631,7632],{},[629,7633,7634],{},"CWA_SERVER_IMAGE_VARIANTS_MAX_MEMORY",[982,7636,7637],{},[629,7638,4341],{},[982,7640,7641],{},[629,7642,7597],{},[982,7644,7645,7647,7648],{},[629,7646,5663],{},": the memory at which a container stops and the next one carries on. Keep it well under ",[629,7649,7613],{},[962,7651,7652,7657,7661,7665],{},[982,7653,7654],{},[629,7655,7656],{},"CWA_SERVER_IMAGE_VARIANTS_MAX_RUNS",[982,7658,7659],{},[629,7660,5678],{},[982,7662,7663],{},[629,7664,7597],{},[982,7666,7667],{},"The most containers one deploy starts. Variants still missing after that are left to the next deploy",[962,7669,7670,7675,7680,7688],{},[982,7671,7672],{},[629,7673,7674],{},"CWA_SERVER_BACKUP_DIR",[982,7676,7677],{},[629,7678,7679],{},"backups",[982,7681,7682,655,7685],{},[629,7683,7684],{},"backup.sh",[629,7686,7687],{},"restore.sh",[982,7689,7690],{},"Where backups are written and read: a path relative to the checkout's root, or an absolute one",[962,7692,7693,7698,7702,7706],{},[982,7694,7695],{},[629,7696,7697],{},"CWA_SERVER_BACKUP_KEEP",[982,7699,7700],{},[629,7701,3256],{},[982,7703,7704],{},[629,7705,7684],{},[982,7707,7708],{},"How many backups to keep. Older ones are deleted",[611,7710,7711,7712,7714,7715,824,7717,7719,7720,624],{},"The page warm at the end of ",[629,7713,7597],{}," also reads ",[629,7716,5808],{},[629,7718,3733],{},"), as the pipeline's warm does (see ",[620,7721,5714],{"href":7722},"#cache-warm",[7724,7725,7726],"style",{},"html pre.shiki code .sOvfz, html code.shiki .sOvfz{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#89DDFF}html pre.shiki code .sBtbT, html code.shiki .sBtbT{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#89DDFF}html pre.shiki code .sphPO, html code.shiki .sphPO{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#C792EA}html pre.shiki code .sbW4m, html code.shiki .sbW4m{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#FFCB6B}html pre.shiki code .scSvc, html code.shiki .scSvc{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#F78C6C}html pre.shiki code .seSrl, html code.shiki .seSrl{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#89DDFF}html pre.shiki code .sLL54, html code.shiki .sLL54{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sRCss, html code.shiki .sRCss{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#FFCB6B}html pre.shiki code .sVlFx, html code.shiki .sVlFx{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#89DDFF}html pre.shiki code .sPB8G, html code.shiki .sPB8G{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#BABED8}html pre.shiki code .szhYu, html code.shiki .szhYu{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#C3E88D}html pre.shiki code .sTBSN, html code.shiki .sTBSN{--shiki-light:#6A737D;--shiki-light-font-style:inherit;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sm4w6, html code.shiki .sm4w6{--shiki-light:#D73A49;--shiki-light-font-style:inherit;--shiki-default:#F97583;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sKpYG, html code.shiki .sKpYG{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#82AAFF}html pre.shiki code .sDHlG, html code.shiki .sDHlG{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#F07178}html pre.shiki code .swB56, html code.shiki .swB56{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#C792EA}",{"title":1770,"searchDepth":2294,"depth":2294,"links":7728},[7729,7730,7731,7732,7733,7736,7741,7744,7745,7746,7747,7752,7753,7754,7755,7756,7757,7758,7759,7760,7766],{"id":647,"depth":2294,"text":648},{"id":839,"depth":2294,"text":840},{"id":954,"depth":2294,"text":712},{"id":823,"depth":2294,"text":1286},{"id":1486,"depth":2294,"text":801,"children":7734},[7735],{"id":1744,"depth":2314,"text":1613},{"id":1817,"depth":2294,"text":771,"children":7737},[7738,7739,7740],{"id":1998,"depth":2314,"text":1872},{"id":2110,"depth":2314,"text":1982},{"id":2463,"depth":2314,"text":2464},{"id":2570,"depth":2294,"text":744,"children":7742},[7743],{"id":2954,"depth":2314,"text":2907},{"id":3086,"depth":2294,"text":3087},{"id":3438,"depth":2294,"text":3439},{"id":3498,"depth":2294,"text":3499},{"id":3604,"depth":2294,"text":3605,"children":7748},[7749,7750,7751],{"id":3618,"depth":2314,"text":3619},{"id":4190,"depth":2314,"text":3837},{"id":4407,"depth":2314,"text":4408},{"id":4627,"depth":2294,"text":4628},{"id":4771,"depth":2294,"text":4772},{"id":5136,"depth":2294,"text":5137},{"id":5291,"depth":2294,"text":5292},{"id":5525,"depth":2294,"text":5526},{"id":5713,"depth":2294,"text":5714},{"id":5870,"depth":2294,"text":5871},{"id":6143,"depth":2294,"text":6144},{"id":6502,"depth":2294,"text":881,"children":7761},[7762,7763,7764,7765],{"id":6638,"depth":2314,"text":6639},{"id":7064,"depth":2314,"text":2206},{"id":7365,"depth":2314,"text":7366},{"id":7421,"depth":2314,"text":7422},{"id":7525,"depth":2294,"text":7526},"Every CI\u002FCD variable the template's pipeline reads, grouped by concern, with its default and the Helm value it sets. Starts with the few a new site must set.","md",{},{"title":569,"description":7767},"232ayHr25F5ZoVJHYLWcHr5uZasKlU-IpAdS79aFjdA",[7773,7775],{"title":565,"path":566,"stem":567,"description":7774,"children":-1},"The template's database step before a deploy rolls out - a read-only plan of production's pending migrations, production's migrations in a Job before the new pods start, an optional backup to a private bucket, and a maintenance window for breaking changes.",{"title":573,"path":574,"stem":575,"description":7776,"children":-1},"Putting a CWA site behind Cloudflare. Proxy only is the recommended default. Edge caching has been tested end to end on the Free plan and is a supported choice, with an optional second rule for API responses.",1793565690202]