[{"data":1,"prerenderedAt":2385},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fdeploy\u002Fkubernetes":3,"docs-nav":2167},{"id":4,"title":5,"body":6,"description":2158,"extension":2159,"meta":2160,"navigation":2162,"path":2163,"seo":2164,"stem":2165,"__hash__":2166},"docs\u002Fdocs\u002Fdeploy\u002Fkubernetes.md","Deploying Tabularia on Kubernetes",{"type":7,"value":8,"toc":2141},"minimark",[9,13,31,39,44,71,82,94,98,337,348,448,452,533,536,610,636,646,684,691,706,779,782,798,834,1011,1018,1024,1084,1098,1104,1124,1132,1136,1195,1199,1241,1244,1248,1276,1301,1932,1950,1983,1986,2004,2078,2090,2094,2137],[10,11,5],"h1",{"id":12},"deploying-tabularia-on-kubernetes",[14,15,16,17,21,22,26,27,30],"p",{},"Operational notes for whoever moves Tabularia from ",[18,19,20],"code",{},"infrastructure\u002Fdocker-compose.yml","\n(the ",[23,24,25],"strong",{},"development"," stack) to Kubernetes. The compose file is the reference for which\nservices exist and how they talk to each other; it is ",[23,28,29],{},"not"," a production manifest:\nit runs dev servers with hot reload and bind-mounts the sources.",[14,32,33,34,38],{},"Everything below marked ",[35,36,37],"em",{},"verified"," was exercised on 2026-09-12 against the real\nstack (Scaleway object storage, managed ClickHouse).",[40,41,43],"h2",{"id":42},"helm-chart","Helm chart",[14,45,46,47,56,57,60,61,66,67,70],{},"A chart that applies everything on this page lives in\n",[48,49,53],"a",{"href":50,"rel":51},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Ftree\u002Fmain\u002Finfrastructure\u002Fhelm\u002Ftabularia",[52],"nofollow",[18,54,55],{},"infrastructure\u002Fhelm\u002Ftabularia",". It encodes\nthe constraints as templates — beat cannot be scaled, the root\nfilesystem is read-only, ",[18,58,59],{},"\u002Ftmp"," is sized per role — and refuses to render when a\nrequired secret is missing. Its\n",[48,62,65],{"href":63,"rel":64},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Fblob\u002Fmain\u002Finfrastructure\u002Fhelm\u002Ftabularia\u002FREADME.md",[52],"README"," explains each decision,\nand ",[18,68,69],{},"values-production.example.yaml"," is a filled-in starting point.",[14,72,73,74,77,78,81],{},"This page remains the reference for ",[35,75,76],{},"why","; the chart is ",[35,79,80],{},"how",".",[14,83,84,85,89,90,81],{},"To try Tabularia on one computer first, without knowing Kubernetes, follow\n",[48,86,88],{"href":87},"\u002Fdocs\u002Fdeploy\u002Fminikube","Try Tabularia on your computer with minikube",". To try the chart as a company would run\nit (several nodes, enforced network policies, a private registry with its own\ncertificate authority), follow ",[48,91,93],{"href":92},"\u002Fdocs\u002Fdeploy\u002Fkind","A company-like cluster on one computer",[40,95,97],{"id":96},"components","Components",[99,100,101,123],"table",{},[102,103,104],"thead",{},[105,106,107,111,114,117,120],"tr",{},[108,109,110],"th",{},"Component",[108,112,113],{},"Image",[108,115,116],{},"Command",[108,118,119],{},"Exposure",[108,121,122],{},"Replicas",[124,125,126,151,175,195,218,236,254,275,291,306,320],"tbody",{},[105,127,128,132,142,145,148],{},[129,130,131],"td",{},"Frontend (Nuxt 3)",[129,133,134,137,138],{},[18,135,136],{},"frontend\u002FDockerfile.prod",", see ",[48,139,141],{"href":140},"#frontend","Frontend",[129,143,144],{},"image default",[129,146,147],{},"public",[129,149,150],{},"N",[105,152,153,156,165,171,173],{},[129,154,155],{},"Gateway (FastAPI control plane)",[129,157,158,137,161],{},[18,159,160],{},"gateway\u002FDockerfile",[48,162,164],{"href":163},"#gateway-image-gatewaydockerfile","Gateway image",[129,166,167,168],{},"image default, ",[18,169,170],{},"APP__ROLE=api",[129,172,147],{},[129,174,150],{},[105,176,177,180,184,189,192],{},[129,178,179],{},"Orchestrator (the gateway's background work)",[129,181,182],{},[18,183,160],{},[129,185,167,186],{},[18,187,188],{},"APP__ROLE=orchestrator",[129,190,191],{},"none",[129,193,194],{},"1, or more for failover",[105,196,197,200,209,211,216],{},[129,198,199],{},"Engine API",[129,201,202,137,205],{},[18,203,204],{},"backend\u002FDockerfile",[48,206,208],{"href":207},"#backend-image-backenddockerfile","Backend image",[129,210,144],{},[129,212,213],{},[23,214,215],{},"internal only",[129,217,150],{},[105,219,220,223,227,232,234],{},[129,221,222],{},"Run worker",[129,224,225],{},[18,226,204],{},[129,228,229],{},[18,230,231],{},"celery -A app.tasks.celery_app worker -E -Q celery --loglevel=info",[129,233,191],{},[129,235,150],{},[105,237,238,241,245,250,252],{},[129,239,240],{},"Preview worker",[129,242,243],{},[18,244,204],{},[129,246,247],{},[18,248,249],{},"celery -A app.tasks.celery_app worker -E -Q preview --loglevel=info",[129,251,191],{},[129,253,150],{},[105,255,256,259,263,268,270],{},[129,257,258],{},"Celery beat",[129,260,261],{},[18,262,204],{},[129,264,265],{},[18,266,267],{},"celery -A app.tasks.celery_app beat --schedule \u002Ftmp\u002Fcelerybeat-schedule --loglevel=info",[129,269,191],{},[129,271,272],{},[23,273,274],{},"exactly 1",[105,276,277,280,283,286,289],{},[129,278,279],{},"PostgreSQL",[129,281,282],{},"managed service recommended",[129,284,285],{},"—",[129,287,288],{},"internal",[129,290,285],{},[105,292,293,296,299,301,303],{},[129,294,295],{},"Valkey \u002F Redis",[129,297,298],{},"any Redis-compatible",[129,300,285],{},[129,302,288],{},[129,304,305],{},"1",[105,307,308,311,314,316,318],{},[129,309,310],{},"Object storage",[129,312,313],{},"any S3-compatible endpoint",[129,315,285],{},[129,317,285],{},[129,319,285],{},[105,321,322,328,331,333,335],{},[129,323,324,325],{},"ClickHouse ",[35,326,327],{},"(optional engine)",[129,329,330],{},"managed service",[129,332,285],{},[129,334,285],{},[129,336,285],{},[14,338,339,340,343,344,347],{},"Use the ",[23,341,342],{},"image default command"," for the gateway and the engine API. The compose file\noverrides them with ",[18,345,346],{},"--reload",", which must not reach production.",[349,350,351,373,392,410,416,422,428,438],"ul",{},[352,353,354,360,361,364,365,368,369,372],"li",{},[23,355,356,357,81],{},"Gateway: two workloads from one image, told apart by ",[18,358,359],{},"APP__ROLE"," With ",[18,362,363],{},"api"," a\nprocess only answers requests, and can be replicated freely: one process uses one\ncore (about 95 requests a second), and sessions, the sign-in throttle and the\neditor's presence notice live in Postgres. With ",[18,366,367],{},"orchestrator"," it\nonly does the background work — schedules, flow executions, reconciliation,\nclean-up — and is not exposed. Several orchestrators share the work through the\ndatabase: a schedule slot and a queued execution are each taken by exactly one, and\na unique index keeps a flow from having two executions running. ",[18,370,371],{},"all"," (the default,\none process doing both) is what the development compose file runs.",[352,374,375,378,379,382,383,387,388,81],{},[23,376,377],{},"Schema."," Migrations (Alembic) are a step of their own: ",[18,380,381],{},"python -m app.db.migra",",\nrun by an init container in the Helm chart before the application starts, under a\nPostgres advisory lock. The application never migrates in production; it checks that\nthe database is not behind its code and refuses to start if it is (a newer database\nis fine: that is the previous version during a rolling update). It seeds the admin\nuser. See ",[48,384,386],{"href":385},"\u002Fdocs\u002Fdatabase-migrations","database-migrations.md","; upgrading an\ninstallation, step by step: ",[48,389,391],{"href":390},"\u002Fdocs\u002Fdeploy\u002Fupgrading","upgrading.md",[352,393,394,397,398,401,402,405,406,409],{},[23,395,396],{},"Database connections."," Each gateway and orchestrator process keeps its own pool,\nup to ",[18,399,400],{},"APP__GATEWAY_THREADS"," × 1.25 + ",[18,403,404],{},"DB__MAX_OVERFLOW"," (25 with twelve threads):\nsize Postgres' ",[18,407,408],{},"max_connections"," for the sum.",[352,411,412,415],{},[23,413,414],{},"Beat: one replica."," Two beats enqueue every periodic task twice.",[352,417,418,421],{},[23,419,420],{},"Postgres"," holds all control-plane metadata (users, groups, permissions, versioned\nflows, encrypted connection credentials, runs, audit log): back it up.",[352,423,424,427],{},[23,425,426],{},"Valkey"," is the Celery broker and the step-cache index. Its content is disposable:\nlosing it drops queued tasks and makes the cache start cold, nothing else.",[352,429,430,433,434,437],{},[23,431,432],{},"One installation per Valkey database."," The queues and keys have fixed names. Two\ninstallations on the same Valkey and the same ",[18,435,436],{},"externalServices.redis.db"," (staging and\nproduction on one managed Redis, both on db 0) share them: one's workers run the\nother's tasks, with the wrong key and storage, and Administration → Settings and\n→ Workers mix the two. Give each its own db number, or its own instance.",[352,439,440,443,444,447],{},[23,441,442],{},"One release per namespace."," The namespaces of the users' code are named after the\nrelease's namespace, and the Jobs, Secrets and images made at run time after run ids:\ntwo releases in one namespace collide (the second install fails on the namespaces; with\n",[18,445,446],{},"createNamespaces: false"," they share them, and follow each other's Jobs).",[40,449,451],{"id":450},"networking","Networking",[349,453,454,476,497,515],{},[352,455,456,459,460,463,464,467,468,471,472,475],{},[23,457,458],{},"Public",": the frontend and the gateway, on one host: ",[18,461,462],{},"\u002F"," to the frontend, ",[18,465,466],{},"\u002Fapi"," to\nthe gateway (not stripped; the gateway knows its prefix through ",[18,469,470],{},"APP__ROOT_PATH",").\nThe browser calls the API relatively (",[18,473,474],{},"NUXT_PUBLIC_API_BASE=\u002Fapi","). Routing the\ngateway's own paths instead cannot work: they are also pages of the frontend.",[352,477,478,481,482,484,485,488,489,492,493,81],{},[23,479,480],{},"The notebooks' host"," (with scripts): a second host name, ",[18,483,466],{}," to the gateway\nalone, where the marimo notebooks' apps and editors are served (",[18,486,487],{},"scripts.notebooks.host",",\n",[18,490,491],{},"NOTEBOOKS__ORIGIN","): there the gateway answers only for them, so a notebook's code\ncannot use the session of whoever opens it. A sibling of the interface's host, with\nits certificate. See ",[48,494,496],{"href":495},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks#an-address-of-their-own","An address of their own",[352,498,499,502,503,506,507,510,511,514],{},[23,500,501],{},"Internal",": the engine API is reached only by the gateway, through\n",[18,504,505],{},"ENGINE__BASE_URL"," (cluster DNS, e.g. ",[18,508,509],{},"http:\u002F\u002Fengine:8000","). Keep it off the ingress:\nit answers only requests carrying the gateway's internal token, derived from\n",[18,512,513],{},"SECURITY__FERNET_KEY"," (nothing to configure), and a NetworkPolicy should admit only\nthe gateway, the orchestrator and the workers — the chart ships one, on by default.",[352,516,517,520,521,524,525,528,529,532],{},[23,518,519],{},"The client's address",": the gateway reads ",[18,522,523],{},"X-Forwarded-For"," from the right, past\nthe proxies it trusts (",[18,526,527],{},"--forwarded-allow-ips",", the chart's ",[18,530,531],{},"gateway.trustedProxies",").\nWithout it every client seems to be the ingress controller, and the sign-in\nthrottle, keyed by (address, email), lets anyone lock the administrator out. The\ndefault trusts every private range, so any pod of the cluster could choose its\naddress: narrow it to the pod CIDR of your ingress controller. The application never\nreads the header itself: the audit log, the sessions and the throttle all use the\naddress the server resolved.",[14,534,535],{},"Ingress settings the application depends on:",[99,537,538,551],{},[102,539,540],{},[105,541,542,545,548],{},[108,543,544],{},"Setting",[108,546,547],{},"Why",[108,549,550],{},"Value",[124,552,553,570,581,596],{},[105,554,555,558,565],{},[129,556,557],{},"Max request body size",[129,559,560,561,564],{},"file uploads stream through the gateway to the engine; the gateway refuses one over ",[18,562,563],{},"UPLOAD__MAX_BYTES"," (1 GiB by default) with a 413",[129,566,567,568],{},"0 (unlimited) or above ",[18,569,563],{},[105,571,572,575,578],{},[129,573,574],{},"Request buffering",[129,576,577],{},"the proxy must not spool uploads to its own disk",[129,579,580],{},"off",[105,582,583,586,593],{},[129,584,585],{},"Read timeout",[129,587,588,589,592],{},"previews wait for a worker synchronously (",[18,590,591],{},"PREVIEW_TIMEOUT_SECONDS"," on the engine API, default 120)",[129,594,595],{},"above that ceiling",[105,597,598,604,607],{},[129,599,600,601],{},"Rate limit on ",[18,602,603],{},"\u002Fauth\u002Flogin",[129,605,606],{},"optional: the gateway already slows repeated failures per (address, email) and per address, across replicas; an edge limit also stops a distributed attack",[129,608,609],{},"per client IP, on that path only",[14,611,612,615,616,619,620,623,624,627,628,631,632,635],{},[23,613,614],{},"Egress."," The engine connects to databases that users configure as connections: that\nis the product, and also a server-side request surface. Restrict the engine pods' egress\nwith a ",[18,617,618],{},"NetworkPolicy"," to the object storage, ClickHouse, Valkey and the approved\ndatabase hosts, and block cloud metadata endpoints such as ",[18,621,622],{},"169.254.169.254",". The chart\ndoes it from ",[18,625,626],{},"networkPolicy.egressCIDRs"," (entries are CIDRs or ",[18,629,630],{},"{cidr, except}","), and\nalways carves the metadata address out of a range that holds it, ",[18,633,634],{},"0.0.0.0\u002F0"," included.\nWith the list empty no egress policy is rendered.",[14,637,638,641,642,645],{},[23,639,640],{},"Administration → Cluster"," (",[18,643,644],{},"clusterView.enabled",", on by default) shows the pods of\nthe release and of the users' code, their state, CPU and memory, and the warning events,\nread with read-only rights in those namespaces (no logs, no exec); CPU and memory need\nmetrics-server.",[14,647,648,641,651,654,655,658,659,662,663,666,667,670,671,674,675,679,680,81],{},[23,649,650],{},"Users' code",[18,652,653],{},"scripts.enabled",", ",[18,656,657],{},"containers.enabled",") runs as Jobs in namespaces of\nits own, never in the release's. The gateway (",[18,660,661],{},"…-scaler",") and the orchestrator\n(",[18,664,665],{},"…-orchestrator",") are the only pods with a Kubernetes API token, and only the\norchestrator may create the image builds, in their ",[18,668,669],{},"privileged"," namespace. Those\nnamespaces get a NetworkPolicy: the internet and the other namespaces, not the pods of\nthe release's namespace, the private ranges (unless listed in\n",[18,672,673],{},"networkPolicy.userCodePrivateCIDRs",") nor the cloud metadata. It needs a network plugin\nthat enforces egress rules. See ",[48,676,678],{"href":677},"\u002Fdocs\u002Fscripting#network-policies","Python and R in flows","\nand ",[48,681,683],{"href":682},"\u002Fdocs\u002Fcontainer-node","The Container node",[40,685,687,688,690],{"id":686},"backend-image-backenddockerfile","Backend image (",[18,689,204],{},")",[14,692,693,694,697,698,701,702,705],{},"One Dockerfile for the four backend roles and for both development and production.\nThe local ",[18,695,696],{},"docker-compose.yml"," builds the very same file with ",[18,699,700],{},"WITH_CHDB=true"," and\n",[18,703,704],{},"WITH_DEV=true"," (test dependencies); production builds it with the defaults.\nProduction variants:",[99,707,708,724],{},[102,709,710],{},[105,711,712,715,718,721],{},[108,713,714],{},"Variant",[108,716,717],{},"Build",[108,719,720],{},"Size",[108,722,723],{},"Engines",[124,725,726,742,759],{},[105,727,728,731,736,739],{},[129,729,730],{},"Lightweight, the default",[129,732,733],{},[18,734,735],{},"docker build backend\u002F",[129,737,738],{},"727 MB",[129,740,741],{},"Polars, DuckDB, ClickHouse external",[105,743,744,747,753,756],{},[129,745,746],{},"With chDB",[129,748,749,750],{},"add ",[18,751,752],{},"--build-arg WITH_CHDB=true",[129,754,755],{},"1.36 GB",[129,757,758],{},"the above plus embedded chDB",[105,760,761,764,769,772],{},[129,762,763],{},"With the SQL Server driver",[129,765,749,766],{},[18,767,768],{},"--build-arg WITH_MSSQL=true",[129,770,771],{},"+35 MB",[129,773,774,775],{},"Microsoft's driver, under Microsoft's licence: never published, see ",[48,776,778],{"href":777},"\u002Fdocs\u002Fsqlserver","sqlserver.md",[14,780,781],{},"chDB alone accounts for almost 600 MB, pandas and numpy included, which nothing else in\nthe application needs. In production the ClickHouse dialect is covered by the external\nClickHouse engine, so the lightweight variant is the default. Without chDB the engine\npicker shows it as not configured, and a flow that still asks for it fails with a clear\nmessage. For reference, the development image is 1.41 GB.",[14,783,784,787,788,791,792,794,795,797],{},[35,785,786],{},"Verified"," with ",[18,789,790],{},"--read-only"," and a tmpfs ",[18,793,59],{},": engine API healthy, run worker ready,\nbeat started; every available engine producing identical results on real data; CSV and\nExcel exports written to ",[18,796,59],{},"; database ingest from PostgreSQL and ClickHouse working in\nthe lightweight variant.",[349,799,800,811,828],{},[352,801,802,803,806,807,810],{},"runs as uid\u002Fgid ",[23,804,805],{},"10001",", no shell login, no build tools, no ",[18,808,809],{},"uv",", no test dependencies;",[352,812,813,814,817,818,820,821,654,824,827],{},"compatible with a ",[23,815,816],{},"read-only root filesystem",": the only writable path is ",[18,819,59],{},"\n(",[18,822,823],{},"TMPDIR=\u002Ftmp",[18,825,826],{},"HOME=\u002Ftmp","); uploads, intermediate parquet files, DuckDB and Polars\nspills and chDB state all go there;",[352,829,830,833],{},[18,831,832],{},"MALLOC_ARENA_MAX=2"," is preset to limit memory fragmentation from Polars\u002FArrow threads.",[835,836,841],"pre",{"className":837,"code":838,"language":839,"meta":840,"style":840},"language-yaml shiki shiki-themes github-dark-dimmed github-light","securityContext:\n  runAsNonRoot: true\n  runAsUser: 10001\n  runAsGroup: 10001\n  readOnlyRootFilesystem: true\n  allowPrivilegeEscalation: false\n  capabilities: { drop: [\"ALL\"] }\nvolumeMounts:\n  - { name: tmp, mountPath: \u002Ftmp }\nvolumes:\n  - name: tmp\n    emptyDir: { sizeLimit: 50Gi }   # size per role, see below\n","yaml","",[18,842,843,856,869,880,890,900,911,933,941,967,975,988],{"__ignoreMap":840},[844,845,848,852],"span",{"class":846,"line":847},"line",1,[844,849,851],{"class":850},"shb1k","securityContext",[844,853,855],{"class":854},"ssh_m",":\n",[844,857,859,862,865],{"class":846,"line":858},2,[844,860,861],{"class":850},"  runAsNonRoot",[844,863,864],{"class":854},": ",[844,866,868],{"class":867},"swcJU","true\n",[844,870,872,875,877],{"class":846,"line":871},3,[844,873,874],{"class":850},"  runAsUser",[844,876,864],{"class":854},[844,878,879],{"class":867},"10001\n",[844,881,883,886,888],{"class":846,"line":882},4,[844,884,885],{"class":850},"  runAsGroup",[844,887,864],{"class":854},[844,889,879],{"class":867},[844,891,893,896,898],{"class":846,"line":892},5,[844,894,895],{"class":850},"  readOnlyRootFilesystem",[844,897,864],{"class":854},[844,899,868],{"class":867},[844,901,903,906,908],{"class":846,"line":902},6,[844,904,905],{"class":850},"  allowPrivilegeEscalation",[844,907,864],{"class":854},[844,909,910],{"class":867},"false\n",[844,912,914,917,920,923,926,930],{"class":846,"line":913},7,[844,915,916],{"class":850},"  capabilities",[844,918,919],{"class":854},": { ",[844,921,922],{"class":850},"drop",[844,924,925],{"class":854},": [",[844,927,929],{"class":928},"sXfbr","\"ALL\"",[844,931,932],{"class":854},"] }\n",[844,934,936,939],{"class":846,"line":935},8,[844,937,938],{"class":850},"volumeMounts",[844,940,855],{"class":854},[844,942,944,947,950,952,955,957,960,962,964],{"class":846,"line":943},9,[844,945,946],{"class":854},"  - { ",[844,948,949],{"class":850},"name",[844,951,864],{"class":854},[844,953,954],{"class":928},"tmp",[844,956,654],{"class":854},[844,958,959],{"class":850},"mountPath",[844,961,864],{"class":854},[844,963,59],{"class":928},[844,965,966],{"class":854}," }\n",[844,968,970,973],{"class":846,"line":969},10,[844,971,972],{"class":850},"volumes",[844,974,855],{"class":854},[844,976,978,981,983,985],{"class":846,"line":977},11,[844,979,980],{"class":854},"  - ",[844,982,949],{"class":850},[844,984,864],{"class":854},[844,986,987],{"class":928},"tmp\n",[844,989,991,994,996,999,1001,1004,1007],{"class":846,"line":990},12,[844,992,993],{"class":850},"    emptyDir",[844,995,919],{"class":854},[844,997,998],{"class":850},"sizeLimit",[844,1000,864],{"class":854},[844,1002,1003],{"class":928},"50Gi",[844,1005,1006],{"class":854}," }   ",[844,1008,1010],{"class":1009},"sgHix","# size per role, see below\n",[1012,1013,1015,1016],"h3",{"id":1014},"sizing-tmp","Sizing ",[18,1017,59],{},[14,1019,1020,1021,1023],{},"Without a sized ",[18,1022,59],{}," volume, a large file lands in the container's ephemeral storage\nand the kubelet evicts the pod.",[99,1025,1026,1041],{},[102,1027,1028],{},[105,1029,1030,1033,1038],{},[108,1031,1032],{},"Role",[108,1034,1035,1036],{},"What lands in ",[18,1037,59],{},[108,1039,1040],{},"Size for",[124,1042,1043,1053,1063,1073],{},[105,1044,1045,1047,1050],{},[129,1046,199],{},[129,1048,1049],{},"uploaded files and their parquet conversion, CSV\u002FExcel exports",[129,1051,1052],{},"largest upload or export",[105,1054,1055,1057,1060],{},[129,1056,222],{},[129,1058,1059],{},"source parquet for in-process engines, database refresh results, data written to database or S3 outputs",[129,1061,1062],{},"largest dataset",[105,1064,1065,1067,1070],{},[129,1066,240],{},[129,1068,1069],{},"source parquet for in-process engines",[129,1071,1072],{},"largest source",[105,1074,1075,1078,1081],{},[129,1076,1077],{},"Beat",[129,1079,1080],{},"its schedule file",[129,1082,1083],{},"a few KB",[14,1085,1086,1087,1094,1095,1097],{},"With the ",[23,1088,1089,1090,1093],{},"ClickHouse engine in ",[18,1091,1092],{},"s3"," transport"," for a flow, in both its development and\nproduction engine, flow execution never touches local disk: the ClickHouse server reads\nsources from and writes results to the bucket. Uploads, exports, database refreshes and\ndatabase\u002FS3 outputs still use ",[18,1096,59],{}," whatever the engine.",[40,1099,1101,1102,690],{"id":1100},"gateway-image-gatewaydockerfile","Gateway image (",[18,1103,160],{},[14,1105,1106,1107,1109,1110,1112,1113,1116,1117,1119,1120,1123],{},"Same rules as the backend image: one Dockerfile for development and production, uid\u002Fgid\n",[23,1108,805],{},", compatible with a read-only root filesystem, ",[18,1111,59],{}," the only writable path.\nThe gateway itself writes no files, so a small ",[18,1114,1115],{},"emptyDir"," on ",[18,1118,59],{}," is enough. Test\ndependencies are included only with ",[18,1121,1122],{},"--build-arg WITH_DEV=true",", which the local compose\nsets. Production size: 199 MB.",[14,1125,1126,1128,1129,1131],{},[35,1127,786],{}," read-only against an empty PostgreSQL: the migration step creates the\nschema, the admin user is seeded, login works. The ",[18,1130,851],{}," shown for the backend\napplies unchanged.",[40,1133,1135],{"id":1134},"worker-lifecycle","Worker lifecycle",[349,1137,1138,1156,1168,1185],{},[352,1139,1140,1143,1144,1147,1148,1151,1152,1155],{},[23,1141,1142],{},"Graceful shutdown."," A run can last up to the Celery task limit (3600 s). On\n",[18,1145,1146],{},"SIGTERM"," a worker stops taking tasks and waits for the running ones; with Kubernetes'\ndefault ",[18,1149,1150],{},"terminationGracePeriodSeconds: 30",", every deploy would kill in-flight runs\n(the chart sets 3600 s for the run workers, 120 s for the preview workers). Tasks are not\nredelivered (",[18,1153,1154],{},"acks_late"," is off, on purpose: outputs in append mode are not\nidempotent). Keep the grace period above the task limit (raised from Administration →\nSettings too), or accept that deploys interrupt the longer runs.",[352,1157,1158,1161,1162,1164,1165,81],{},[23,1159,1160],{},"A worker killed mid-run"," (OOMKilled: on cgroup v2 the kernel kills the whole\ncontainer; an emptyDir past its ",[18,1163,998],{},"; its node lost) leaves its runs started in\nCelery's results. The engine tells them apart from running ones by the worker's own\nrecord in Valkey (gone, or started again after the run began) and reports them failed,\n«The worker running it stopped before the end», within about half a minute, instead of\nwaiting for ",[18,1166,1167],{},"ENGINE__RUN_STALE_TIMEOUT_SECONDS",[352,1169,1170,1173,1174,1177,1178,679,1181,1184],{},[23,1171,1172],{},"Gateway and engine."," Stopping, they wait 5 s (",[18,1175,1176],{},"preStop",") for the Service to drop\nthem, then finish the requests in flight (a preview waits up to 290 s, an upload as long\nas it takes) for up to their grace period minus 15 s: ",[18,1179,1180],{},"gateway.terminationGracePeriodSeconds",[18,1182,1183],{},"engineApi.terminationGracePeriodSeconds",", 300 s. The orchestrator's is 60 s: it\ncloses the executions it carries as interrupted and stops their steps already with a\nworker (revoked), so a flow run again does not write on top of them.",[352,1186,1187,1190,1191,1194],{},[23,1188,1189],{},"Memory."," Set a memory limit per pod. The per-child memory cap is derived from the\ncontainer limit and ",[18,1192,1193],{},"CELERY__WORKER_CONCURRENCY",", so the limit is also the tuning knob.",[40,1196,1198],{"id":1197},"probes","Probes",[99,1200,1201,1211],{},[102,1202,1203],{},[105,1204,1205,1208],{},[108,1206,1207],{},"Service",[108,1209,1210],{},"Endpoint",[124,1212,1213,1223,1232],{},[105,1214,1215,1218],{},[129,1216,1217],{},"Gateway",[129,1219,1220],{},[18,1221,1222],{},"GET \u002Fhealth",[105,1224,1225,1227],{},[129,1226,199],{},[129,1228,1229],{},[18,1230,1231],{},"GET \u002Fhealthcheck\u002F",[105,1233,1234,1236],{},[129,1235,141],{},[129,1237,1238],{},[18,1239,1240],{},"GET \u002Flogin",[14,1242,1243],{},"The gateway's and the engine's answer on the event loop, apart from the threads serving\nrequests, so a busy pod is not taken for a dead one; the chart gives every probe 5 s.\nWorkers and beat expose no HTTP port; rely on the restart policy.",[40,1245,1247],{"id":1246},"configuration","Configuration",[14,1249,1250,1251,1254,1255,1258,1259,1263,1264,1271,1272,1275],{},"Settings are environment variables with ",[18,1252,1253],{},"__"," as the nesting delimiter. Every one of\nthem, with its default, its bounds and the ",[18,1256,1257],{},"values.yaml"," key the chart sets it from, is\nin the ",[48,1260,1262],{"href":1261},"\u002Fdocs\u002Fconfiguration","configuration reference","; the commented starting point is\n",[48,1265,1268],{"href":1266,"rel":1267},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Fblob\u002Fmain\u002Finfrastructure\u002F.env.example",[52],[18,1269,1270],{},"infrastructure\u002F.env.example",". Values marked\n",[35,1273,1274],{},"secret"," belong in Kubernetes Secrets.",[14,1277,1278,1281,1282,1285,1286,1289,1290,1293,1294,1296,1297,1300],{},[23,1279,1280],{},"Comments in env files."," Not every tool strips a trailing ",[18,1283,1284],{},"# comment"," from a value:\n",[18,1287,1288],{},"docker run --env-file"," keeps it as part of the value, and the gateway then refuses to\nstart on ",[18,1291,1292],{},"APP__TIMEZONE"," — worse, a secret with a trailing comment would be silently\nwrong. ",[18,1295,1270],{}," keeps every comment on its own line; if you derive\nConfigMaps or Secrets from an existing ",[18,1298,1299],{},".env",", check that no value carries a comment.",[99,1302,1303,1318],{},[102,1304,1305],{},[105,1306,1307,1310,1312,1315],{},[108,1308,1309],{},"Variable",[108,1311,1217],{},[108,1313,1314],{},"Backend roles",[108,1316,1317],{},"Notes",[124,1319,1320,1339,1358,1379,1401,1426,1439,1459,1487,1512,1533,1552,1573,1599,1619,1640,1662,1695,1708,1729,1753,1773,1800,1813,1830,1847,1861,1877,1901,1915],{},[105,1321,1322,1327,1330,1332],{},[129,1323,1324],{},[18,1325,1326],{},"APP__ENV_NAME=production",[129,1328,1329],{},"✓",[129,1331],{},[129,1333,1334,1335,1338],{},"enables the production guard: the gateway refuses to start with development secrets. It has ",[23,1336,1337],{},"no effect"," on the engine or the workers — nothing reads it there",[105,1340,1341,1348,1350,1352],{},[129,1342,1343,1345,1346],{},[18,1344,513],{}," ",[35,1347,1274],{},[129,1349,1329],{},[129,1351,1329],{},[129,1353,1354,1357],{},[23,1355,1356],{},"same value everywhere","; encrypts database credentials; every process refuses to start without a valid key",[105,1359,1360,1364,1366,1368],{},[129,1361,1362],{},[18,1363,1292],{},[129,1365,1329],{},[129,1367],{},[129,1369,1370,1371,1374,1375,1378],{},"timezone in which the ",[23,1372,1373],{},"gateway"," interprets cron schedules. Celery beat keeps its own clock (",[18,1376,1377],{},"CELERY__TIMEZONE",", UTC by default), but the gateway converts schedules to UTC before enqueuing, so leaving beat on UTC is correct",[105,1380,1381,1385,1387,1389],{},[129,1382,1383],{},[18,1384,359],{},[129,1386,1329],{},[129,1388],{},[129,1390,1391,1392,1394,1395,1397,1398,1400],{},"what a gateway process does: ",[18,1393,363],{}," (answer requests), ",[18,1396,367],{}," (schedules, flow executions, clean-up) or ",[18,1399,371],{}," (both, the default). Set per workload, not in the shared ConfigMap",[105,1402,1403,1408,1410,1412],{},[129,1404,1405],{},[18,1406,1407],{},"OPENLINEAGE__URL",[129,1409,1329],{},[129,1411],{},[129,1413,1414,1415,1418,1419,1422,1423,690],{},"OpenLineage collector (Marquez, DataHub…); with ",[18,1416,1417],{},"OPENLINEAGE__FILE"," (JSON Lines) and ",[18,1420,1421],{},"OPENLINEAGE__NAMESPACE",". Empty = off. The API key goes in the secret (",[18,1424,1425],{},"secrets.openlineageApiKey",[105,1427,1428,1432,1434,1436],{},[129,1429,1430],{},[18,1431,400],{},[129,1433,1329],{},[129,1435],{},[129,1437,1438],{},"threads per gateway process (default 40; 12 is what the chart and the measurements use: fewer serve more). The connection pool follows it",[105,1440,1441,1452,1454,1456],{},[129,1442,1443,654,1446,654,1449],{},[18,1444,1445],{},"ORCHESTRATOR__DEAD_AFTER_SECONDS",[18,1447,1448],{},"ORCHESTRATOR__QUEUE_TIMEOUT_SECONDS",[18,1450,1451],{},"ORCHESTRATOR__MAX_CONCURRENT",[129,1453,1329],{},[129,1455],{},[129,1457,1458],{},"when a silent execution is given up for dead (60), when a queued one is no longer started (900), how many one orchestrator carries at once (20)",[105,1460,1461,1480,1482,1484],{},[129,1462,1463,654,1466,654,1469,654,1472,1345,1475,654,1477],{},[18,1464,1465],{},"DB__HOST",[18,1467,1468],{},"DB__PORT",[18,1470,1471],{},"DB__USER",[18,1473,1474],{},"DB__PASSWORD",[35,1476,1274],{},[18,1478,1479],{},"DB__NAME",[129,1481,1329],{},[129,1483],{},[129,1485,1486],{},"metadata Postgres",[105,1488,1489,1494,1496,1498],{},[129,1490,1491],{},[18,1492,1493],{},"DB__AUTO_MIGRATE",[129,1495,1329],{},[129,1497],{},[129,1499,1500,1501,1504,1505,1507,1508,1511],{},"leave it ",[18,1502,1503],{},"false"," (the default): the schema is migrated by the init container (",[18,1506,381],{},") and the gateway only checks it. ",[18,1509,1510],{},"true"," is for the development compose",[105,1513,1514,1519,1521,1523],{},[129,1515,1516],{},[18,1517,1518],{},"AUDIT__RETENTION_MINUTES",[129,1520,1329],{},[129,1522],{},[129,1524,1525,1526,1529,1530,690],{},"how long the audit log is kept, in minutes; ",[18,1527,1528],{},"0"," (the default) = forever. The privacy notice can quote it (",[18,1531,1532],{},"app.auditRetentionMinutes",[105,1534,1535,1545,1547,1549],{},[129,1536,1537,1345,1540,654,1542],{},[18,1538,1539],{},"JWT__SECRET",[35,1541,1274],{},[18,1543,1544],{},"JWT__ACCESS_TTL_MINUTES",[129,1546,1329],{},[129,1548],{},[129,1550,1551],{},"signs the session tokens; the longest a session lasts, 720 minutes by default",[105,1553,1554,1559,1561,1563],{},[129,1555,1556],{},[18,1557,1558],{},"AUTH__COOKIE_DOMAIN",[129,1560,1329],{},[129,1562],{},[129,1564,1565,1566,1569,1570,690],{},"the session cookie's domain; empty (the default) = the gateway's host. Set the shared domain (",[18,1567,1568],{},"example.com",") only when the UI and the API are on two hosts of it (",[18,1571,1572],{},"auth.cookieDomain",[105,1574,1575,1583,1585,1587],{},[129,1576,1577,654,1580],{},[18,1578,1579],{},"AUTH__PERSONAL_TOKENS",[18,1581,1582],{},"AUTH__API_TOKEN_MAX_DAYS",[129,1584,1329],{},[129,1586],{},[129,1588,1589,1590,1592,1593,654,1596],{},"API tokens (docs\u002Fapi.md): whether people may create their own (",[18,1591,1510],{},"), and the longest a token may last (365 days) — ",[18,1594,1595],{},"auth.personalTokens",[18,1597,1598],{},"auth.apiTokenMaxDays",[105,1600,1601,1609,1611,1613],{},[129,1602,1603,654,1606],{},[18,1604,1605],{},"WEBHOOKS__RETENTION_DAYS",[18,1607,1608],{},"WEBHOOKS__TIMEOUT_SECONDS",[129,1610,1329],{},[129,1612],{},[129,1614,1615,1616,1618],{},"webhook call history kept (30 days) and how long a receiver may take (10 s). Calls go out from the ",[23,1617,367],{}," pods, which the chart's egress policy does not restrict; if your cluster limits their egress otherwise, open it to the receivers",[105,1620,1621,1626,1628,1630],{},[129,1622,1623],{},[18,1624,1625],{},"AUTH__SESSION_IDLE_MINUTES",[129,1627,1329],{},[129,1629],{},[129,1631,1632,1633,1635,1636,1639],{},"minutes without activity after which a session closes; ",[18,1634,1528],{}," (the default) = never. The open page signs out by itself after a one-minute warning (",[18,1637,1638],{},"auth.sessionIdleMinutes"," in the chart)",[105,1641,1642,1655,1657,1659],{},[129,1643,1644,654,1647,1345,1650,654,1652],{},[18,1645,1646],{},"AUTH__ADMIN_EMAIL",[18,1648,1649],{},"AUTH__ADMIN_PASSWORD",[35,1651,1274],{},[18,1653,1654],{},"AUTH__ADMIN_NAME",[129,1656,1329],{},[129,1658],{},[129,1660,1661],{},"break-glass admin, seeded at first start",[105,1663,1664,1669,1671,1673],{},[129,1665,1666],{},[18,1667,1668],{},"APP__CORS_ORIGINS",[129,1670,1329],{},[129,1672],{},[129,1674,1675,1676,1679,1680,1683,1684,1686,1687,1690,1691,1694],{},"JSON list with the public frontend URL, e.g. ",[18,1677,1678],{},"[\"https:\u002F\u002Fapp.example.com\"]",". Needed ",[23,1681,1682],{},"only"," when the frontend is served from a different host than the gateway — then set ",[18,1685,1558],{}," too; the reference ingress puts both on one host, where it can stay unset (",[18,1688,1689],{},"app.corsOrigins"," in the chart). ",[18,1692,1693],{},"*"," is refused: the session is a cookie",[105,1696,1697,1701,1703,1705],{},[129,1698,1699],{},[18,1700,505],{},[129,1702,1329],{},[129,1704],{},[129,1706,1707],{},"internal URL of the engine API",[105,1709,1710,1715,1717,1719],{},[129,1711,1712],{},[18,1713,1714],{},"ENGINE__BUCKET",[129,1716,1329],{},[129,1718],{},[129,1720,1721,1722,1345,1725,1728],{},"the gateway's own copy of the bucket name; it compares it against the bucket in every request and rejects a mismatch with 403. It ",[23,1723,1724],{},"must equal",[18,1726,1727],{},"STORAGE__BUCKET",", so the chart and the compose file both derive it from the same single value — set it by hand only if you know why",[105,1730,1731,1736,1738,1740],{},[129,1732,1733],{},[18,1734,1735],{},"ENGINE__SETTINGS_PANEL",[129,1737,1329],{},[129,1739],{},[129,1741,1742,1743,1746,1747,1749,1750,690],{},"Administration → Settings may change about a hundred settings of the gateway and the engine at runtime (docs\u002Fengine-settings.md); the ones read at start are applied by restarting their pods one at a time (",[18,1744,1745],{},"settingsRollout.enabled","). ",[18,1748,1503],{}," keeps the environment the only source (",[18,1751,1752],{},"engine.settingsPanel",[105,1754,1755,1766,1768,1770],{},[129,1756,1757,654,1760,654,1763],{},[18,1758,1759],{},"REDIS__HOST",[18,1761,1762],{},"REDIS__PORT",[18,1764,1765],{},"REDIS__DB",[129,1767],{},[129,1769,1329],{},[129,1771,1772],{},"broker and cache index",[105,1774,1775,1793,1795,1797],{},[129,1776,1777,654,1780,654,1783,1345,1786,654,1788,654,1790],{},[18,1778,1779],{},"STORAGE__ENDPOINT",[18,1781,1782],{},"STORAGE__ACCESS_KEY",[18,1784,1785],{},"STORAGE__SECRET_KEY",[35,1787,1274],{},[18,1789,1727],{},[18,1791,1792],{},"STORAGE__REGION",[129,1794],{},[129,1796,1329],{},[129,1798,1799],{},"object storage",[105,1801,1802,1806,1808,1810],{},[129,1803,1804],{},[18,1805,1193],{},[129,1807],{},[129,1809,1329],{},[129,1811,1812],{},"per worker pod",[105,1814,1815,1820,1822,1824],{},[129,1816,1817],{},[18,1818,1819],{},"EXPORT__MAX_ROWS",[129,1821],{},[129,1823,1329],{},[129,1825,1826,1827,1829],{},"rows a CSV download may have (default 5,000,000; ",[18,1828,1528],{}," = no cap): the browser holds the whole file in memory",[105,1831,1832,1840,1842,1844],{},[129,1833,1834,654,1837],{},[18,1835,1836],{},"CACHE__TTL_SECONDS",[18,1838,1839],{},"CACHE__SWEEP_INTERVAL_SECONDS",[129,1841],{},[129,1843,1329],{},[129,1845,1846],{},"step cache lifetime and sweep cadence",[105,1848,1849,1854,1856,1858],{},[129,1850,1851],{},[18,1852,1853],{},"CLICKHOUSE_EXTERNAL__*",[129,1855],{},[129,1857,1329],{},[129,1859,1860],{},"optional ClickHouse engine, off when the host is empty",[105,1862,1863,1868,1870,1872],{},[129,1864,1865],{},[18,1866,1867],{},"BIGQUERY__*",[129,1869],{},[129,1871,1329],{},[129,1873,1874,1875],{},"optional BigQuery engine, off when the project is empty; credentials ",[35,1876,1274],{},[105,1878,1879,1884,1886,1888],{},[129,1880,1881],{},[18,1882,1883],{},"AI__*",[129,1885,1329],{},[129,1887],{},[129,1889,1890,1891,1893,1894,654,1897,1900],{},"optional AI assistant, off without a base URL and a key (",[35,1892,1274],{},"). Set the spending caps (",[18,1895,1896],{},"AI__MAX_COST_PER_DAY_USD",[18,1898,1899],{},"AI__MAX_COST_PER_DAY_TOTAL_USD",") wherever people you do not know can sign in",[105,1902,1903,1908,1910,1912],{},[129,1904,1905],{},[18,1906,1907],{},"OIDC__*",[129,1909,1329],{},[129,1911],{},[129,1913,1914],{},"optional SSO, off when the issuer is empty",[105,1916,1917,1925,1927,1929],{},[129,1918,1919,654,1922],{},[18,1920,1921],{},"NUXT_PUBLIC_API_BASE",[18,1923,1924],{},"NUXT_PUBLIC_BUCKET",[129,1926],{},[129,1928],{},[129,1930,1931],{},"frontend, read at startup: public gateway URL and default bucket",[14,1933,1934,1935,1938,1939,1942,1943,1946,1947,1949],{},"The Helm chart sets the variables above from named values; any other one goes through\n",[18,1936,1937],{},"extraEnv",", by its variable name (secrets through ",[18,1940,1941],{},"existingSecret","). The ",[23,1944,1945],{},"Helm value","\ncolumn of the ",[48,1948,1262],{"href":1261}," says which is which.",[14,1951,1952,1345,1955,1958,1959,1962,1963,1966,1967,1970,1971,1974,1975,1978,1979,81],{},[23,1953,1954],{},"SSO behind real hostnames.",[18,1956,1957],{},"OIDC__REDIRECT_URI"," is\n",[18,1960,1961],{},"https:\u002F\u002F\u003Chost>\u002Fapi\u002Fauth\u002Fsso\u002Fcallback"," and must be registered, identical, on the\nidentity provider; ",[18,1964,1965],{},"OIDC__POST_LOGIN_URL"," is ",[18,1968,1969],{},"https:\u002F\u002F\u003Chost>\u002Fauth\u002Fcallback",". ",[18,1972,1973],{},"\u003Chost>"," is\nthe address people open Tabularia at: a sign-in started at another name of the same\nserver (an IP, an internal alias) loses its cookie on the way back and fails with\n",[18,1976,1977],{},"tx_missing"," (the gateway logs both addresses). The gateway allows a minute between its\nclocks and the identity provider's; keep NTP on the nodes. Step by step, with\nKeycloak: ",[48,1980,1982],{"href":1981},"\u002Fdocs\u002Fsso-keycloak","Single sign-on with Keycloak",[40,1984,141],{"id":1985},"frontend",[14,1987,1988,1989,1992,1993,1995,1996,1999,2000,2003],{},"Build the ",[23,1990,1991],{},"production"," image from ",[18,1994,136],{},"; ",[18,1997,1998],{},"frontend\u002FDockerfile"," is\nthe development server with hot reload and must not reach production. The production\nimage compiles the app with ",[18,2001,2002],{},"nuxt build"," and ships only the compiled output, served by\nNitro on port 3000. Size: 176 MB.",[349,2005,2006,2024,2030,2042],{},[352,2007,2008,2011,2012,2014,2015,2017,2018,2020,2021,81],{},[23,2009,2010],{},"One image for every environment."," The public settings are read at startup, so no\nrebuild per environment: ",[18,2013,1921],{}," (public gateway URL, called by the\nbrowser), ",[18,2016,1924],{}," (must equal ",[18,2019,1727],{},") and, optionally,\n",[18,2022,2023],{},"NUXT_PUBLIC_GRAFANA_URL",[352,2025,2026,2029],{},[23,2027,2028],{},"No server-side API calls."," Server rendering never calls the gateway; every API call\ncomes from the browser. The frontend pods need no route to the gateway.",[352,2031,2032,2035,2036,2038,2039,2041],{},[23,2033,2034],{},"Same hardening as the other images",": uid\u002Fgid 10001, read-only root filesystem, ",[18,2037,59],{},"\nthe only writable path; the ",[18,2040,851],{}," shown for the backend applies unchanged.",[352,2043,2044,2047,2048,2051,2052,2055,2056,2058,2059,2061,2062,2065,2066,2069,2070,2073,2074,2077],{},[23,2045,2046],{},"Content-Security-Policy from the frontend itself."," Every page carries one: scripts\nonly from ",[18,2049,2050],{},"\u002F_nuxt"," plus Nuxt's own inline scripts, allowed by hash; ",[18,2053,2054],{},"fetch"," only to the\nsite and to ",[18,2057,1921],{},"; frames only the site and ",[18,2060,2023],{},".\nNothing to configure on the ingress. The session cookie is written by the gateway,\n",[18,2063,2064],{},"HttpOnly"," (no script on the page can read it) and ",[18,2067,2068],{},"SameSite=Lax",", and ",[18,2071,2072],{},"Secure"," when\nthe page is served over HTTPS — behind a proxy that terminates TLS, through\n",[18,2075,2076],{},"X-Forwarded-Proto",", which ingress-nginx sets.",[14,2079,2080,2082,2083,2085,2086,2089],{},[35,2081,786],{}," read-only: the login page renders, a gateway URL set at startup ends up in the\nserved page, compiled assets are served, and ",[18,2084,462],{}," redirects to ",[18,2087,2088],{},"\u002Flogin"," without a session.",[40,2091,2093],{"id":2092},"known-application-limits-that-affect-operations","Known application limits that affect operations",[349,2095,2096,2105,2114,2131],{},[352,2097,2098,2101,2102,2104],{},[23,2099,2100],{},"Flow executions are not resumed."," An execution whose orchestrator stops or dies\nis closed as failed, with a message that says so — at once on a clean stop, after\n",[18,2103,1445],{}," (a minute) when the process is killed. The steps\nalready done have written their output; the flow has to be launched again. Rollouts\nof the orchestrator make this visible in the run history.",[352,2106,2107,2110,2111,2113],{},[23,2108,2109],{},"Versions before the queue must not run next to this one."," A gateway from before\n",[18,2112,359],{}," existed ran the scheduler in process and knows nothing of the queue:\nstop it before starting the new version (the chart's README has the two commands).",[352,2115,2116,2119,2120,654,2122,2124,2125,2127,2128,2130],{},[23,2117,2118],{},"The interface and the gateway must be on the same domain."," The browser's session\nis a cookie the gateway writes (",[18,2121,2064],{},[18,2123,2068],{},"), and the interface's\nserver must see it too — the reference ingress serves both from one host, with the\ngateway under ",[18,2126,466],{},". Two hosts of one domain work with ",[18,2129,1558],{},"; two\nunrelated domains leave the browser without a session.",[352,2132,2133,2136],{},[23,2134,2135],{},"Removing someone from an SSO group takes effect at their next sign-in",", or at once\nif an administrator closes their sessions (Audit page). Disabling the account, or\nchanging its password, closes them by itself.",[2138,2139,2140],"style",{},"html pre.shiki code .shb1k, html code.shiki .shb1k{--shiki-default:#8DDB8C;--shiki-light:#22863A}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .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);}",{"title":840,"searchDepth":858,"depth":871,"links":2142},[2143,2144,2145,2146,2151,2153,2154,2155,2156,2157],{"id":42,"depth":858,"text":43},{"id":96,"depth":858,"text":97},{"id":450,"depth":858,"text":451},{"id":686,"depth":858,"text":2147,"children":2148},"Backend image (backend\u002FDockerfile)",[2149],{"id":1014,"depth":871,"text":2150},"Sizing \u002Ftmp",{"id":1100,"depth":858,"text":2152},"Gateway image (gateway\u002FDockerfile)",{"id":1134,"depth":858,"text":1135},{"id":1197,"depth":858,"text":1198},{"id":1246,"depth":858,"text":1247},{"id":1985,"depth":858,"text":141},{"id":2092,"depth":858,"text":2093},"Operational notes for whoever moves Tabularia from infrastructure\u002Fdocker-compose.yml\n(the development stack) to Kubernetes. The compose file is the reference for which\nservices exist and how they talk to each other; it is not a production manifest:\nit runs dev servers with hot reload and bind-mounts the sources.","md",{"source":2161},"docs\u002Fdeploy\u002Fkubernetes.md",true,"\u002Fdocs\u002Fdeploy\u002Fkubernetes",{"title":5,"description":2158},"docs\u002Fdeploy\u002Fkubernetes","tLFXwNSTZyDZByCQC6z4u4eKNcyPuQxeeFwv1lyEgiA",[2168,2186,2240,2272,2303,2330,2356,2377],{"title":2169,"icon":2170,"intro":2171,"items":2172},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[2173,2175,2178,2180,2183],{"path":2174,"title":2169},"\u002Fdocs\u002Fgetting-started",{"path":2176,"title":2177},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":87,"title":2179},"Try it on your computer (minikube)",{"path":2181,"title":2182},"\u002Fdocs\u002Fconcepts","Concepts",{"path":2184,"title":2185},"\u002Fdocs\u002Foverview","Overview (README)",{"title":2187,"icon":2188,"intro":2189,"items":2190},"Capabilities","capabilities","What you can build: visual flows, analysis on any step, AI questions, pixel-perfect reports, dashboards, Python, R and SQL scripts, containers and the orchestration that ties them together.",[2191,2194,2197,2200,2203,2206,2209,2212,2215,2218,2221,2223,2226,2229,2232,2235,2237],{"path":2192,"title":2193},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":2195,"title":2196},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":2198,"title":2199},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":2201,"title":2202},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":2204,"title":2205},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":2207,"title":2208},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":2210,"title":2211},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":2213,"title":2214},"\u002Fdocs\u002Fscripting","Python and R",{"path":2216,"title":2217},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":2219,"title":2220},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":682,"title":2222},"Docker images in flows",{"path":2224,"title":2225},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":2227,"title":2228},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":2230,"title":2231},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":2233,"title":2234},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":2236,"title":723},"\u002Fdocs\u002Fcapabilities\u002Fengines",{"path":2238,"title":2239},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":2241,"icon":2242,"intro":2243,"items":2244},"Sources","sources","Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[2245,2248,2251,2254,2257,2260,2263,2266,2269],{"path":2246,"title":2247},"\u002Fdocs\u002Fsources","All sources",{"path":2249,"title":2250},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":2252,"title":2253},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":2255,"title":2256},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":2258,"title":2259},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":2261,"title":2262},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":2264,"title":2265},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":2267,"title":2268},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":2270,"title":2271},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":2273,"icon":2274,"intro":2275,"items":2276},"Connections","connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[2277,2280,2283,2285,2288,2290,2291,2293,2294,2297,2300],{"path":2278,"title":2279},"\u002Fdocs\u002Fconnections","All connections",{"path":2281,"title":2282},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":777,"title":2284},"Microsoft SQL Server driver",{"path":2286,"title":2287},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":2258,"title":2289},"SFTP servers",{"path":2261,"title":2262},{"path":2264,"title":2292},"SharePoint",{"path":2267,"title":2268},{"path":2295,"title":2296},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":2298,"title":2299},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":2301,"title":2302},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":2304,"icon":2305,"intro":2306,"items":2307},"Integrations","integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[2308,2311,2314,2317,2320,2323,2324,2327],{"path":2309,"title":2310},"\u002Fdocs\u002Fintegrations","All integrations",{"path":2312,"title":2313},"\u002Fdocs\u002Fintegrations\u002Fai","AI: where it is used",{"path":2315,"title":2316},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":2318,"title":2319},"\u002Fdocs\u002Fdbt-export","Export to dbt",{"path":2321,"title":2322},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":1981,"title":1982},{"path":2325,"title":2326},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":2328,"title":2329},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":2331,"icon":2332,"intro":2333,"items":2334},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[2335,2338,2341,2344,2347,2350,2353],{"path":2336,"title":2337},"\u002Fdocs\u002Fgovernance","Overview",{"path":2339,"title":2340},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":2342,"title":2343},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":2345,"title":2346},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":2348,"title":2349},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",{"path":2351,"title":2352},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example on a laptop",{"path":2354,"title":2355},"\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user","A ClickHouse user for the AI assistant",{"title":2357,"icon":2358,"intro":2359,"items":2360},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[2361,2363,2365,2367,2370,2372,2375],{"path":2163,"title":2362},"Kubernetes and Helm",{"path":92,"title":2364},"A company-like cluster on one computer (kind)",{"path":390,"title":2366},"Upgrading, backups and restore",{"path":2368,"title":2369},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","Install and upgrade checklist",{"path":1261,"title":2371},"Configuration reference",{"path":2373,"title":2374},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":385,"title":2376},"Database migrations",{"title":2378,"icon":2379,"intro":2380,"items":2381},"Reference","reference","Release process and the remaining reference pages.",[2382],{"path":2383,"title":2384},"\u002Fdocs\u002Frelease-process","Release process",1793656371142]