[{"data":1,"prerenderedAt":4197},["ShallowReactive",2],{"navigation_docs":3,"section-navigation-docs":130,"-docs-self-hosting":164,"-docs-self-hosting-surround":4105,"doc-previews":4108},[4,8,50,54,58,62,66,70,74,78,82,86,90,94,98,102,106,110,114,118,122,126],{"title":5,"path":6,"stem":7},"Quickstart","\u002Fdocs\u002Fquickstart","docs\u002F01.quickstart",{"title":9,"path":10,"stem":11,"children":12},"Guides","\u002Fdocs\u002Fguides","docs\u002F02.guides\u002Findex",[13,14,18,22,26,30,34,38,42,46],{"title":9,"path":10,"stem":11},{"title":15,"path":16,"stem":17},"Next.js","\u002Fdocs\u002Fguides\u002Fnextjs","docs\u002F02.guides\u002F1.nextjs",{"title":19,"path":20,"stem":21},"Nuxt","\u002Fdocs\u002Fguides\u002Fnuxt","docs\u002F02.guides\u002F2.nuxt",{"title":23,"path":24,"stem":25},"SvelteKit","\u002Fdocs\u002Fguides\u002Fsveltekit","docs\u002F02.guides\u002F3.sveltekit",{"title":27,"path":28,"stem":29},"Astro","\u002Fdocs\u002Fguides\u002Fastro","docs\u002F02.guides\u002F4.astro",{"title":31,"path":32,"stem":33},"Express","\u002Fdocs\u002Fguides\u002Fexpress","docs\u002F02.guides\u002F5.express",{"title":35,"path":36,"stem":37},"FastAPI","\u002Fdocs\u002Fguides\u002Ffastapi","docs\u002F02.guides\u002F6.fastapi",{"title":39,"path":40,"stem":41},"Django","\u002Fdocs\u002Fguides\u002Fdjango","docs\u002F02.guides\u002F7.django",{"title":43,"path":44,"stem":45},"Laravel","\u002Fdocs\u002Fguides\u002Flaravel","docs\u002F02.guides\u002F8.laravel",{"title":47,"path":48,"stem":49},"Go","\u002Fdocs\u002Fguides\u002Fgo","docs\u002F02.guides\u002F9.go",{"title":51,"path":52,"stem":53},"Services","\u002Fdocs\u002Fservices","docs\u002F03.services",{"title":55,"path":56,"stem":57},"Deployments","\u002Fdocs\u002Fdeployments","docs\u002F04.deployments",{"title":59,"path":60,"stem":61},"Builds","\u002Fdocs\u002Fbuilds","docs\u002F05.builds",{"title":63,"path":64,"stem":65},"Variables","\u002Fdocs\u002Fvariables","docs\u002F06.variables",{"title":67,"path":68,"stem":69},"Domains","\u002Fdocs\u002Fdomains","docs\u002F07.domains",{"title":71,"path":72,"stem":73},"PostgreSQL","\u002Fdocs\u002Fpostgres","docs\u002F08.postgres",{"title":75,"path":76,"stem":77},"Object storage","\u002Fdocs\u002Fobject-storage","docs\u002F09.object-storage",{"title":79,"path":80,"stem":81},"Volumes","\u002Fdocs\u002Fvolumes","docs\u002F10.volumes",{"title":83,"path":84,"stem":85},"Key-value store","\u002Fdocs\u002Fredis","docs\u002F11.redis",{"title":87,"path":88,"stem":89},"Workspaces","\u002Fdocs\u002Fworkspaces","docs\u002F12.workspaces",{"title":91,"path":92,"stem":93},"Projects","\u002Fdocs\u002Fprojects","docs\u002F13.projects",{"title":95,"path":96,"stem":97},"Environments","\u002Fdocs\u002Fenvironments","docs\u002F14.environments",{"title":99,"path":100,"stem":101},"Eject","\u002Fdocs\u002Feject","docs\u002F15.eject",{"title":103,"path":104,"stem":105},"API","\u002Fdocs\u002Fapi","docs\u002F16.api",{"title":107,"path":108,"stem":109},"CLI","\u002Fdocs\u002Fcli","docs\u002F17.cli",{"title":111,"path":112,"stem":113},"Claude Plugin & MCP","\u002Fdocs\u002Fclaude-plugin","docs\u002F18.claude-plugin",{"title":115,"path":116,"stem":117},"Metrics","\u002Fdocs\u002Fmetrics","docs\u002F19.metrics",{"title":119,"path":120,"stem":121},"Logs","\u002Fdocs\u002Flogs","docs\u002F20.logs",{"title":123,"path":124,"stem":125},"Billing","\u002Fdocs\u002Fbilling","docs\u002F21.billing",{"title":127,"path":128,"stem":129},"Self-hosting","\u002Fdocs\u002Fself-hosting","docs\u002F22.self-hosting",[131,132,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163],{"title":5,"path":6,"stem":7},{"title":9,"path":10,"stem":11,"children":133},[134,135,136,137,138,139,140,141,142,143],{"title":9,"path":10,"stem":11},{"title":15,"path":16,"stem":17},{"title":19,"path":20,"stem":21},{"title":23,"path":24,"stem":25},{"title":27,"path":28,"stem":29},{"title":31,"path":32,"stem":33},{"title":35,"path":36,"stem":37},{"title":39,"path":40,"stem":41},{"title":43,"path":44,"stem":45},{"title":47,"path":48,"stem":49},{"title":51,"path":52,"stem":53},{"title":55,"path":56,"stem":57},{"title":59,"path":60,"stem":61},{"title":63,"path":64,"stem":65},{"title":67,"path":68,"stem":69},{"title":71,"path":72,"stem":73},{"title":75,"path":76,"stem":77},{"title":79,"path":80,"stem":81},{"title":83,"path":84,"stem":85},{"title":87,"path":88,"stem":89},{"title":91,"path":92,"stem":93},{"title":95,"path":96,"stem":97},{"title":99,"path":100,"stem":101},{"title":103,"path":104,"stem":105},{"title":107,"path":108,"stem":109},{"title":111,"path":112,"stem":113},{"title":115,"path":116,"stem":117},{"title":119,"path":120,"stem":121},{"title":123,"path":124,"stem":125},{"title":127,"path":128,"stem":129},{"id":165,"title":127,"body":166,"description":4099,"extension":4100,"links":4101,"meta":4102,"navigation":930,"path":128,"seo":4103,"stem":129,"__hash__":4104},"docs\u002Fdocs\u002F22.self-hosting.md",{"type":167,"value":168,"toc":4073},"minimark",[169,182,185,190,193,279,282,312,323,335,339,342,472,476,479,484,491,568,575,579,583,586,598,651,658,811,814,817,821,828,1713,1716,1722,1789,1792,1857,1863,1866,1941,1944,1969,1972,2020,2023,2034,2037,2094,2100,2104,2107,2169,2172,2175,2179,2182,2185,2207,2214,2217,2221,2232,2250,2258,2262,2269,2351,2364,2372,2376,2379,2461,2467,2470,2477,2481,2484,2487,2497,2510,2516,2523,2531,2534,2541,2560,2564,2574,2578,2581,2585,2588,2633,2640,2644,2693,2700,2702,2705,2753,2757,2775,2790,2793,2797,2800,2917,2923,3434,3444,3658,3661,3712,3728,3731,3782,3785,3791,3795,3798,3859,3870,3873,3962,3968,3971,4023,4026,4033,4037,4044,4066,4069],[170,171,172,173,177,178,181],"p",{},"Lucity is two Helm charts on a Kubernetes cluster. ",[174,175,176],"code",{},"lucity-infra"," brings the pieces the platform leans on, and ",[174,179,180],{},"lucity"," brings the platform itself. Nothing phones home, nothing is licensed, and the cluster stays yours.",[170,183,184],{},"That said, this is not a one-command install. The platform runs other people's code on shared infrastructure, so it wants TLS, DNS, an identity provider, and a container registry before it will do anything useful. Budget an afternoon for the first one.",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[170,191,192],{},"A cluster, first of all:",[194,195,196,209],"table",{},[197,198,199],"thead",{},[200,201,202,206],"tr",{},[203,204,205],"th",{},"Requirement",[203,207,208],{},"Why",[210,211,212,221,229,237,258,271],"tbody",{},[200,213,214,218],{},[215,216,217],"td",{},"Kubernetes 1.28+",[215,219,220],{},"Gateway API and CNPG both want a recent cluster",[200,222,223,226],{},[215,224,225],{},"Three nodes or more",[215,227,228],{},"The control plane pods spread across nodes, and you want room for workloads",[200,230,231,234],{},[215,232,233],{},"A CSI storage class with expansion",[215,235,236],{},"The registry, the databases, and user volumes all need persistent storage",[200,238,239,242],{},[215,240,241],{},"A Gateway API controller",[215,243,244,251,252,257],{},[245,246,250],"a",{"href":247,"rel":248},"https:\u002F\u002Fcilium.io",[249],"nofollow","Cilium"," and ",[245,253,256],{"href":254,"rel":255},"https:\u002F\u002Fgateway.envoyproxy.io",[249],"Envoy Gateway"," are both fine. On an IPv4-only cluster, pin the gateway Service to single-stack IPv4",[200,259,260,268],{},[215,261,262,267],{},[245,263,266],{"href":264,"rel":265},"https:\u002F\u002Fcert-manager.io",[249],"cert-manager"," 1.21+",[215,269,270],{},"Issues the platform certificates and one per custom domain",[200,272,273,276],{},[215,274,275],{},"A load balancer for the gateway",[215,277,278],{},"Whatever your provider offers, reachable from the internet",[170,280,281],{},"And two domains, which must be different from each other:",[194,283,284,294],{},[197,285,286],{},[200,287,288,291],{},[203,289,290],{},"Domain",[203,292,293],{},"Holds",[210,295,296,304],{},[200,297,298,301],{},[215,299,300],{},"Platform domain",[215,302,303],{},"The dashboard, the API, the docs, and the identity provider on a subdomain",[200,305,306,309],{},[215,307,308],{},"Workload domain",[215,310,311],{},"Every deployed service, on generated subdomains",[170,313,314,315,318,319,322],{},"Workload subdomains are handed to arbitrary user code, so anything sharing that domain shares cookies and certificate scope with it. Our own split is ",[174,316,317],{},"lucity.cloud"," for the platform and ",[174,320,321],{},"lucity.app"," for workloads.",[170,324,325,326,330,331,334],{},"You will also need a ",[327,328,329],"strong",{},"GitHub App",", which is how the platform reads source repositories, and an ",[327,332,333],{},"object store"," for buckets.",[186,336,338],{"id":337},"what-is-required-and-what-is-not","What is required and what is not",[170,340,341],{},"The infra chart ships a lot of switches. Only some of them are load-bearing:",[194,343,344,357],{},[197,345,346],{},[200,347,348,351,354],{},[203,349,350],{},"Component",[203,352,353],{},"Status",[203,355,356],{},"Notes",[210,358,359,370,380,390,400,411,421,432,442,452,462],{},[200,360,361,364,367],{},[215,362,363],{},"Zot (OCI registry)",[215,365,366],{},"Required",[215,368,369],{},"Holds every image the platform builds",[200,371,372,375,377],{},[215,373,374],{},"CloudNativePG",[215,376,366],{},[215,378,379],{},"Backs both the identity provider and user databases",[200,381,382,385,387],{},[215,383,384],{},"Logto",[215,386,366],{},[215,388,389],{},"The identity provider. Users, workspaces, roles, and GitHub sign-in",[200,391,392,395,397],{},[215,393,394],{},"VictoriaMetrics",[215,396,366],{},[215,398,399],{},"The conductor refuses to start without it, because metrics drive the dashboard and scaling",[200,401,402,405,408],{},[215,403,404],{},"VictoriaLogs",[215,406,407],{},"Recommended",[215,409,410],{},"Build and runtime logs in the dashboard",[200,412,413,416,418],{},[215,414,415],{},"OpenTelemetry collector",[215,417,407],{},[215,419,420],{},"Feeds both of the above",[200,422,423,426,429],{},[215,424,425],{},"Traefik",[215,427,428],{},"Optional",[215,430,431],{},"Only for exposing databases over the internet by SNI",[200,433,434,437,439],{},[215,435,436],{},"Barman Cloud plugin",[215,438,428],{},[215,440,441],{},"Only if you want database backups to object storage",[200,443,444,447,449],{},[215,445,446],{},"Grafana, Alertmanager",[215,448,428],{},[215,450,451],{},"Operator convenience, nothing in the platform reads them",[200,453,454,457,459],{},[215,455,456],{},"Rybbit",[215,458,428],{},[215,460,461],{},"Web analytics for the marketing site",[200,463,464,467,469],{},[215,465,466],{},"Billing (cashier)",[215,468,428],{},[215,470,471],{},"Off unless you are charging people",[186,473,475],{"id":474},"before-the-charts","Before the charts",[170,477,478],{},"Two things have to be true about the cluster before either chart will behave.",[480,481,483],"h3",{"id":482},"let-containerd-pull-from-the-in-cluster-registry","Let containerd pull from the in-cluster registry",[170,485,486,487,490],{},"The platform pulls user images from Zot over plain HTTP on a fixed service IP, and containerd will not do that by default. The infra chart runs a DaemonSet that writes the per-registry ",[174,488,489],{},"hosts.toml"," to every node, but containerd only reads it if its config points there:",[492,493,498],"pre",{"className":494,"code":495,"language":496,"meta":497,"style":497},"language-toml shiki shiki-themes gruvbox-dark-medium gruvbox-dark-medium gruvbox-dark-medium","[plugins.\"io.containerd.grpc.v1.cri\".registry]\nconfig_path = \"\u002Fetc\u002Fcontainerd\u002Fcerts.d\"\n","toml","",[174,499,500,548],{"__ignoreMap":497},[501,502,505,509,513,517,520,522,525,527,530,532,535,537,540,542,545],"span",{"class":503,"line":504},"line",1,[501,506,508],{"class":507},"si-ur","[",[501,510,512],{"class":511},"sX5TD","plugins",[501,514,516],{"class":515},"spvMa",".",[501,518,519],{"class":511},"\"io",[501,521,516],{"class":515},[501,523,524],{"class":511},"containerd",[501,526,516],{"class":515},[501,528,529],{"class":511},"grpc",[501,531,516],{"class":515},[501,533,534],{"class":511},"v1",[501,536,516],{"class":515},[501,538,539],{"class":511},"cri\"",[501,541,516],{"class":515},[501,543,544],{"class":511},"registry",[501,546,547],{"class":507},"]\n",[501,549,551,555,558,561,565],{"class":503,"line":550},2,[501,552,554],{"class":553},"sIi04","config_path",[501,556,557],{"class":507}," =",[501,559,560],{"class":507}," \"",[501,562,564],{"class":563},"sQLXx","\u002Fetc\u002Fcontainerd\u002Fcerts.d",[501,566,567],{"class":507},"\"\n",[170,569,570,571,574],{},"Containerd loads one config file rather than merging them, so add that block to a full copy of your nodes' existing config and restart containerd. On some distributions, Flatcar among them, the running config lives outside ",[174,572,573],{},"\u002Fetc"," and the service has to be pointed at the new file explicitly.",[576,577,578],"note",{},"This is per-node state, so it is lost when a node is replaced. If you build your own node images, bake it in.",[480,580,582],{"id":581},"give-cert-manager-dns-01-access","Give cert-manager DNS-01 access",[170,584,585],{},"Platform certificates include a wildcard for the workload domain, and a wildcard can only be validated over DNS-01. So cert-manager needs an API credential for your DNS provider that can write TXT records in both zones. Scope it to those zones and nothing else: it is the one credential here that can change public DNS.",[170,587,588,589,594,595,597],{},"How you create it depends on the provider, and ",[245,590,593],{"href":591,"rel":592},"https:\u002F\u002Fcert-manager.io\u002Fdocs\u002Fconfiguration\u002Facme\u002Fdns01\u002F",[249],"cert-manager documents a solver for each of the common ones",". Whichever you use, the shape is the same. Put the provider token in a Secret in the ",[174,596,266],{}," namespace:",[492,599,603],{"className":600,"code":601,"language":602,"meta":497,"style":497},"language-bash shiki shiki-themes gruvbox-dark-medium gruvbox-dark-medium gruvbox-dark-medium","kubectl create secret generic dns-credentials \\\n  -n cert-manager \\\n  --from-literal=api-token='\u003Cprovider-api-token>'\n","bash",[174,604,605,626,636],{"__ignoreMap":497},[501,606,607,610,613,616,619,622],{"class":503,"line":504},[501,608,609],{"class":511},"kubectl",[501,611,612],{"class":563}," create",[501,614,615],{"class":563}," secret",[501,617,618],{"class":563}," generic",[501,620,621],{"class":563}," dns-credentials",[501,623,625],{"class":624},"s1Tsi"," \\\n",[501,627,628,631,634],{"class":503,"line":550},[501,629,630],{"class":624},"  -n",[501,632,633],{"class":563}," cert-manager",[501,635,625],{"class":624},[501,637,639,642,645,648],{"class":503,"line":638},3,[501,640,641],{"class":624},"  --from-literal=api-token=",[501,643,644],{"class":507},"'",[501,646,647],{"class":563},"\u003Cprovider-api-token>",[501,649,650],{"class":507},"'\n",[170,652,653,654,657],{},"Then reference it from an issuer in your values file, rather than applying the issuer by hand, with one solver per zone selected by ",[174,655,656],{},"dnsZones",":",[492,659,663],{"className":660,"code":661,"language":662,"meta":497,"style":497},"language-yaml shiki shiki-themes gruvbox-dark-medium gruvbox-dark-medium gruvbox-dark-medium","clusterIssuers:\n  - name: letsencrypt-dns01\n    acme:\n      server: https:\u002F\u002Facme-v02.api.letsencrypt.org\u002Fdirectory\n      email: you@example.com\n      solvers:\n        - selector:\n            dnsZones: [\"\u003Cplatform-domain>\"]\n          dns01:\n            # provider-specific block, see the cert-manager docs\n        - selector:\n            dnsZones: [\"\u003Cworkload-domain>\"]\n          dns01:\n            # same provider, the other zone\n","yaml",[174,664,665,674,687,694,705,716,724,735,756,764,771,780,798,805],{"__ignoreMap":497},[501,666,667,671],{"class":503,"line":504},[501,668,670],{"class":669},"s3drp","clusterIssuers",[501,672,673],{"class":507},":\n",[501,675,676,679,682,684],{"class":503,"line":550},[501,677,678],{"class":507},"  -",[501,680,681],{"class":669}," name",[501,683,657],{"class":507},[501,685,686],{"class":563}," letsencrypt-dns01\n",[501,688,689,692],{"class":503,"line":638},[501,690,691],{"class":669},"    acme",[501,693,673],{"class":507},[501,695,697,700,702],{"class":503,"line":696},4,[501,698,699],{"class":669},"      server",[501,701,657],{"class":507},[501,703,704],{"class":563}," https:\u002F\u002Facme-v02.api.letsencrypt.org\u002Fdirectory\n",[501,706,708,711,713],{"class":503,"line":707},5,[501,709,710],{"class":669},"      email",[501,712,657],{"class":507},[501,714,715],{"class":563}," you@example.com\n",[501,717,719,722],{"class":503,"line":718},6,[501,720,721],{"class":669},"      solvers",[501,723,673],{"class":507},[501,725,727,730,733],{"class":503,"line":726},7,[501,728,729],{"class":507},"        -",[501,731,732],{"class":669}," selector",[501,734,673],{"class":507},[501,736,738,741,743,746,749,752,754],{"class":503,"line":737},8,[501,739,740],{"class":669},"            dnsZones",[501,742,657],{"class":507},[501,744,745],{"class":507}," [",[501,747,748],{"class":507},"\"",[501,750,751],{"class":563},"\u003Cplatform-domain>",[501,753,748],{"class":507},[501,755,547],{"class":507},[501,757,759,762],{"class":503,"line":758},9,[501,760,761],{"class":669},"          dns01",[501,763,673],{"class":507},[501,765,767],{"class":503,"line":766},10,[501,768,770],{"class":769},"sIIdi","            # provider-specific block, see the cert-manager docs\n",[501,772,774,776,778],{"class":503,"line":773},11,[501,775,729],{"class":507},[501,777,732],{"class":669},[501,779,673],{"class":507},[501,781,783,785,787,789,791,794,796],{"class":503,"line":782},12,[501,784,740],{"class":669},[501,786,657],{"class":507},[501,788,745],{"class":507},[501,790,748],{"class":507},[501,792,793],{"class":563},"\u003Cworkload-domain>",[501,795,748],{"class":507},[501,797,547],{"class":507},[501,799,801,803],{"class":503,"line":800},13,[501,802,761],{"class":669},[501,804,673],{"class":507},[501,806,808],{"class":503,"line":807},14,[501,809,810],{"class":769},"            # same provider, the other zone\n",[170,812,813],{},"If the two domains live with different providers, give each solver its own provider block.",[170,815,816],{},"A second issuer, using HTTP-01, handles certificates for the custom domains your users attach to their own services. That one needs no credentials, because it validates over the gateway that is already serving the domain.",[186,818,820],{"id":819},"the-infra-chart","The infra chart",[170,822,823,824,827],{},"Two files drive this chart. ",[174,825,826],{},"infra-values.yaml"," holds the shape of your installation:",[492,829,831],{"className":660,"code":830,"language":662,"meta":497,"style":497},"zot:\n  persistence: true\n  pvc:\n    create: true\n    storage: 20Gi\n    storageClassName: \u003Cstorage-class>\n  service:\n    # Pin this inside your service CIDR. Nodes pull images by this address,\n    # so it has to be stable and it has to match registryPull.host later.\n    clusterIP: \"10.96.100.100\"\n  mountConfig: true\n\ngateway:\n  className: cilium\n  listeners:\n    - name: platform-http\n      protocol: HTTP\n      port: 80\n      hostname: \u003Cplatform-domain>\n    - name: platform-https\n      protocol: HTTPS\n      port: 443\n      hostname: \u003Cplatform-domain>\n      tls:\n        mode: Terminate\n        certificateRefs:\n          - name: platform-tls\n    - name: workload-http\n      protocol: HTTP\n      port: 80\n      hostname: \"*.\u003Cworkload-domain>\"\n    - name: workload-https\n      protocol: HTTPS\n      port: 443\n      hostname: \"*.\u003Cworkload-domain>\"\n      tls:\n        mode: Terminate\n        certificateRefs:\n          - name: workload-tls\n    - name: id-http\n      protocol: HTTP\n      port: 80\n      hostname: id.\u003Cplatform-domain>\n    - name: id-https\n      protocol: HTTPS\n      port: 443\n      hostname: id.\u003Cplatform-domain>\n      tls:\n        mode: Terminate\n        certificateRefs:\n          - name: id-tls\n\ncertificates:\n  - name: platform-tls\n    secretName: platform-tls\n    issuerRef: { name: letsencrypt-dns01 }\n    dnsNames: [\"\u003Cplatform-domain>\"]\n  - name: workload-tls\n    secretName: workload-tls\n    issuerRef: { name: letsencrypt-dns01 }\n    dnsNames: [\"*.\u003Cworkload-domain>\"]\n  - name: id-tls\n    secretName: id-tls\n    issuerRef: { name: letsencrypt-dns01 }\n    dnsNames: [\"id.\u003Cplatform-domain>\"]\n\nclusterIssuers: # both issuers from the previous section\n\nlogto:\n  enabled: true\n  endpoint: https:\u002F\u002Fid.\u003Cplatform-domain>\n  postgres:\n    storageClass: \u003Cstorage-class>\n\nvictoria-metrics-single:\n  enabled: true\n  server:\n    persistentVolume:\n      storageClassName: \u003Cstorage-class>\n\nvictoria-logs-single:\n  enabled: true\n  server:\n    persistentVolume:\n      storageClassName: \u003Cstorage-class>\n\notelCollector:\n  daemonset: { enabled: true }\n  deployment: { enabled: true }\n",[174,832,833,840,850,857,866,876,886,893,898,903,917,926,932,939,949,957,970,981,992,1003,1015,1025,1035,1044,1052,1063,1071,1084,1096,1105,1114,1128,1140,1149,1158,1171,1178,1187,1194,1206,1218,1227,1236,1246,1258,1267,1276,1285,1292,1301,1308,1320,1325,1333,1344,1354,1375,1393,1404,1413,1430,1447,1458,1467,1484,1502,1507,1517,1522,1530,1540,1551,1559,1569,1574,1582,1591,1599,1607,1617,1622,1630,1639,1646,1653,1662,1667,1675,1695],{"__ignoreMap":497},[501,834,835,838],{"class":503,"line":504},[501,836,837],{"class":669},"zot",[501,839,673],{"class":507},[501,841,842,845,847],{"class":503,"line":550},[501,843,844],{"class":669},"  persistence",[501,846,657],{"class":507},[501,848,849],{"class":624}," true\n",[501,851,852,855],{"class":503,"line":638},[501,853,854],{"class":669},"  pvc",[501,856,673],{"class":507},[501,858,859,862,864],{"class":503,"line":696},[501,860,861],{"class":669},"    create",[501,863,657],{"class":507},[501,865,849],{"class":624},[501,867,868,871,873],{"class":503,"line":707},[501,869,870],{"class":669},"    storage",[501,872,657],{"class":507},[501,874,875],{"class":563}," 20Gi\n",[501,877,878,881,883],{"class":503,"line":718},[501,879,880],{"class":669},"    storageClassName",[501,882,657],{"class":507},[501,884,885],{"class":563}," \u003Cstorage-class>\n",[501,887,888,891],{"class":503,"line":726},[501,889,890],{"class":669},"  service",[501,892,673],{"class":507},[501,894,895],{"class":503,"line":737},[501,896,897],{"class":769},"    # Pin this inside your service CIDR. Nodes pull images by this address,\n",[501,899,900],{"class":503,"line":758},[501,901,902],{"class":769},"    # so it has to be stable and it has to match registryPull.host later.\n",[501,904,905,908,910,912,915],{"class":503,"line":766},[501,906,907],{"class":669},"    clusterIP",[501,909,657],{"class":507},[501,911,560],{"class":507},[501,913,914],{"class":563},"10.96.100.100",[501,916,567],{"class":507},[501,918,919,922,924],{"class":503,"line":773},[501,920,921],{"class":669},"  mountConfig",[501,923,657],{"class":507},[501,925,849],{"class":624},[501,927,928],{"class":503,"line":782},[501,929,931],{"emptyLinePlaceholder":930},true,"\n",[501,933,934,937],{"class":503,"line":800},[501,935,936],{"class":669},"gateway",[501,938,673],{"class":507},[501,940,941,944,946],{"class":503,"line":807},[501,942,943],{"class":669},"  className",[501,945,657],{"class":507},[501,947,948],{"class":563}," cilium\n",[501,950,952,955],{"class":503,"line":951},15,[501,953,954],{"class":669},"  listeners",[501,956,673],{"class":507},[501,958,960,963,965,967],{"class":503,"line":959},16,[501,961,962],{"class":507},"    -",[501,964,681],{"class":669},[501,966,657],{"class":507},[501,968,969],{"class":563}," platform-http\n",[501,971,973,976,978],{"class":503,"line":972},17,[501,974,975],{"class":669},"      protocol",[501,977,657],{"class":507},[501,979,980],{"class":563}," HTTP\n",[501,982,984,987,989],{"class":503,"line":983},18,[501,985,986],{"class":669},"      port",[501,988,657],{"class":507},[501,990,991],{"class":624}," 80\n",[501,993,995,998,1000],{"class":503,"line":994},19,[501,996,997],{"class":669},"      hostname",[501,999,657],{"class":507},[501,1001,1002],{"class":563}," \u003Cplatform-domain>\n",[501,1004,1006,1008,1010,1012],{"class":503,"line":1005},20,[501,1007,962],{"class":507},[501,1009,681],{"class":669},[501,1011,657],{"class":507},[501,1013,1014],{"class":563}," platform-https\n",[501,1016,1018,1020,1022],{"class":503,"line":1017},21,[501,1019,975],{"class":669},[501,1021,657],{"class":507},[501,1023,1024],{"class":563}," HTTPS\n",[501,1026,1028,1030,1032],{"class":503,"line":1027},22,[501,1029,986],{"class":669},[501,1031,657],{"class":507},[501,1033,1034],{"class":624}," 443\n",[501,1036,1038,1040,1042],{"class":503,"line":1037},23,[501,1039,997],{"class":669},[501,1041,657],{"class":507},[501,1043,1002],{"class":563},[501,1045,1047,1050],{"class":503,"line":1046},24,[501,1048,1049],{"class":669},"      tls",[501,1051,673],{"class":507},[501,1053,1055,1058,1060],{"class":503,"line":1054},25,[501,1056,1057],{"class":669},"        mode",[501,1059,657],{"class":507},[501,1061,1062],{"class":563}," Terminate\n",[501,1064,1066,1069],{"class":503,"line":1065},26,[501,1067,1068],{"class":669},"        certificateRefs",[501,1070,673],{"class":507},[501,1072,1074,1077,1079,1081],{"class":503,"line":1073},27,[501,1075,1076],{"class":507},"          -",[501,1078,681],{"class":669},[501,1080,657],{"class":507},[501,1082,1083],{"class":563}," platform-tls\n",[501,1085,1087,1089,1091,1093],{"class":503,"line":1086},28,[501,1088,962],{"class":507},[501,1090,681],{"class":669},[501,1092,657],{"class":507},[501,1094,1095],{"class":563}," workload-http\n",[501,1097,1099,1101,1103],{"class":503,"line":1098},29,[501,1100,975],{"class":669},[501,1102,657],{"class":507},[501,1104,980],{"class":563},[501,1106,1108,1110,1112],{"class":503,"line":1107},30,[501,1109,986],{"class":669},[501,1111,657],{"class":507},[501,1113,991],{"class":624},[501,1115,1117,1119,1121,1123,1126],{"class":503,"line":1116},31,[501,1118,997],{"class":669},[501,1120,657],{"class":507},[501,1122,560],{"class":507},[501,1124,1125],{"class":563},"*.\u003Cworkload-domain>",[501,1127,567],{"class":507},[501,1129,1131,1133,1135,1137],{"class":503,"line":1130},32,[501,1132,962],{"class":507},[501,1134,681],{"class":669},[501,1136,657],{"class":507},[501,1138,1139],{"class":563}," workload-https\n",[501,1141,1143,1145,1147],{"class":503,"line":1142},33,[501,1144,975],{"class":669},[501,1146,657],{"class":507},[501,1148,1024],{"class":563},[501,1150,1152,1154,1156],{"class":503,"line":1151},34,[501,1153,986],{"class":669},[501,1155,657],{"class":507},[501,1157,1034],{"class":624},[501,1159,1161,1163,1165,1167,1169],{"class":503,"line":1160},35,[501,1162,997],{"class":669},[501,1164,657],{"class":507},[501,1166,560],{"class":507},[501,1168,1125],{"class":563},[501,1170,567],{"class":507},[501,1172,1174,1176],{"class":503,"line":1173},36,[501,1175,1049],{"class":669},[501,1177,673],{"class":507},[501,1179,1181,1183,1185],{"class":503,"line":1180},37,[501,1182,1057],{"class":669},[501,1184,657],{"class":507},[501,1186,1062],{"class":563},[501,1188,1190,1192],{"class":503,"line":1189},38,[501,1191,1068],{"class":669},[501,1193,673],{"class":507},[501,1195,1197,1199,1201,1203],{"class":503,"line":1196},39,[501,1198,1076],{"class":507},[501,1200,681],{"class":669},[501,1202,657],{"class":507},[501,1204,1205],{"class":563}," workload-tls\n",[501,1207,1209,1211,1213,1215],{"class":503,"line":1208},40,[501,1210,962],{"class":507},[501,1212,681],{"class":669},[501,1214,657],{"class":507},[501,1216,1217],{"class":563}," id-http\n",[501,1219,1221,1223,1225],{"class":503,"line":1220},41,[501,1222,975],{"class":669},[501,1224,657],{"class":507},[501,1226,980],{"class":563},[501,1228,1230,1232,1234],{"class":503,"line":1229},42,[501,1231,986],{"class":669},[501,1233,657],{"class":507},[501,1235,991],{"class":624},[501,1237,1239,1241,1243],{"class":503,"line":1238},43,[501,1240,997],{"class":669},[501,1242,657],{"class":507},[501,1244,1245],{"class":563}," id.\u003Cplatform-domain>\n",[501,1247,1249,1251,1253,1255],{"class":503,"line":1248},44,[501,1250,962],{"class":507},[501,1252,681],{"class":669},[501,1254,657],{"class":507},[501,1256,1257],{"class":563}," id-https\n",[501,1259,1261,1263,1265],{"class":503,"line":1260},45,[501,1262,975],{"class":669},[501,1264,657],{"class":507},[501,1266,1024],{"class":563},[501,1268,1270,1272,1274],{"class":503,"line":1269},46,[501,1271,986],{"class":669},[501,1273,657],{"class":507},[501,1275,1034],{"class":624},[501,1277,1279,1281,1283],{"class":503,"line":1278},47,[501,1280,997],{"class":669},[501,1282,657],{"class":507},[501,1284,1245],{"class":563},[501,1286,1288,1290],{"class":503,"line":1287},48,[501,1289,1049],{"class":669},[501,1291,673],{"class":507},[501,1293,1295,1297,1299],{"class":503,"line":1294},49,[501,1296,1057],{"class":669},[501,1298,657],{"class":507},[501,1300,1062],{"class":563},[501,1302,1304,1306],{"class":503,"line":1303},50,[501,1305,1068],{"class":669},[501,1307,673],{"class":507},[501,1309,1311,1313,1315,1317],{"class":503,"line":1310},51,[501,1312,1076],{"class":507},[501,1314,681],{"class":669},[501,1316,657],{"class":507},[501,1318,1319],{"class":563}," id-tls\n",[501,1321,1323],{"class":503,"line":1322},52,[501,1324,931],{"emptyLinePlaceholder":930},[501,1326,1328,1331],{"class":503,"line":1327},53,[501,1329,1330],{"class":669},"certificates",[501,1332,673],{"class":507},[501,1334,1336,1338,1340,1342],{"class":503,"line":1335},54,[501,1337,678],{"class":507},[501,1339,681],{"class":669},[501,1341,657],{"class":507},[501,1343,1083],{"class":563},[501,1345,1347,1350,1352],{"class":503,"line":1346},55,[501,1348,1349],{"class":669},"    secretName",[501,1351,657],{"class":507},[501,1353,1083],{"class":563},[501,1355,1357,1360,1362,1365,1367,1369,1372],{"class":503,"line":1356},56,[501,1358,1359],{"class":669},"    issuerRef",[501,1361,657],{"class":507},[501,1363,1364],{"class":507}," {",[501,1366,681],{"class":669},[501,1368,657],{"class":507},[501,1370,1371],{"class":563}," letsencrypt-dns01",[501,1373,1374],{"class":507}," }\n",[501,1376,1378,1381,1383,1385,1387,1389,1391],{"class":503,"line":1377},57,[501,1379,1380],{"class":669},"    dnsNames",[501,1382,657],{"class":507},[501,1384,745],{"class":507},[501,1386,748],{"class":507},[501,1388,751],{"class":563},[501,1390,748],{"class":507},[501,1392,547],{"class":507},[501,1394,1396,1398,1400,1402],{"class":503,"line":1395},58,[501,1397,678],{"class":507},[501,1399,681],{"class":669},[501,1401,657],{"class":507},[501,1403,1205],{"class":563},[501,1405,1407,1409,1411],{"class":503,"line":1406},59,[501,1408,1349],{"class":669},[501,1410,657],{"class":507},[501,1412,1205],{"class":563},[501,1414,1416,1418,1420,1422,1424,1426,1428],{"class":503,"line":1415},60,[501,1417,1359],{"class":669},[501,1419,657],{"class":507},[501,1421,1364],{"class":507},[501,1423,681],{"class":669},[501,1425,657],{"class":507},[501,1427,1371],{"class":563},[501,1429,1374],{"class":507},[501,1431,1433,1435,1437,1439,1441,1443,1445],{"class":503,"line":1432},61,[501,1434,1380],{"class":669},[501,1436,657],{"class":507},[501,1438,745],{"class":507},[501,1440,748],{"class":507},[501,1442,1125],{"class":563},[501,1444,748],{"class":507},[501,1446,547],{"class":507},[501,1448,1450,1452,1454,1456],{"class":503,"line":1449},62,[501,1451,678],{"class":507},[501,1453,681],{"class":669},[501,1455,657],{"class":507},[501,1457,1319],{"class":563},[501,1459,1461,1463,1465],{"class":503,"line":1460},63,[501,1462,1349],{"class":669},[501,1464,657],{"class":507},[501,1466,1319],{"class":563},[501,1468,1470,1472,1474,1476,1478,1480,1482],{"class":503,"line":1469},64,[501,1471,1359],{"class":669},[501,1473,657],{"class":507},[501,1475,1364],{"class":507},[501,1477,681],{"class":669},[501,1479,657],{"class":507},[501,1481,1371],{"class":563},[501,1483,1374],{"class":507},[501,1485,1487,1489,1491,1493,1495,1498,1500],{"class":503,"line":1486},65,[501,1488,1380],{"class":669},[501,1490,657],{"class":507},[501,1492,745],{"class":507},[501,1494,748],{"class":507},[501,1496,1497],{"class":563},"id.\u003Cplatform-domain>",[501,1499,748],{"class":507},[501,1501,547],{"class":507},[501,1503,1505],{"class":503,"line":1504},66,[501,1506,931],{"emptyLinePlaceholder":930},[501,1508,1510,1512,1514],{"class":503,"line":1509},67,[501,1511,670],{"class":669},[501,1513,657],{"class":507},[501,1515,1516],{"class":769}," # both issuers from the previous section\n",[501,1518,1520],{"class":503,"line":1519},68,[501,1521,931],{"emptyLinePlaceholder":930},[501,1523,1525,1528],{"class":503,"line":1524},69,[501,1526,1527],{"class":669},"logto",[501,1529,673],{"class":507},[501,1531,1533,1536,1538],{"class":503,"line":1532},70,[501,1534,1535],{"class":669},"  enabled",[501,1537,657],{"class":507},[501,1539,849],{"class":624},[501,1541,1543,1546,1548],{"class":503,"line":1542},71,[501,1544,1545],{"class":669},"  endpoint",[501,1547,657],{"class":507},[501,1549,1550],{"class":563}," https:\u002F\u002Fid.\u003Cplatform-domain>\n",[501,1552,1554,1557],{"class":503,"line":1553},72,[501,1555,1556],{"class":669},"  postgres",[501,1558,673],{"class":507},[501,1560,1562,1565,1567],{"class":503,"line":1561},73,[501,1563,1564],{"class":669},"    storageClass",[501,1566,657],{"class":507},[501,1568,885],{"class":563},[501,1570,1572],{"class":503,"line":1571},74,[501,1573,931],{"emptyLinePlaceholder":930},[501,1575,1577,1580],{"class":503,"line":1576},75,[501,1578,1579],{"class":669},"victoria-metrics-single",[501,1581,673],{"class":507},[501,1583,1585,1587,1589],{"class":503,"line":1584},76,[501,1586,1535],{"class":669},[501,1588,657],{"class":507},[501,1590,849],{"class":624},[501,1592,1594,1597],{"class":503,"line":1593},77,[501,1595,1596],{"class":669},"  server",[501,1598,673],{"class":507},[501,1600,1602,1605],{"class":503,"line":1601},78,[501,1603,1604],{"class":669},"    persistentVolume",[501,1606,673],{"class":507},[501,1608,1610,1613,1615],{"class":503,"line":1609},79,[501,1611,1612],{"class":669},"      storageClassName",[501,1614,657],{"class":507},[501,1616,885],{"class":563},[501,1618,1620],{"class":503,"line":1619},80,[501,1621,931],{"emptyLinePlaceholder":930},[501,1623,1625,1628],{"class":503,"line":1624},81,[501,1626,1627],{"class":669},"victoria-logs-single",[501,1629,673],{"class":507},[501,1631,1633,1635,1637],{"class":503,"line":1632},82,[501,1634,1535],{"class":669},[501,1636,657],{"class":507},[501,1638,849],{"class":624},[501,1640,1642,1644],{"class":503,"line":1641},83,[501,1643,1596],{"class":669},[501,1645,673],{"class":507},[501,1647,1649,1651],{"class":503,"line":1648},84,[501,1650,1604],{"class":669},[501,1652,673],{"class":507},[501,1654,1656,1658,1660],{"class":503,"line":1655},85,[501,1657,1612],{"class":669},[501,1659,657],{"class":507},[501,1661,885],{"class":563},[501,1663,1665],{"class":503,"line":1664},86,[501,1666,931],{"emptyLinePlaceholder":930},[501,1668,1670,1673],{"class":503,"line":1669},87,[501,1671,1672],{"class":669},"otelCollector",[501,1674,673],{"class":507},[501,1676,1678,1681,1683,1685,1688,1690,1693],{"class":503,"line":1677},88,[501,1679,1680],{"class":669},"  daemonset",[501,1682,657],{"class":507},[501,1684,1364],{"class":507},[501,1686,1687],{"class":669}," enabled",[501,1689,657],{"class":507},[501,1691,1692],{"class":624}," true",[501,1694,1374],{"class":507},[501,1696,1698,1701,1703,1705,1707,1709,1711],{"class":503,"line":1697},89,[501,1699,1700],{"class":669},"  deployment",[501,1702,657],{"class":507},[501,1704,1364],{"class":507},[501,1706,1687],{"class":669},[501,1708,657],{"class":507},[501,1710,1692],{"class":624},[501,1712,1374],{"class":507},[170,1714,1715],{},"Every listener needs a matching certificate, and the certificate names here are what the listeners reference.",[170,1717,1718,1721],{},[174,1719,1720],{},"infra-secrets.yaml"," holds the credentials. The registry needs two users, one that pushes built images and one that nodes pull with:",[492,1723,1725],{"className":600,"code":1724,"language":602,"meta":497,"style":497},"htpasswd -bBn builder \"$(openssl rand -hex 16)\"  > htpasswd\nhtpasswd -bBn reader  \"$(openssl rand -hex 16)\" >> htpasswd\n",[174,1726,1727,1762],{"__ignoreMap":497},[501,1728,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759],{"class":503,"line":504},[501,1730,1731],{"class":511},"htpasswd",[501,1733,1734],{"class":624}," -bBn",[501,1736,1737],{"class":563}," builder",[501,1739,1740],{"class":507}," \"$(",[501,1742,1743],{"class":511},"openssl",[501,1745,1746],{"class":563}," rand ",[501,1748,1749],{"class":624},"-hex",[501,1751,1752],{"class":624}," 16",[501,1754,1755],{"class":507},")\"",[501,1757,1758],{"class":669},"  >",[501,1760,1761],{"class":563}," htpasswd\n",[501,1763,1764,1766,1768,1771,1774,1776,1778,1780,1782,1784,1787],{"class":503,"line":550},[501,1765,1731],{"class":511},[501,1767,1734],{"class":624},[501,1769,1770],{"class":563}," reader",[501,1772,1773],{"class":507},"  \"$(",[501,1775,1743],{"class":511},[501,1777,1746],{"class":563},[501,1779,1749],{"class":624},[501,1781,1752],{"class":624},[501,1783,1755],{"class":507},[501,1785,1786],{"class":669}," >>",[501,1788,1761],{"class":563},[170,1790,1791],{},"Keep both plaintext passwords, because the platform chart needs them again. Then:",[492,1793,1795],{"className":660,"code":1794,"language":662,"meta":497,"style":497},"zot:\n  configFiles:\n    htpasswd: |\n      # both lines from the file above\n\nlogto:\n  secrets:\n    secretVaultKek: \"\u003Copenssl rand -base64 32>\"\n",[174,1796,1797,1803,1810,1821,1826,1830,1836,1843],{"__ignoreMap":497},[501,1798,1799,1801],{"class":503,"line":504},[501,1800,837],{"class":669},[501,1802,673],{"class":507},[501,1804,1805,1808],{"class":503,"line":550},[501,1806,1807],{"class":669},"  configFiles",[501,1809,673],{"class":507},[501,1811,1812,1815,1817],{"class":503,"line":638},[501,1813,1814],{"class":669},"    htpasswd",[501,1816,657],{"class":507},[501,1818,1820],{"class":1819},"sJHtc"," |\n",[501,1822,1823],{"class":503,"line":696},[501,1824,1825],{"class":563},"      # both lines from the file above\n",[501,1827,1828],{"class":503,"line":707},[501,1829,931],{"emptyLinePlaceholder":930},[501,1831,1832,1834],{"class":503,"line":718},[501,1833,1527],{"class":669},[501,1835,673],{"class":507},[501,1837,1838,1841],{"class":503,"line":726},[501,1839,1840],{"class":669},"  secrets",[501,1842,673],{"class":507},[501,1844,1845,1848,1850,1852,1855],{"class":503,"line":737},[501,1846,1847],{"class":669},"    secretVaultKek",[501,1849,657],{"class":507},[501,1851,560],{"class":507},[501,1853,1854],{"class":563},"\u003Copenssl rand -base64 32>",[501,1856,567],{"class":507},[170,1858,1859,1860,1862],{},"Install ",[174,1861,176],{}," in two passes the first time. The chart carries both the CloudNativePG operator and a PostgreSQL cluster that needs its CRDs, so a single-pass install cannot resolve them.",[170,1864,1865],{},"Pass one brings up the operator with the identity provider switched off:",[492,1867,1869],{"className":600,"code":1868,"language":602,"meta":497,"style":497},"helm upgrade --install lucity-infra oci:\u002F\u002Fghcr.io\u002Fzeitlos\u002Flucity\u002Fcharts\u002Flucity-infra \\\n  --version \u003Cchart-version> -n lucity-system --create-namespace \\\n  -f infra-values.yaml -f infra-secrets.yaml --set logto.enabled=false\n",[174,1870,1871,1890,1918],{"__ignoreMap":497},[501,1872,1873,1876,1879,1882,1885,1888],{"class":503,"line":504},[501,1874,1875],{"class":511},"helm",[501,1877,1878],{"class":563}," upgrade",[501,1880,1881],{"class":624}," --install",[501,1883,1884],{"class":563}," lucity-infra",[501,1886,1887],{"class":563}," oci:\u002F\u002Fghcr.io\u002Fzeitlos\u002Flucity\u002Fcharts\u002Flucity-infra",[501,1889,625],{"class":624},[501,1891,1892,1895,1898,1901,1904,1907,1910,1913,1916],{"class":503,"line":550},[501,1893,1894],{"class":624},"  --version",[501,1896,1897],{"class":669}," \u003C",[501,1899,1900],{"class":563},"chart-versio",[501,1902,1903],{"class":515},"n",[501,1905,1906],{"class":669},">",[501,1908,1909],{"class":624}," -n",[501,1911,1912],{"class":563}," lucity-system",[501,1914,1915],{"class":624}," --create-namespace",[501,1917,625],{"class":624},[501,1919,1920,1923,1926,1929,1932,1935,1938],{"class":503,"line":638},[501,1921,1922],{"class":624},"  -f",[501,1924,1925],{"class":563}," infra-values.yaml",[501,1927,1928],{"class":624}," -f",[501,1930,1931],{"class":563}," infra-secrets.yaml",[501,1933,1934],{"class":624}," --set",[501,1936,1937],{"class":563}," logto.enabled=",[501,1939,1940],{"class":624},"false\n",[170,1942,1943],{},"Wait for the operator and its webhook:",[492,1945,1947],{"className":600,"code":1946,"language":602,"meta":497,"style":497},"kubectl -n lucity-system wait --for=condition=Available deploy\u002Flucity-infra-cloudnative-pg --timeout=180s\n",[174,1948,1949],{"__ignoreMap":497},[501,1950,1951,1953,1955,1957,1960,1963,1966],{"class":503,"line":504},[501,1952,609],{"class":511},[501,1954,1909],{"class":624},[501,1956,1912],{"class":563},[501,1958,1959],{"class":563}," wait",[501,1961,1962],{"class":624}," --for=condition=Available",[501,1964,1965],{"class":563}," deploy\u002Flucity-infra-cloudnative-pg",[501,1967,1968],{"class":624}," --timeout=180s\n",[170,1970,1971],{},"Then pass two, with nothing overridden:",[492,1973,1975],{"className":600,"code":1974,"language":602,"meta":497,"style":497},"helm upgrade --install lucity-infra oci:\u002F\u002Fghcr.io\u002Fzeitlos\u002Flucity\u002Fcharts\u002Flucity-infra \\\n  --version \u003Cchart-version> -n lucity-system \\\n  -f infra-values.yaml -f infra-secrets.yaml\n",[174,1976,1977,1991,2009],{"__ignoreMap":497},[501,1978,1979,1981,1983,1985,1987,1989],{"class":503,"line":504},[501,1980,1875],{"class":511},[501,1982,1878],{"class":563},[501,1984,1881],{"class":624},[501,1986,1884],{"class":563},[501,1988,1887],{"class":563},[501,1990,625],{"class":624},[501,1992,1993,1995,1997,1999,2001,2003,2005,2007],{"class":503,"line":550},[501,1994,1894],{"class":624},[501,1996,1897],{"class":669},[501,1998,1900],{"class":563},[501,2000,1903],{"class":515},[501,2002,1906],{"class":669},[501,2004,1909],{"class":624},[501,2006,1912],{"class":563},[501,2008,625],{"class":624},[501,2010,2011,2013,2015,2017],{"class":503,"line":638},[501,2012,1922],{"class":624},[501,2014,1925],{"class":563},[501,2016,1928],{"class":624},[501,2018,2019],{"class":563}," infra-secrets.yaml\n",[170,2021,2022],{},"Upgrades after this are single-pass, because the CRDs are already in the cluster.",[576,2024,2025,2026,2029,2030,2033],{},"Always pass ",[174,2027,2028],{},"--version"," explicitly, because ",[174,2031,2032],{},"latest"," resolves to the highest stable release and can move you backwards.",[170,2035,2036],{},"Give the identity provider a minute to pick up its database credentials, then check that everything is up:",[492,2038,2040],{"className":600,"code":2039,"language":602,"meta":497,"style":497},"kubectl -n lucity-system get pods\nkubectl -n lucity-system get cluster\nkubectl -n lucity-system get certificate\nkubectl get gateway -A\n",[174,2041,2042,2056,2069,2082],{"__ignoreMap":497},[501,2043,2044,2046,2048,2050,2053],{"class":503,"line":504},[501,2045,609],{"class":511},[501,2047,1909],{"class":624},[501,2049,1912],{"class":563},[501,2051,2052],{"class":563}," get",[501,2054,2055],{"class":563}," pods\n",[501,2057,2058,2060,2062,2064,2066],{"class":503,"line":550},[501,2059,609],{"class":511},[501,2061,1909],{"class":624},[501,2063,1912],{"class":563},[501,2065,2052],{"class":563},[501,2067,2068],{"class":563}," cluster\n",[501,2070,2071,2073,2075,2077,2079],{"class":503,"line":638},[501,2072,609],{"class":511},[501,2074,1909],{"class":624},[501,2076,1912],{"class":563},[501,2078,2052],{"class":563},[501,2080,2081],{"class":563}," certificate\n",[501,2083,2084,2086,2088,2091],{"class":503,"line":696},[501,2085,609],{"class":511},[501,2087,2052],{"class":563},[501,2089,2090],{"class":563}," gateway",[501,2092,2093],{"class":624}," -A\n",[170,2095,2096,2097,516],{},"The gateway should report an address and ",[174,2098,2099],{},"PROGRAMMED: True",[186,2101,2103],{"id":2102},"dns-for-the-platform","DNS for the platform",[170,2105,2106],{},"The gateway now has an address, so the platform hostnames can point at it. Three records carry the whole installation:",[194,2108,2109,2122],{},[197,2110,2111],{},[200,2112,2113,2116,2119],{},[203,2114,2115],{},"Record",[203,2117,2118],{},"Points at",[203,2120,2121],{},"Serves",[210,2123,2124,2138,2154],{},[200,2125,2126,2132,2135],{},[215,2127,2128,2131],{},[174,2129,2130],{},"A"," on the platform domain",[215,2133,2134],{},"Gateway address",[215,2136,2137],{},"Dashboard, API, docs",[200,2139,2140,2149,2151],{},[215,2141,2142,2144,2145,2148],{},[174,2143,2130],{}," on ",[174,2146,2147],{},"id."," of the platform domain",[215,2150,2134],{},[215,2152,2153],{},"The identity provider",[200,2155,2156,2164,2166],{},[215,2157,2158,2144,2160,2163],{},[174,2159,2130],{},[174,2161,2162],{},"*"," of the workload domain",[215,2165,2134],{},[215,2167,2168],{},"Every deployed service",[170,2170,2171],{},"The wildcard is what makes a freshly generated service hostname work immediately, with no DNS wait and no per-service record.",[170,2173,2174],{},"Once a wildcard covers the workload domain, the only records left are those two platform hostnames, which never change, so external-dns buys you little here.",[186,2176,2178],{"id":2177},"bootstrapping-the-identity-provider","Bootstrapping the identity provider",[170,2180,2181],{},"Logto starts empty and the platform expects a specific shape inside it. Nothing automates this yet, so it is console work.",[170,2183,2184],{},"The console is not exposed publicly, so reach it over a port-forward:",[492,2186,2188],{"className":600,"code":2187,"language":602,"meta":497,"style":497},"kubectl -n lucity-system port-forward svc\u002Flucity-infra-logto 3002:3002\n",[174,2189,2190],{"__ignoreMap":497},[501,2191,2192,2194,2196,2198,2201,2204],{"class":503,"line":504},[501,2193,609],{"class":511},[501,2195,1909],{"class":624},[501,2197,1912],{"class":563},[501,2199,2200],{"class":563}," port-forward",[501,2202,2203],{"class":563}," svc\u002Flucity-infra-logto",[501,2205,2206],{"class":563}," 3002:3002\n",[170,2208,2209,2210,2213],{},"Open ",[174,2211,2212],{},"http:\u002F\u002Flocalhost:3002"," and create the first admin account. Then work through the sections below in order.",[170,2215,2216],{},"Workspaces are Logto organizations, and a workspace member's rights come from their organization role. Build this in the order below, because the permissions live on the API resource rather than on the organization.",[480,2218,2220],{"id":2219},"api-resource","API resource",[170,2222,2223,2224,2227,2228,2231],{},"Create the API resource first. Its identifier is the audience you will give the conductor, for example ",[174,2225,2226],{},"https:\u002F\u002Fapi.\u003Cyour-platform-domain>",", and it becomes the ",[174,2229,2230],{},"OIDC_AUDIENCE"," value.",[170,2233,2234,2235,2238,2239,2242,2243,2246,2247,516],{},"Then give that resource three ",[327,2236,2237],{},"permissions",": ",[174,2240,2241],{},"admin",", ",[174,2244,2245],{},"member",", and ",[174,2248,2249],{},"deployer",[2251,2252,2253,2254,2257],"warning",{},"These must be ",[327,2255,2256],{},"API resource permissions",", not organization permissions, because the platform requests its scopes against the API resource.",[480,2259,2261],{"id":2260},"organization-roles","Organization roles",[170,2263,2264,2265,2268],{},"Now create the roles under ",[327,2266,2267],{},"Organization template",". Each asks for a name, a description, and a role type. Assign the API resource permissions from the previous step, leaving organization permissions empty:",[194,2270,2271,2284],{},[197,2272,2273],{},[200,2274,2275,2278,2281],{},[203,2276,2277],{},"Role",[203,2279,2280],{},"Type",[203,2282,2283],{},"Permissions",[210,2285,2286,2299,2311,2325,2338],{},[200,2287,2288,2292,2295],{},[215,2289,2290],{},[174,2291,2241],{},[215,2293,2294],{},"User",[215,2296,2297],{},[174,2298,2241],{},[200,2300,2301,2305,2307],{},[215,2302,2303],{},[174,2304,2245],{},[215,2306,2294],{},[215,2308,2309],{},[174,2310,2245],{},[200,2312,2313,2318,2321],{},[215,2314,2315],{},[174,2316,2317],{},"machine-admin",[215,2319,2320],{},"Machine-to-machine",[215,2322,2323],{},[174,2324,2241],{},[200,2326,2327,2332,2334],{},[215,2328,2329],{},[174,2330,2331],{},"machine-member",[215,2333,2320],{},[215,2335,2336],{},[174,2337,2245],{},[200,2339,2340,2345,2347],{},[215,2341,2342],{},[174,2343,2344],{},"machine-deployer",[215,2346,2320],{},[215,2348,2349],{},[174,2350,2249],{},[170,2352,2353,2354,2356,2357,2360,2361,2363],{},"The two ",[327,2355,2294],{}," roles cover people signing in. The three ",[327,2358,2359],{},"machine-to-machine"," roles cover workspace API keys and CI, so ",[174,2362,2344],{}," is what a GitHub Actions deploy runs as.",[2251,2365,2366,2367,251,2369,2371],{},"The names must be exactly ",[174,2368,2241],{},[174,2370,2245],{},", lowercase. Descriptions are yours to write.",[480,2373,2375],{"id":2374},"applications","Applications",[170,2377,2378],{},"Three, because three different clients sign in:",[194,2380,2381,2396],{},[197,2382,2383],{},[200,2384,2385,2388,2390,2393],{},[203,2386,2387],{},"Application",[203,2389,2280],{},[203,2391,2392],{},"Redirect URI",[203,2394,2395],{},"Becomes",[210,2397,2398,2418,2442],{},[200,2399,2400,2403,2408,2413],{},[215,2401,2402],{},"Dashboard",[215,2404,2405],{},[327,2406,2407],{},"SPA",[215,2409,2410],{},[174,2411,2412],{},"https:\u002F\u002F\u003Cplatform-domain>\u002Fauth\u002Fcallback",[215,2414,2415],{},[174,2416,2417],{},"OIDC_CLIENT_ID",[200,2419,2420,2422,2425,2437],{},[215,2421,107],{},[215,2423,2424],{},"Native",[215,2426,2427,2430,2431,251,2434],{},[174,2428,2429],{},"http:\u002F\u002F127.0.0.1:8765\u002Fcallback",", and the same for ",[174,2432,2433],{},"8766",[174,2435,2436],{},"8767",[215,2438,2439],{},[174,2440,2441],{},"OIDC_CLI_CLIENT_ID",[200,2443,2444,2447,2450,2453],{},[215,2445,2446],{},"Machine to machine",[215,2448,2449],{},"M2M",[215,2451,2452],{},"none",[215,2454,2455,251,2458],{},[174,2456,2457],{},"LOGTO_M2M_APP_ID",[174,2459,2460],{},"LOGTO_M2M_APP_SECRET",[2251,2462,2463,2464,2466],{},"The dashboard application must be a ",[327,2465,2407],{},", because the platform authenticates with PKCE and sends no client secret. Logto cannot change an application's type after creation, so fix a mistake by deleting the application and making a new one.",[170,2468,2469],{},"The CLI logs in with PKCE against a loopback listener and takes the first free port, so register all three.",[170,2471,2472,2473,2476],{},"Give the M2M application the ",[327,2474,2475],{},"Logto Management API"," role. The conductor uses it to create organizations, assign roles, and read members, so without it workspace creation fails.",[480,2478,2480],{"id":2479},"github-connector","GitHub connector",[170,2482,2483],{},"Users sign in to Logto, Logto runs the GitHub handshake, and the resulting token is kept in Logto's Secret Vault, so GitHub redirects back to Logto and not to the platform.",[170,2485,2486],{},"Add a GitHub social connector. Three fields on that page matter.",[170,2488,2489,2492,2493,2496],{},[327,2490,2491],{},"Identity provider name"," must be ",[174,2494,2495],{},"github",", which is also the default.",[170,2498,2499,2502,2503,2505,2506,2509],{},[327,2500,2501],{},"Client ID and Client Secret",", under Parameter configuration, take the values from your ",[327,2504,329],{},", not from a separate OAuth App. Leave ",[327,2507,2508],{},"Scope"," empty: a GitHub App's access is decided by where it is installed, and it ignores OAuth scopes entirely.",[170,2511,2512,2515],{},[327,2513,2514],{},"Store tokens for persistent API access"," in General settings has to be turned on, because it is off by default and the platform reads the stored GitHub token from the Secret Vault.",[170,2517,2518,2519,2522],{},"The same page shows the ",[327,2520,2521],{},"Redirect URI (Callback URI)"," that GitHub must send users back to:",[492,2524,2529],{"className":2525,"code":2527,"language":2528},[2526],"language-text","https:\u002F\u002F\u003Clogto-endpoint>\u002Fcallback\u002F\u003Cconnector-id>\n","text",[174,2530,2527],{"__ignoreMap":497},[170,2532,2533],{},"The connector ID is generated when the connector is created, so this cannot be known in advance. Copy it and add it to your GitHub App's callback URL list. A GitHub App accepts several, so adding it after the fact disturbs nothing.",[2251,2535,2536,2537,2540],{},"Set ",[174,2538,2539],{},"logto.secrets.secretVaultKek"," before Logto first starts and never change it, because it encrypts every stored GitHub token.",[170,2542,2543,2544,2547,2548,2551,2552,2555,2556,2559],{},"Under ",[327,2545,2546],{},"Sign-in and account"," → ",[327,2549,2550],{},"Sign-up and sign-in",", add GitHub under ",[327,2553,2554],{},"Social sign-in"," and leave the identifier lists empty, so \"Continue with GitHub\" is the only way in. Leave ",[327,2557,2558],{},"Enable user registration"," on unless you want an invite-only installation.",[480,2561,2563],{"id":2562},"account-api","Account API",[170,2565,2566,2567,2570,2571,516],{},"On the ",[327,2568,2569],{},"Account center"," tab of that same page, switch the account API on. Every self-service field underneath it can stay ",[327,2572,2573],{},"Off",[186,2575,2577],{"id":2576},"the-github-app","The GitHub App",[170,2579,2580],{},"The App is what lets the platform read source repositories and hear about pushes. Create it under the account or organization that owns the repositories you want to deploy, and give it these settings in full.",[480,2582,2584],{"id":2583},"urls","URLs",[170,2586,2587],{},"Three of them, and they point in two different directions, which is the part people get wrong:",[194,2589,2590,2600],{},[197,2591,2592],{},[200,2593,2594,2597],{},[203,2595,2596],{},"Setting",[203,2598,2599],{},"Value",[210,2601,2602,2612,2623],{},[200,2603,2604,2607],{},[215,2605,2606],{},"Callback URL",[215,2608,2609],{},[174,2610,2611],{},"https:\u002F\u002F\u003Clogto-endpoint>\u002Fcallback\u002F\u003Cconnector-id>",[200,2613,2614,2617],{},[215,2615,2616],{},"Setup URL",[215,2618,2619,2622],{},[174,2620,2621],{},"https:\u002F\u002F\u003Cplatform-domain>\u002Fauth\u002Fgithub\u002Fsetup",", with \"Redirect on update\" enabled",[200,2624,2625,2628],{},[215,2626,2627],{},"Webhook URL",[215,2629,2630],{},[174,2631,2632],{},"https:\u002F\u002F\u003Cplatform-domain>\u002Fwebhooks\u002Fgithub",[170,2634,2635,2636,2639],{},"The callback goes to the ",[327,2637,2638],{},"identity provider",", not to the platform, because the identity provider runs the GitHub sign-in handshake. The other two go to the platform. You get the connector id when you create the GitHub connector, so fill the callback in afterwards.",[480,2641,2643],{"id":2642},"webhook","Webhook",[194,2645,2646,2654],{},[197,2647,2648],{},[200,2649,2650,2652],{},[203,2651,2596],{},[203,2653,2599],{},[210,2655,2656,2663,2673,2681],{},[200,2657,2658,2660],{},[215,2659,2643],{},[215,2661,2662],{},"Active",[200,2664,2665,2668],{},[215,2666,2667],{},"Content type",[215,2669,2670],{},[174,2671,2672],{},"application\u002Fjson",[200,2674,2675,2678],{},[215,2676,2677],{},"SSL verification",[215,2679,2680],{},"Enabled",[200,2682,2683,2686],{},[215,2684,2685],{},"Secret",[215,2687,2688,2689,2692],{},"The same value as ",[174,2690,2691],{},"GITHUB_WEBHOOK_SECRET"," in your platform secrets",[170,2694,2695,2696,2699],{},"Subscribe to exactly one event: ",[327,2697,2698],{},"Push",". That is what triggers an automatic deploy on a branch the platform is watching. Nothing else is read, so leave the rest unsubscribed.",[480,2701,2283],{"id":2237},[170,2703,2704],{},"The platform only ever reads your code, so every permission it needs is read-only:",[194,2706,2707,2720],{},[197,2708,2709],{},[200,2710,2711,2714,2717],{},[203,2712,2713],{},"Permission",[203,2715,2716],{},"Level",[203,2718,2719],{},"Used for",[210,2721,2722,2733,2743],{},[200,2723,2724,2727,2730],{},[215,2725,2726],{},"Repository → Contents",[215,2728,2729],{},"Read-only",[215,2731,2732],{},"Cloning source at build time, reading commits and branches",[200,2734,2735,2738,2740],{},[215,2736,2737],{},"Repository → Metadata",[215,2739,2729],{},[215,2741,2742],{},"Mandatory for any App, and how repositories are listed",[200,2744,2745,2748,2750],{},[215,2746,2747],{},"Account → Email addresses",[215,2749,2729],{},[215,2751,2752],{},"The user's email at sign-in",[480,2754,2756],{"id":2755},"private-key","Private key",[170,2758,2759,2760,2763,2764,2767,2768,2771,2772,516],{},"At the bottom of the App's General page, click ",[327,2761,2762],{},"Generate a private key",". GitHub downloads a ",[174,2765,2766],{},".pem"," and shows it to you once, so keep it somewhere safe or generate a fresh one later. Its contents go into ",[174,2769,2770],{},"githubPrivateKey"," in the platform secrets, and the conductor reads it back from the path in ",[174,2773,2774],{},"GITHUB_PRIVATE_KEY_PATH",[170,2776,2777,2778,2781,2782,2785,2786,2789],{},"Note the ",[327,2779,2780],{},"App ID",", the ",[327,2783,2784],{},"Client ID",", and a generated ",[327,2787,2788],{},"client secret"," from the same page while you are there. The client secret is also what the identity provider's GitHub connector needs.",[170,2791,2792],{},"Finally, install the App on the account or organization holding the repositories you want to deploy.",[186,2794,2796],{"id":2795},"the-platform-chart","The platform chart",[170,2798,2799],{},"With the identity provider configured, the platform chart has everything it needs. Its values file is where the identifiers you collected come together:",[194,2801,2802,2811],{},[197,2803,2804],{},[200,2805,2806,2808],{},[203,2807,2599],{},[203,2809,2810],{},"From",[210,2812,2813,2825,2834,2843,2852,2863,2881,2894,2904],{},[200,2814,2815,2820],{},[215,2816,2817],{},[174,2818,2819],{},"OIDC_ISSUER_URL",[215,2821,2822],{},[174,2823,2824],{},"https:\u002F\u002F\u003Clogto-endpoint>\u002Foidc",[200,2826,2827,2831],{},[215,2828,2829],{},[174,2830,2230],{},[215,2832,2833],{},"The API resource identifier",[200,2835,2836,2840],{},[215,2837,2838],{},[174,2839,2417],{},[215,2841,2842],{},"The Dashboard application",[200,2844,2845,2849],{},[215,2846,2847],{},[174,2848,2441],{},[215,2850,2851],{},"The CLI application",[200,2853,2854,2860],{},[215,2855,2856,251,2858],{},[174,2857,2457],{},[174,2859,2460],{},[215,2861,2862],{},"The M2M application",[200,2864,2865,2879],{},[215,2866,2867,2242,2870,2242,2873,2242,2876],{},[174,2868,2869],{},"GITHUB_APP_ID",[174,2871,2872],{},"GITHUB_APP_SLUG",[174,2874,2875],{},"GITHUB_CLIENT_ID",[174,2877,2878],{},"GITHUB_CLIENT_SECRET",[215,2880,2577],{},[200,2882,2883,2891],{},[215,2884,2885,2242,2888],{},[174,2886,2887],{},"WORKLOAD_DOMAIN",[174,2889,2890],{},"DATABASE_DOMAIN",[215,2892,2893],{},"Your workload domain",[200,2895,2896,2901],{},[215,2897,2898],{},[174,2899,2900],{},"IP_ADDRESS",[215,2902,2903],{},"The gateway address from the previous step",[200,2905,2906,2914],{},[215,2907,2908,251,2911],{},[174,2909,2910],{},"routes.enabled",[174,2912,2913],{},"routes.hostname",[215,2915,2916],{},"Your platform domain. These default to off and to our hostname, so set both",[170,2918,2919,2920,657],{},"Which comes together as ",[174,2921,2922],{},"values.yaml",[492,2924,2926],{"className":660,"code":2925,"language":662,"meta":497,"style":497},"registryPull:\n  host: \"10.96.100.100:5000\"   # the Zot clusterIP you pinned\n  username: reader\n\nroutes:\n  enabled: true\n  hostname: \u003Cplatform-domain>\n\nconductor:\n  registryHost: lucity-infra-zot.lucity-system.svc.cluster.local:5000\n  registryUsername: builder\n  buildkit:\n    enabled: true\n    storageSize: 20Gi\n  env:\n    OIDC_ISSUER_URL: https:\u002F\u002Fid.\u003Cplatform-domain>\u002Foidc\n    OIDC_AUDIENCE: https:\u002F\u002Fapi.\u003Cplatform-domain>\n    OIDC_CLIENT_ID: \u003Cdashboard-app-id>\n    OIDC_CLI_CLIENT_ID: \u003Ccli-app-id>\n    OIDC_CALLBACK_URL: https:\u002F\u002F\u003Cplatform-domain>\u002Fauth\u002Fcallback\n    DASHBOARD_URL: https:\u002F\u002F\u003Cplatform-domain>\u002Fapp\n\n    GITHUB_APP_ID: \"\u003Capp-id>\"\n    GITHUB_APP_SLUG: \u003Capp-slug>\n    GITHUB_CLIENT_ID: \u003Cclient-id>\n    GITHUB_PRIVATE_KEY_PATH: \u002Fsecrets\u002Fgithub\u002Fgithub-app.pem\n\n    LOGTO_ENDPOINT: http:\u002F\u002Flucity-infra-logto.lucity-system.svc.cluster.local:3001\n    VICTORIA_METRICS_URL: http:\u002F\u002Flucity-infra-victoria-metrics-single-server.lucity-system.svc.cluster.local:8428\n\n    INTERNAL_JWT_PRIVATE_KEY_PATH: \u002Fsecrets\u002Finternal-jwt\u002Fprivate.pem\n    INTERNAL_JWT_PUBLIC_KEY_PATH: \u002Fsecrets\u002Finternal-jwt\u002Fpublic.pem\n\n    REGISTRY_URL: lucity-infra-zot.lucity-system.svc.cluster.local:5000\n    REGISTRY_PUSH_URL: lucity-infra-zot.lucity-system.svc.cluster.local:5000\n    REGISTRY_PULL_URL: 10.96.100.100:5000\n    REGISTRY_AUTH_SECRET: lucity-registry-auth\n\n    WORKLOAD_DOMAIN: \u003Cworkload-domain>\n    DATABASE_DOMAIN: db.\u003Cworkload-domain>\n    IP_ADDRESS: \"\u003Cgateway-address>\"\n\n    OVH_PROJECT_ID: \"\u003Covh-project-id>\"\n    OVH_ENDPOINT: ovh-eu\n    OVH_REGION: GRA\n  envSecret:\n    SESSION_SECRET: SESSION_SECRET\n    GITHUB_CLIENT_SECRET: GITHUB_CLIENT_SECRET\n    WEBHOOK_SECRET: GITHUB_WEBHOOK_SECRET\n    LOGTO_M2M_APP_ID: LOGTO_M2M_APP_ID\n    LOGTO_M2M_APP_SECRET: LOGTO_M2M_APP_SECRET\n    OVH_APPLICATION_KEY: OVH_APPLICATION_KEY\n    OVH_APPLICATION_SECRET: OVH_APPLICATION_SECRET\n    OVH_CONSUMER_KEY: OVH_CONSUMER_KEY\n\ncashier:\n  enabled: false\n",[174,2927,2928,2935,2952,2962,2966,2973,2981,2990,2994,3001,3011,3021,3028,3037,3046,3053,3063,3073,3083,3093,3103,3113,3117,3131,3141,3151,3161,3165,3175,3185,3189,3199,3209,3213,3222,3231,3241,3251,3255,3265,3275,3289,3293,3307,3317,3327,3334,3344,3354,3364,3374,3384,3394,3404,3414,3418,3425],{"__ignoreMap":497},[501,2929,2930,2933],{"class":503,"line":504},[501,2931,2932],{"class":669},"registryPull",[501,2934,673],{"class":507},[501,2936,2937,2940,2942,2944,2947,2949],{"class":503,"line":550},[501,2938,2939],{"class":669},"  host",[501,2941,657],{"class":507},[501,2943,560],{"class":507},[501,2945,2946],{"class":563},"10.96.100.100:5000",[501,2948,748],{"class":507},[501,2950,2951],{"class":769},"   # the Zot clusterIP you pinned\n",[501,2953,2954,2957,2959],{"class":503,"line":638},[501,2955,2956],{"class":669},"  username",[501,2958,657],{"class":507},[501,2960,2961],{"class":563}," reader\n",[501,2963,2964],{"class":503,"line":696},[501,2965,931],{"emptyLinePlaceholder":930},[501,2967,2968,2971],{"class":503,"line":707},[501,2969,2970],{"class":669},"routes",[501,2972,673],{"class":507},[501,2974,2975,2977,2979],{"class":503,"line":718},[501,2976,1535],{"class":669},[501,2978,657],{"class":507},[501,2980,849],{"class":624},[501,2982,2983,2986,2988],{"class":503,"line":726},[501,2984,2985],{"class":669},"  hostname",[501,2987,657],{"class":507},[501,2989,1002],{"class":563},[501,2991,2992],{"class":503,"line":737},[501,2993,931],{"emptyLinePlaceholder":930},[501,2995,2996,2999],{"class":503,"line":758},[501,2997,2998],{"class":669},"conductor",[501,3000,673],{"class":507},[501,3002,3003,3006,3008],{"class":503,"line":766},[501,3004,3005],{"class":669},"  registryHost",[501,3007,657],{"class":507},[501,3009,3010],{"class":563}," lucity-infra-zot.lucity-system.svc.cluster.local:5000\n",[501,3012,3013,3016,3018],{"class":503,"line":773},[501,3014,3015],{"class":669},"  registryUsername",[501,3017,657],{"class":507},[501,3019,3020],{"class":563}," builder\n",[501,3022,3023,3026],{"class":503,"line":782},[501,3024,3025],{"class":669},"  buildkit",[501,3027,673],{"class":507},[501,3029,3030,3033,3035],{"class":503,"line":800},[501,3031,3032],{"class":669},"    enabled",[501,3034,657],{"class":507},[501,3036,849],{"class":624},[501,3038,3039,3042,3044],{"class":503,"line":807},[501,3040,3041],{"class":669},"    storageSize",[501,3043,657],{"class":507},[501,3045,875],{"class":563},[501,3047,3048,3051],{"class":503,"line":951},[501,3049,3050],{"class":669},"  env",[501,3052,673],{"class":507},[501,3054,3055,3058,3060],{"class":503,"line":959},[501,3056,3057],{"class":669},"    OIDC_ISSUER_URL",[501,3059,657],{"class":507},[501,3061,3062],{"class":563}," https:\u002F\u002Fid.\u003Cplatform-domain>\u002Foidc\n",[501,3064,3065,3068,3070],{"class":503,"line":972},[501,3066,3067],{"class":669},"    OIDC_AUDIENCE",[501,3069,657],{"class":507},[501,3071,3072],{"class":563}," https:\u002F\u002Fapi.\u003Cplatform-domain>\n",[501,3074,3075,3078,3080],{"class":503,"line":983},[501,3076,3077],{"class":669},"    OIDC_CLIENT_ID",[501,3079,657],{"class":507},[501,3081,3082],{"class":563}," \u003Cdashboard-app-id>\n",[501,3084,3085,3088,3090],{"class":503,"line":994},[501,3086,3087],{"class":669},"    OIDC_CLI_CLIENT_ID",[501,3089,657],{"class":507},[501,3091,3092],{"class":563}," \u003Ccli-app-id>\n",[501,3094,3095,3098,3100],{"class":503,"line":1005},[501,3096,3097],{"class":669},"    OIDC_CALLBACK_URL",[501,3099,657],{"class":507},[501,3101,3102],{"class":563}," https:\u002F\u002F\u003Cplatform-domain>\u002Fauth\u002Fcallback\n",[501,3104,3105,3108,3110],{"class":503,"line":1017},[501,3106,3107],{"class":669},"    DASHBOARD_URL",[501,3109,657],{"class":507},[501,3111,3112],{"class":563}," https:\u002F\u002F\u003Cplatform-domain>\u002Fapp\n",[501,3114,3115],{"class":503,"line":1027},[501,3116,931],{"emptyLinePlaceholder":930},[501,3118,3119,3122,3124,3126,3129],{"class":503,"line":1037},[501,3120,3121],{"class":669},"    GITHUB_APP_ID",[501,3123,657],{"class":507},[501,3125,560],{"class":507},[501,3127,3128],{"class":563},"\u003Capp-id>",[501,3130,567],{"class":507},[501,3132,3133,3136,3138],{"class":503,"line":1046},[501,3134,3135],{"class":669},"    GITHUB_APP_SLUG",[501,3137,657],{"class":507},[501,3139,3140],{"class":563}," \u003Capp-slug>\n",[501,3142,3143,3146,3148],{"class":503,"line":1054},[501,3144,3145],{"class":669},"    GITHUB_CLIENT_ID",[501,3147,657],{"class":507},[501,3149,3150],{"class":563}," \u003Cclient-id>\n",[501,3152,3153,3156,3158],{"class":503,"line":1065},[501,3154,3155],{"class":669},"    GITHUB_PRIVATE_KEY_PATH",[501,3157,657],{"class":507},[501,3159,3160],{"class":563}," \u002Fsecrets\u002Fgithub\u002Fgithub-app.pem\n",[501,3162,3163],{"class":503,"line":1073},[501,3164,931],{"emptyLinePlaceholder":930},[501,3166,3167,3170,3172],{"class":503,"line":1086},[501,3168,3169],{"class":669},"    LOGTO_ENDPOINT",[501,3171,657],{"class":507},[501,3173,3174],{"class":563}," http:\u002F\u002Flucity-infra-logto.lucity-system.svc.cluster.local:3001\n",[501,3176,3177,3180,3182],{"class":503,"line":1098},[501,3178,3179],{"class":669},"    VICTORIA_METRICS_URL",[501,3181,657],{"class":507},[501,3183,3184],{"class":563}," http:\u002F\u002Flucity-infra-victoria-metrics-single-server.lucity-system.svc.cluster.local:8428\n",[501,3186,3187],{"class":503,"line":1107},[501,3188,931],{"emptyLinePlaceholder":930},[501,3190,3191,3194,3196],{"class":503,"line":1116},[501,3192,3193],{"class":669},"    INTERNAL_JWT_PRIVATE_KEY_PATH",[501,3195,657],{"class":507},[501,3197,3198],{"class":563}," \u002Fsecrets\u002Finternal-jwt\u002Fprivate.pem\n",[501,3200,3201,3204,3206],{"class":503,"line":1130},[501,3202,3203],{"class":669},"    INTERNAL_JWT_PUBLIC_KEY_PATH",[501,3205,657],{"class":507},[501,3207,3208],{"class":563}," \u002Fsecrets\u002Finternal-jwt\u002Fpublic.pem\n",[501,3210,3211],{"class":503,"line":1142},[501,3212,931],{"emptyLinePlaceholder":930},[501,3214,3215,3218,3220],{"class":503,"line":1151},[501,3216,3217],{"class":669},"    REGISTRY_URL",[501,3219,657],{"class":507},[501,3221,3010],{"class":563},[501,3223,3224,3227,3229],{"class":503,"line":1160},[501,3225,3226],{"class":669},"    REGISTRY_PUSH_URL",[501,3228,657],{"class":507},[501,3230,3010],{"class":563},[501,3232,3233,3236,3238],{"class":503,"line":1173},[501,3234,3235],{"class":669},"    REGISTRY_PULL_URL",[501,3237,657],{"class":507},[501,3239,3240],{"class":563}," 10.96.100.100:5000\n",[501,3242,3243,3246,3248],{"class":503,"line":1180},[501,3244,3245],{"class":669},"    REGISTRY_AUTH_SECRET",[501,3247,657],{"class":507},[501,3249,3250],{"class":563}," lucity-registry-auth\n",[501,3252,3253],{"class":503,"line":1189},[501,3254,931],{"emptyLinePlaceholder":930},[501,3256,3257,3260,3262],{"class":503,"line":1196},[501,3258,3259],{"class":669},"    WORKLOAD_DOMAIN",[501,3261,657],{"class":507},[501,3263,3264],{"class":563}," \u003Cworkload-domain>\n",[501,3266,3267,3270,3272],{"class":503,"line":1208},[501,3268,3269],{"class":669},"    DATABASE_DOMAIN",[501,3271,657],{"class":507},[501,3273,3274],{"class":563}," db.\u003Cworkload-domain>\n",[501,3276,3277,3280,3282,3284,3287],{"class":503,"line":1220},[501,3278,3279],{"class":669},"    IP_ADDRESS",[501,3281,657],{"class":507},[501,3283,560],{"class":507},[501,3285,3286],{"class":563},"\u003Cgateway-address>",[501,3288,567],{"class":507},[501,3290,3291],{"class":503,"line":1229},[501,3292,931],{"emptyLinePlaceholder":930},[501,3294,3295,3298,3300,3302,3305],{"class":503,"line":1238},[501,3296,3297],{"class":669},"    OVH_PROJECT_ID",[501,3299,657],{"class":507},[501,3301,560],{"class":507},[501,3303,3304],{"class":563},"\u003Covh-project-id>",[501,3306,567],{"class":507},[501,3308,3309,3312,3314],{"class":503,"line":1248},[501,3310,3311],{"class":669},"    OVH_ENDPOINT",[501,3313,657],{"class":507},[501,3315,3316],{"class":563}," ovh-eu\n",[501,3318,3319,3322,3324],{"class":503,"line":1260},[501,3320,3321],{"class":669},"    OVH_REGION",[501,3323,657],{"class":507},[501,3325,3326],{"class":563}," GRA\n",[501,3328,3329,3332],{"class":503,"line":1269},[501,3330,3331],{"class":669},"  envSecret",[501,3333,673],{"class":507},[501,3335,3336,3339,3341],{"class":503,"line":1278},[501,3337,3338],{"class":669},"    SESSION_SECRET",[501,3340,657],{"class":507},[501,3342,3343],{"class":563}," SESSION_SECRET\n",[501,3345,3346,3349,3351],{"class":503,"line":1287},[501,3347,3348],{"class":669},"    GITHUB_CLIENT_SECRET",[501,3350,657],{"class":507},[501,3352,3353],{"class":563}," GITHUB_CLIENT_SECRET\n",[501,3355,3356,3359,3361],{"class":503,"line":1294},[501,3357,3358],{"class":669},"    WEBHOOK_SECRET",[501,3360,657],{"class":507},[501,3362,3363],{"class":563}," GITHUB_WEBHOOK_SECRET\n",[501,3365,3366,3369,3371],{"class":503,"line":1303},[501,3367,3368],{"class":669},"    LOGTO_M2M_APP_ID",[501,3370,657],{"class":507},[501,3372,3373],{"class":563}," LOGTO_M2M_APP_ID\n",[501,3375,3376,3379,3381],{"class":503,"line":1310},[501,3377,3378],{"class":669},"    LOGTO_M2M_APP_SECRET",[501,3380,657],{"class":507},[501,3382,3383],{"class":563}," LOGTO_M2M_APP_SECRET\n",[501,3385,3386,3389,3391],{"class":503,"line":1322},[501,3387,3388],{"class":669},"    OVH_APPLICATION_KEY",[501,3390,657],{"class":507},[501,3392,3393],{"class":563}," OVH_APPLICATION_KEY\n",[501,3395,3396,3399,3401],{"class":503,"line":1327},[501,3397,3398],{"class":669},"    OVH_APPLICATION_SECRET",[501,3400,657],{"class":507},[501,3402,3403],{"class":563}," OVH_APPLICATION_SECRET\n",[501,3405,3406,3409,3411],{"class":503,"line":1335},[501,3407,3408],{"class":669},"    OVH_CONSUMER_KEY",[501,3410,657],{"class":507},[501,3412,3413],{"class":563}," OVH_CONSUMER_KEY\n",[501,3415,3416],{"class":503,"line":1346},[501,3417,931],{"emptyLinePlaceholder":930},[501,3419,3420,3423],{"class":503,"line":1356},[501,3421,3422],{"class":669},"cashier",[501,3424,673],{"class":507},[501,3426,3427,3429,3431],{"class":503,"line":1377},[501,3428,1535],{"class":669},[501,3430,657],{"class":507},[501,3432,3433],{"class":624}," false\n",[170,3435,3436,3439,3440,3443],{},[174,3437,3438],{},"envSecret"," maps an environment variable to a key in ",[174,3441,3442],{},"secrets.yaml",", which holds everything sensitive:",[492,3445,3447],{"className":660,"code":3446,"language":662,"meta":497,"style":497},"secrets:\n  SESSION_SECRET: \"\u003Copenssl rand -hex 32>\"\n  GITHUB_WEBHOOK_SECRET: \"\u003Csame value as the GitHub App webhook secret>\"\n  GITHUB_CLIENT_SECRET: \"\u003Cfrom the GitHub App>\"\n  REGISTRY_PASSWORD: \"\u003Cthe builder password>\"\n  LOGTO_M2M_APP_ID: \"\u003Cthe M2M application's App ID>\"\n  LOGTO_M2M_APP_SECRET: \"\u003Cthe M2M application's App Secret>\"\n  OVH_APPLICATION_KEY: \"\"\n  OVH_APPLICATION_SECRET: \"\"\n  OVH_CONSUMER_KEY: \"\"\n\ngithubPrivateKey: |\n  -----BEGIN RSA PRIVATE KEY-----\n  ...the .pem you downloaded from the GitHub App\n  -----END RSA PRIVATE KEY-----\n\nregistryPull:\n  password: \"\u003Cthe reader password>\"\n\ninternalJWT:\n  privateKey: |\n    -----BEGIN EC PRIVATE KEY-----\n  publicKey: |\n    -----BEGIN PUBLIC KEY-----\n",[174,3448,3449,3456,3470,3484,3498,3512,3526,3540,3550,3559,3568,3572,3580,3585,3590,3595,3599,3605,3619,3623,3630,3639,3644,3653],{"__ignoreMap":497},[501,3450,3451,3454],{"class":503,"line":504},[501,3452,3453],{"class":669},"secrets",[501,3455,673],{"class":507},[501,3457,3458,3461,3463,3465,3468],{"class":503,"line":550},[501,3459,3460],{"class":669},"  SESSION_SECRET",[501,3462,657],{"class":507},[501,3464,560],{"class":507},[501,3466,3467],{"class":563},"\u003Copenssl rand -hex 32>",[501,3469,567],{"class":507},[501,3471,3472,3475,3477,3479,3482],{"class":503,"line":638},[501,3473,3474],{"class":669},"  GITHUB_WEBHOOK_SECRET",[501,3476,657],{"class":507},[501,3478,560],{"class":507},[501,3480,3481],{"class":563},"\u003Csame value as the GitHub App webhook secret>",[501,3483,567],{"class":507},[501,3485,3486,3489,3491,3493,3496],{"class":503,"line":696},[501,3487,3488],{"class":669},"  GITHUB_CLIENT_SECRET",[501,3490,657],{"class":507},[501,3492,560],{"class":507},[501,3494,3495],{"class":563},"\u003Cfrom the GitHub App>",[501,3497,567],{"class":507},[501,3499,3500,3503,3505,3507,3510],{"class":503,"line":707},[501,3501,3502],{"class":669},"  REGISTRY_PASSWORD",[501,3504,657],{"class":507},[501,3506,560],{"class":507},[501,3508,3509],{"class":563},"\u003Cthe builder password>",[501,3511,567],{"class":507},[501,3513,3514,3517,3519,3521,3524],{"class":503,"line":718},[501,3515,3516],{"class":669},"  LOGTO_M2M_APP_ID",[501,3518,657],{"class":507},[501,3520,560],{"class":507},[501,3522,3523],{"class":563},"\u003Cthe M2M application's App ID>",[501,3525,567],{"class":507},[501,3527,3528,3531,3533,3535,3538],{"class":503,"line":726},[501,3529,3530],{"class":669},"  LOGTO_M2M_APP_SECRET",[501,3532,657],{"class":507},[501,3534,560],{"class":507},[501,3536,3537],{"class":563},"\u003Cthe M2M application's App Secret>",[501,3539,567],{"class":507},[501,3541,3542,3545,3547],{"class":503,"line":737},[501,3543,3544],{"class":669},"  OVH_APPLICATION_KEY",[501,3546,657],{"class":507},[501,3548,3549],{"class":507}," \"\"\n",[501,3551,3552,3555,3557],{"class":503,"line":758},[501,3553,3554],{"class":669},"  OVH_APPLICATION_SECRET",[501,3556,657],{"class":507},[501,3558,3549],{"class":507},[501,3560,3561,3564,3566],{"class":503,"line":766},[501,3562,3563],{"class":669},"  OVH_CONSUMER_KEY",[501,3565,657],{"class":507},[501,3567,3549],{"class":507},[501,3569,3570],{"class":503,"line":773},[501,3571,931],{"emptyLinePlaceholder":930},[501,3573,3574,3576,3578],{"class":503,"line":782},[501,3575,2770],{"class":669},[501,3577,657],{"class":507},[501,3579,1820],{"class":1819},[501,3581,3582],{"class":503,"line":800},[501,3583,3584],{"class":563},"  -----BEGIN RSA PRIVATE KEY-----\n",[501,3586,3587],{"class":503,"line":807},[501,3588,3589],{"class":563},"  ...the .pem you downloaded from the GitHub App\n",[501,3591,3592],{"class":503,"line":951},[501,3593,3594],{"class":563},"  -----END RSA PRIVATE KEY-----\n",[501,3596,3597],{"class":503,"line":959},[501,3598,931],{"emptyLinePlaceholder":930},[501,3600,3601,3603],{"class":503,"line":972},[501,3602,2932],{"class":669},[501,3604,673],{"class":507},[501,3606,3607,3610,3612,3614,3617],{"class":503,"line":983},[501,3608,3609],{"class":669},"  password",[501,3611,657],{"class":507},[501,3613,560],{"class":507},[501,3615,3616],{"class":563},"\u003Cthe reader password>",[501,3618,567],{"class":507},[501,3620,3621],{"class":503,"line":994},[501,3622,931],{"emptyLinePlaceholder":930},[501,3624,3625,3628],{"class":503,"line":1005},[501,3626,3627],{"class":669},"internalJWT",[501,3629,673],{"class":507},[501,3631,3632,3635,3637],{"class":503,"line":1017},[501,3633,3634],{"class":669},"  privateKey",[501,3636,657],{"class":507},[501,3638,1820],{"class":1819},[501,3640,3641],{"class":503,"line":1027},[501,3642,3643],{"class":563},"    -----BEGIN EC PRIVATE KEY-----\n",[501,3645,3646,3649,3651],{"class":503,"line":1037},[501,3647,3648],{"class":669},"  publicKey",[501,3650,657],{"class":507},[501,3652,1820],{"class":1819},[501,3654,3655],{"class":503,"line":1046},[501,3656,3657],{"class":563},"    -----BEGIN PUBLIC KEY-----\n",[170,3659,3660],{},"The internal keypair signs the tokens the control plane issues to its own build and deploy jobs. Generate it with:",[492,3662,3664],{"className":600,"code":3663,"language":602,"meta":497,"style":497},"openssl ecparam -name prime256v1 -genkey -noout -out internal-jwt-private.pem\nopenssl ec -in internal-jwt-private.pem -pubout -out internal-jwt-public.pem\n",[174,3665,3666,3691],{"__ignoreMap":497},[501,3667,3668,3670,3673,3676,3679,3682,3685,3688],{"class":503,"line":504},[501,3669,1743],{"class":511},[501,3671,3672],{"class":563}," ecparam",[501,3674,3675],{"class":624}," -name",[501,3677,3678],{"class":563}," prime256v1",[501,3680,3681],{"class":624}," -genkey",[501,3683,3684],{"class":624}," -noout",[501,3686,3687],{"class":624}," -out",[501,3689,3690],{"class":563}," internal-jwt-private.pem\n",[501,3692,3693,3695,3698,3701,3704,3707,3709],{"class":503,"line":550},[501,3694,1743],{"class":511},[501,3696,3697],{"class":563}," ec",[501,3699,3700],{"class":624}," -in",[501,3702,3703],{"class":563}," internal-jwt-private.pem",[501,3705,3706],{"class":624}," -pubout",[501,3708,3687],{"class":624},[501,3710,3711],{"class":563}," internal-jwt-public.pem\n",[170,3713,3714,3715,2242,3718,2242,3721,251,3724,3727],{},"Object storage credentials are also required, and the conductor will not start without them. Today the only implemented backend is OVH, so you need ",[174,3716,3717],{},"OVH_APPLICATION_KEY",[174,3719,3720],{},"OVH_APPLICATION_SECRET",[174,3722,3723],{},"OVH_CONSUMER_KEY",[174,3725,3726],{},"OVH_PROJECT_ID",". Scope them to the one project the platform should use, because it creates and deletes buckets and credentials there on its own.",[170,3729,3730],{},"Then install, the same way as the infra chart and with the same caution about versions:",[492,3732,3734],{"className":600,"code":3733,"language":602,"meta":497,"style":497},"helm upgrade --install lucity oci:\u002F\u002Fghcr.io\u002Fzeitlos\u002Flucity\u002Fcharts\u002Flucity \\\n  --version \u003Cchart-version> -n lucity-system \\\n  -f values.yaml -f secrets.yaml\n",[174,3735,3736,3752,3770],{"__ignoreMap":497},[501,3737,3738,3740,3742,3744,3747,3750],{"class":503,"line":504},[501,3739,1875],{"class":511},[501,3741,1878],{"class":563},[501,3743,1881],{"class":624},[501,3745,3746],{"class":563}," lucity",[501,3748,3749],{"class":563}," oci:\u002F\u002Fghcr.io\u002Fzeitlos\u002Flucity\u002Fcharts\u002Flucity",[501,3751,625],{"class":624},[501,3753,3754,3756,3758,3760,3762,3764,3766,3768],{"class":503,"line":550},[501,3755,1894],{"class":624},[501,3757,1897],{"class":669},[501,3759,1900],{"class":563},[501,3761,1903],{"class":515},[501,3763,1906],{"class":669},[501,3765,1909],{"class":624},[501,3767,1912],{"class":563},[501,3769,625],{"class":624},[501,3771,3772,3774,3777,3779],{"class":503,"line":638},[501,3773,1922],{"class":624},[501,3775,3776],{"class":563}," values.yaml",[501,3778,1928],{"class":624},[501,3780,3781],{"class":563}," secrets.yaml\n",[170,3783,3784],{},"A healthy conductor says so plainly in its logs, and the line worth looking for is the one proving it reached the identity provider and found your roles:",[492,3786,3789],{"className":3787,"code":3788,"language":2528},[2526],"INFO logto org roles cached admin=... member=...\n",[174,3790,3788],{"__ignoreMap":497},[186,3792,3794],{"id":3793},"verifying-the-install","Verifying the install",[170,3796,3797],{},"A few checks, fastest first:",[492,3799,3801],{"className":600,"code":3800,"language":602,"meta":497,"style":497},"kubectl -n lucity-system get pods\nkubectl -n lucity-system get certificate\nkubectl -n lucity-system get gateway lucity-gateway \\\n  -o jsonpath='{range .status.listeners[*]}{.name}{\": \"}{range .conditions[*]}{.type}={.status} {end}{\"\\n\"}{end}'\n",[174,3802,3803,3815,3827,3844],{"__ignoreMap":497},[501,3804,3805,3807,3809,3811,3813],{"class":503,"line":504},[501,3806,609],{"class":511},[501,3808,1909],{"class":624},[501,3810,1912],{"class":563},[501,3812,2052],{"class":563},[501,3814,2055],{"class":563},[501,3816,3817,3819,3821,3823,3825],{"class":503,"line":550},[501,3818,609],{"class":511},[501,3820,1909],{"class":624},[501,3822,1912],{"class":563},[501,3824,2052],{"class":563},[501,3826,2081],{"class":563},[501,3828,3829,3831,3833,3835,3837,3839,3842],{"class":503,"line":638},[501,3830,609],{"class":511},[501,3832,1909],{"class":624},[501,3834,1912],{"class":563},[501,3836,2052],{"class":563},[501,3838,2090],{"class":563},[501,3840,3841],{"class":563}," lucity-gateway",[501,3843,625],{"class":624},[501,3845,3846,3849,3852,3854,3857],{"class":503,"line":696},[501,3847,3848],{"class":624},"  -o",[501,3850,3851],{"class":563}," jsonpath=",[501,3853,644],{"class":507},[501,3855,3856],{"class":563},"{range .status.listeners[*]}{.name}{\": \"}{range .conditions[*]}{.type}={.status} {end}{\"\\n\"}{end}",[501,3858,650],{"class":507},[170,3860,3861,3862,3865,3866,3869],{},"Every certificate ",[174,3863,3864],{},"True",", and every listener ",[174,3867,3868],{},"Programmed=True",". A listener stuck unprogrammed is nearly always waiting on its certificate.",[170,3871,3872],{},"Then the platform itself, which should answer on each path the route table sends somewhere different:",[492,3874,3876],{"className":600,"code":3875,"language":602,"meta":497,"style":497},"curl -o \u002Fdev\u002Fnull -w '%{http_code}\\n' https:\u002F\u002F\u003Cplatform-domain>\u002F\ncurl -o \u002Fdev\u002Fnull -w '%{http_code}\\n' https:\u002F\u002F\u003Cplatform-domain>\u002Fapp\u002F\ncurl https:\u002F\u002F\u003Cplatform-domain>\u002Fauth\u002Fconfig\n",[174,3877,3878,3916,3945],{"__ignoreMap":497},[501,3879,3880,3883,3886,3889,3892,3895,3898,3900,3903,3906,3909,3911,3913],{"class":503,"line":504},[501,3881,3882],{"class":511},"curl",[501,3884,3885],{"class":624}," -o",[501,3887,3888],{"class":563}," \u002Fdev\u002Fnull",[501,3890,3891],{"class":624}," -w",[501,3893,3894],{"class":507}," '",[501,3896,3897],{"class":563},"%{http_code}\\n",[501,3899,644],{"class":507},[501,3901,3902],{"class":563}," https:\u002F\u002F",[501,3904,3905],{"class":669},"\u003C",[501,3907,3908],{"class":563},"platform-domai",[501,3910,1903],{"class":515},[501,3912,1906],{"class":669},[501,3914,3915],{"class":563},"\u002F\n",[501,3917,3918,3920,3922,3924,3926,3928,3930,3932,3934,3936,3938,3940,3942],{"class":503,"line":550},[501,3919,3882],{"class":511},[501,3921,3885],{"class":624},[501,3923,3888],{"class":563},[501,3925,3891],{"class":624},[501,3927,3894],{"class":507},[501,3929,3897],{"class":563},[501,3931,644],{"class":507},[501,3933,3902],{"class":563},[501,3935,3905],{"class":669},[501,3937,3908],{"class":563},[501,3939,1903],{"class":515},[501,3941,1906],{"class":669},[501,3943,3944],{"class":563},"\u002Fapp\u002F\n",[501,3946,3947,3949,3951,3953,3955,3957,3959],{"class":503,"line":638},[501,3948,3882],{"class":511},[501,3950,3902],{"class":563},[501,3952,3905],{"class":669},[501,3954,3908],{"class":563},[501,3956,1903],{"class":515},[501,3958,1906],{"class":669},[501,3960,3961],{"class":563},"\u002Fauth\u002Fconfig\n",[170,3963,3964,3967],{},[174,3965,3966],{},"\u002Fauth\u002Fconfig"," is the most useful of the three, because it echoes back the issuer, audience, and CLI client ID the conductor actually loaded.",[170,3969,3970],{},"Finally, confirm nodes can pull from the registry by asking for an image that does not exist:",[492,3972,3974],{"className":600,"code":3973,"language":602,"meta":497,"style":497},"kubectl run pulltest --image=\u003Cregistry-service-ip>:5000\u002Fdoes-not-exist:test --restart=Never --command -- true\nkubectl describe pod pulltest\n",[174,3975,3976,4010],{"__ignoreMap":497},[501,3977,3978,3980,3983,3986,3989,3991,3994,3996,3999,4002,4005,4008],{"class":503,"line":504},[501,3979,609],{"class":511},[501,3981,3982],{"class":563}," run",[501,3984,3985],{"class":563}," pulltest",[501,3987,3988],{"class":624}," --image=",[501,3990,3905],{"class":669},[501,3992,3993],{"class":624},"registry-service-ip",[501,3995,1906],{"class":669},[501,3997,3998],{"class":624},":5000\u002Fdoes-not-exist:test",[501,4000,4001],{"class":624}," --restart=Never",[501,4003,4004],{"class":624}," --command",[501,4006,4007],{"class":624}," --",[501,4009,849],{"class":624},[501,4011,4012,4014,4017,4020],{"class":503,"line":550},[501,4013,609],{"class":511},[501,4015,4016],{"class":563}," describe",[501,4018,4019],{"class":563}," pod",[501,4021,4022],{"class":563}," pulltest\n",[170,4024,4025],{},"An auth or not-found error is success, because containerd reached the registry and got an answer. Delete the pod afterwards.",[170,4027,4028,4029,4032],{},"Sign in at ",[174,4030,4031],{},"https:\u002F\u002F\u003Cplatform-domain>\u002Fapp\u002F",", and the first account to authenticate gets its own workspace.",[186,4034,4036],{"id":4035},"reference-profiles","Reference profiles",[170,4038,4039,4040,4043],{},"The repository carries the complete values files for the clusters we run, under ",[174,4041,4042],{},"deployments\u002F",". They are the fastest way to see how the pieces fit together, and the closest thing to a known-good starting point:",[4045,4046,4047,4054,4060],"ul",{},[4048,4049,4050,4053],"li",{},[174,4051,4052],{},"deployments\u002Flucity-prod\u002F"," is the production profile, with billing, backups, analytics, alerting, and database exposure all switched on",[4048,4055,4056,4059],{},[174,4057,4058],{},"deployments\u002Flucity-dev\u002F"," is a smaller three-node profile with those switched off, which is roughly the minimum that still runs real workloads",[4048,4061,4062,4065],{},[174,4063,4064],{},"deployments\u002Fminikube\u002F"," is the local development profile",[170,4067,4068],{},"Copy whichever is closest, then work through the values that name a domain, a storage class, or a credential.",[4070,4071,4072],"style",{},"html pre.shiki code .sX5TD, html code.shiki .sX5TD{--shiki-light:#FABD2F;--shiki-default:#FABD2F;--shiki-dark:#FABD2F}html pre.shiki code .sQLXx, html code.shiki .sQLXx{--shiki-light:#B8BB26;--shiki-default:#B8BB26;--shiki-dark:#B8BB26}html pre.shiki code .s1Tsi, html code.shiki .s1Tsi{--shiki-light:#D3869B;--shiki-default:#D3869B;--shiki-dark:#D3869B}html pre.shiki code .si-ur, html code.shiki .si-ur{--shiki-light:#A89984;--shiki-default:#A89984;--shiki-dark:#A89984}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 .s3drp, html code.shiki .s3drp{--shiki-light:#8EC07C;--shiki-default:#8EC07C;--shiki-dark:#8EC07C}html pre.shiki code .sIIdi, html code.shiki .sIIdi{--shiki-light:#928374;--shiki-light-font-style:italic;--shiki-default:#928374;--shiki-default-font-style:italic;--shiki-dark:#928374;--shiki-dark-font-style:italic}html pre.shiki code .sJHtc, html code.shiki .sJHtc{--shiki-light:#FB4934;--shiki-default:#FB4934;--shiki-dark:#FB4934}html pre.shiki code .spvMa, html code.shiki .spvMa{--shiki-light:#EBDBB2;--shiki-default:#EBDBB2;--shiki-dark:#EBDBB2}html pre.shiki code .sIi04, html code.shiki .sIi04{--shiki-light:#83A598;--shiki-default:#83A598;--shiki-dark:#83A598}",{"title":497,"searchDepth":550,"depth":550,"links":4074},[4075,4076,4077,4081,4082,4083,4090,4096,4097,4098],{"id":188,"depth":550,"text":189},{"id":337,"depth":550,"text":338},{"id":474,"depth":550,"text":475,"children":4078},[4079,4080],{"id":482,"depth":638,"text":483},{"id":581,"depth":638,"text":582},{"id":819,"depth":550,"text":820},{"id":2102,"depth":550,"text":2103},{"id":2177,"depth":550,"text":2178,"children":4084},[4085,4086,4087,4088,4089],{"id":2219,"depth":638,"text":2220},{"id":2260,"depth":638,"text":2261},{"id":2374,"depth":638,"text":2375},{"id":2479,"depth":638,"text":2480},{"id":2562,"depth":638,"text":2563},{"id":2576,"depth":550,"text":2577,"children":4091},[4092,4093,4094,4095],{"id":2583,"depth":638,"text":2584},{"id":2642,"depth":638,"text":2643},{"id":2237,"depth":638,"text":2283},{"id":2755,"depth":638,"text":2756},{"id":2795,"depth":550,"text":2796},{"id":3793,"depth":550,"text":3794},{"id":4035,"depth":550,"text":4036},"Run the whole platform on a Kubernetes cluster you own, using the same two Helm charts we run ourselves.","md",null,{},{"title":127,"description":4099},"KJLf_4rn3SKKGl9BSPL0SmwJdjfZmINtionm5mTzpWM",[4106,4101],{"title":123,"path":124,"stem":125,"description":4107,"children":-1},"Plans, resource usage, the free trial, and what happens when a payment fails.",[4109,4112,4114,4117,4120,4123,4126,4129,4132,4135,4138,4141,4144,4146,4148,4150,4152,4154,4156,4158,4160,4162,4164,4166,4168,4170,4172,4174,4176,4178,4180,4181,4182,4185,4189,4193],{"path":6,"title":4110,"description":4111},"Deploy your first service","Deploy an app to Lucity, put it on a public domain, and connect a PostgreSQL database and an object storage bucket in about fifteen minutes.",{"path":4113,"title":9,"description":4101},"\u002Fdocs\u002Fguides\u002F.navigation",{"path":16,"title":4115,"description":4116},"Deploy a Next.js app","Deploy a Next.js app on Lucity, with a database, environment variables that behave, and no Dockerfile.",{"path":20,"title":4118,"description":4119},"Deploy a Nuxt app","Deploy a Nuxt app on Lucity. Nitro output, runtime config, and a database, with no preset to pick.",{"path":24,"title":4121,"description":4122},"Deploy a SvelteKit app","Deploy a SvelteKit app on Lucity. One adapter change stands between a fresh project and a running service.",{"path":28,"title":4124,"description":4125},"Deploy an Astro site","Deploy an Astro site on Lucity, static by default and server rendered when you add an adapter.",{"path":32,"title":4127,"description":4128},"Deploy an Express API","Deploy an Express API on Lucity. Three requirements, none of them platform-specific.",{"path":36,"title":4130,"description":4131},"Deploy a FastAPI service","Deploy a FastAPI service on Lucity. Detection is picky about layout, and the fix is one setting.",{"path":40,"title":4133,"description":4134},"Deploy a Django project","Deploy a Django project on Lucity, with migrations that run themselves and static files that do not.",{"path":44,"title":4136,"description":4137},"Deploy a Laravel application","Deploy a Laravel application on Lucity, with migrations, caches and a queue worker, and no server to configure.",{"path":48,"title":4139,"description":4140},"Deploy a Go service","Deploy a Go service on Lucity. Compiled in the build, shipped as a binary, nothing else in the image.",{"path":10,"title":4142,"description":4143},"Deploy your framework","Deploy guides for the frameworks people actually ship: Next.js, Nuxt, SvelteKit, Astro, Express, FastAPI, Django, Laravel, and Go.",{"path":52,"title":51,"description":4145},"Services are the deployable units of a project: what they are, how they are configured, and how they scale.",{"path":56,"title":55,"description":4147},"How a deployment goes live on Lucity, and the ways to trigger one.",{"path":60,"title":59,"description":4149},"How Lucity turns your source code into a container image.",{"path":64,"title":63,"description":4151},"Configuration for your services: service variables, shared variables, and dynamic variables.",{"path":68,"title":67,"description":4153},"Put a service on the internet. Use a platform hostname for quick tests, or a domain you own.",{"path":72,"title":71,"description":4155},"Managed PostgreSQL databases, replicated and backed up, connected to your services without copying credentials.",{"path":76,"title":75,"description":4157},"S3-compatible buckets for uploads, exports and anything else your service needs to keep.",{"path":80,"title":79,"description":4159},"Persistent disks for services that need to keep state on the filesystem.",{"path":84,"title":83,"description":4161},"A Redis®-compatible key-value store for caching, sessions and queues.",{"path":88,"title":87,"description":4163},"Workspaces represent your organization in Lucity, with their own members, isolation and billing.",{"path":92,"title":91,"description":4165},"Projects group services, databases and the environments they run in.",{"path":96,"title":95,"description":4167},"Development, staging, production: isolated copies of the same application.",{"path":100,"title":99,"description":4169},"Download a project as a Helm chart and run it on a Kubernetes cluster of your own.",{"path":104,"title":103,"description":4171},"The GraphQL API behind the dashboard, with an interactive playground.",{"path":108,"title":107,"description":4173},"Deploy and manage your services from a terminal or a CI pipeline.",{"path":112,"title":111,"description":4175},"Deploy and manage your services from Claude Code, Codex, Cursor or any other MCP client.",{"path":116,"title":115,"description":4177},"CPU and memory for every service, and disk usage for every volume.",{"path":120,"title":119,"description":4179},"What your services print while they run, and the output of every deployment step.",{"path":124,"title":123,"description":4107},{"path":128,"title":127,"description":4099},{"path":4183,"title":4184,"description":4101},"\u002Flegal\u002F.navigation","Legal",{"path":4186,"title":4187,"description":4188},"\u002Flegal\u002Fdata-processing-agreement","Data Processing Agreement","How Lucity processes personal data on your behalf.",{"path":4190,"title":4191,"description":4192},"\u002Flegal\u002Fprivacy-policy","Privacy Policy","How Lucity handles your data.",{"path":4194,"title":4195,"description":4196},"\u002Flegal\u002Fterms-of-use","Terms of Use","Terms governing your use of Lucity.",1791216728955]