[{"data":1,"prerenderedAt":1173},["ShallowReactive",2],{"doc:\u002Fdocs\u002Frelease-process":3,"docs-nav":948},{"id":4,"title":5,"body":6,"description":939,"extension":940,"meta":941,"navigation":943,"path":944,"seo":945,"stem":946,"__hash__":947},"docs\u002Fdocs\u002Frelease-process.md","Release process",{"type":7,"value":8,"toc":926},"minimark",[9,13,23,28,138,149,153,244,263,267,273,301,320,324,347,367,444,448,458,468,472,479,493,496,580,587,591,647,653,658,661,698,754,760,779,783,820,842,896,902,906,922],[10,11,5],"h1",{"id":12},"release-process",[14,15,16,17,22],"p",{},"How a release goes from a tag to Docker Hub. Written for whoever maintains the\nproject; installing or upgrading is in ",[18,19,21],"a",{"href":20},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","deploy\u002Frelease-checklist.md",".",[24,25,27],"h2",{"id":26},"cutting-a-release","Cutting a release",[29,30,31,68,79],"ol",{},[32,33,34,35,39,40,43,44,47,48,51,52,55,56,59,60,63,64,67],"li",{},"Set the new version everywhere the code carries it: ",[36,37,38],"code",{},"backend\u002Fpyproject.toml",",\n",[36,41,42],{},"gateway\u002Fpyproject.toml",", ",[36,45,46],{},"gateway\u002Fapp\u002Fcore\u002Fconfig.py"," (the version the app shows),\n",[36,49,50],{},"frontend\u002Fpackage.json",", and in the Helm chart ",[36,53,54],{},"appVersion"," in ",[36,57,58],{},"Chart.yaml"," plus the\nimage tags in ",[36,61,62],{},"values.yaml"," and ",[36,65,66],{},"values-production.example.yaml",". The workflow\nrefuses a tag that disagrees with any of them and names the file.",[32,69,70,71,74,75,78],{},"Move the ",[36,72,73],{},"Unreleased"," section of ",[36,76,77],{},"CHANGELOG.md"," under the new version.",[32,80,81,82,85,86],{},"Commit on ",[36,83,84],{},"main",", tag and push the tag:",[87,88,93],"pre",{"className":89,"code":90,"language":91,"meta":92,"style":92},"language-bash shiki shiki-themes github-dark-dimmed github-light","git tag -a v1.2.0 -m \"release: 1.2.0\"\ngit push origin main v1.2.0\n","bash","",[36,94,95,121],{"__ignoreMap":92},[96,97,100,104,108,112,115,118],"span",{"class":98,"line":99},"line",1,[96,101,103],{"class":102},"sqRhv","git",[96,105,107],{"class":106},"sXfbr"," tag",[96,109,111],{"class":110},"swcJU"," -a",[96,113,114],{"class":106}," v1.2.0",[96,116,117],{"class":110}," -m",[96,119,120],{"class":106}," \"release: 1.2.0\"\n",[96,122,124,126,129,132,135],{"class":98,"line":123},2,[96,125,103],{"class":102},[96,127,128],{"class":106}," push",[96,130,131],{"class":106}," origin",[96,133,134],{"class":106}," main",[96,136,137],{"class":106}," v1.2.0\n",[14,139,140,141,22],{},"The tag starts ",[18,142,146],{"href":143,"rel":144},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Fblob\u002Fmain\u002F.github\u002Fworkflows\u002Frelease-images.yml",[145],"nofollow",[36,147,148],{},".github\u002Fworkflows\u002Frelease-images.yml",[24,150,152],{"id":151},"what-the-workflow-does","What the workflow does",[29,154,155,162,172,218],{},[32,156,157,161],{},[158,159,160],"strong",{},"Version and tags."," Checks the tag against the version in the code, works out\nwhich tags to publish, and runs the tests of the secret scanner and of the\nlicence check.",[32,163,164,167,168,171],{},[158,165,166],{},"Tests."," The CI workflow (",[36,169,170],{},"ci.yml",", below) on the tag's commit: no image is built\nunless the engine's and the gateway's suites and the interface's type check pass.",[32,173,174,177,178],{},[158,175,176],{},"One job per image and architecture"," (backend, gateway, frontend × amd64,\narm64; arm64 runs on GitHub's native ARM runners, no emulation). Each job:\n",[179,180,181,184,191,197,207],"ul",{},[32,182,183],{},"builds the image from scratch on fresh base images;",[32,185,186,187,190],{},"smoke-tests it: runs as uid 10001, has the right architecture and version\nlabel; the engine has no chDB and no SQL Server driver, the gateway imports its migrations, the\ninterface answers ",[36,188,189],{},"\u002Flogin"," with a read-only filesystem;",[32,192,193,196],{},[158,194,195],{},"checks the licences"," of the packages inside it (below);",[32,198,199,200,63,203,206],{},"saves it with ",[36,201,202],{},"docker save",[158,204,205],{},"scans it for secrets",", twice (below);",[32,208,209,210,213,214,217],{},"only then logs in to Docker Hub and pushes ",[36,211,212],{},"1.2.0-amd64"," or ",[36,215,216],{},"1.2.0-arm64",". The\nDocker Hub token never enters a job whose scans have not passed.",[32,219,220,223,224,227,228,231,232,235,236,239,240,243],{},[158,221,222],{},"Multi-architecture tags."," Joins the two architectures under ",[36,225,226],{},"1.2.0",", under\n",[36,229,230],{},"1.2"," when it is the highest ",[36,233,234],{},"1.2.x",", and under ",[36,237,238],{},"latest"," when it is the highest\nstable release: moving tags only move forward. A pre-release tag\n(",[36,241,242],{},"v1.2.0-rc.1",") publishes its own name only.",[14,245,246,249,250,254,255,258,259,262],{},[158,247,248],{},"Dry run:"," Actions → ",[251,252,253],"em",{},"Release images"," → ",[251,256,257],{},"Run workflow",", give an existing tag and\nleave ",[251,260,261],{},"publish"," off: everything runs except the pushes.",[24,264,266],{"id":265},"the-tests-ci","The tests (CI)",[14,268,269,272],{},[36,270,271],{},".github\u002Fworkflows\u002Fci.yml"," runs on every push, to any branch, and every pull request, and the\nrelease calls it on the tagged commit:",[179,274,275,281,287],{},[32,276,277,280],{},[158,278,279],{},"engine",": the backend's suite in its development image (pytest, chDB), next to a real\nClickHouse, PostgreSQL, MinIO and Valkey started for the run;",[32,282,283,286],{},[158,284,285],{},"gateway",": the gateway's suite, with a real PostgreSQL for the migrations, the queue\nlocks and the retention;",[32,288,289,292,293,296,297,300],{},[158,290,291],{},"interface",": ",[36,294,295],{},"npm ci"," and the type check (",[36,298,299],{},"vue-tsc",", pinned).",[14,302,303,304,307,308,311,312,315,316,319],{},"A test that skips because its server is missing would pass unseen, so any skip fails the\nrun except those the CI cannot avoid (live BigQuery, Oracle, SQL Server):\n",[36,305,306],{},".github\u002Fscripts\u002Fci_skips.py",". The same script runs the suites on a laptop, with the\ncommands at the top of ",[36,309,310],{},".github\u002Fscripts\u002Fci_tests.sh",". The end-to-end oracles of ",[36,313,314],{},"e2e\u002F","\nneed a whole stack and stay a step of the release (",[36,317,318],{},"e2e\u002Fdbt_export\u002Frun_all.py all",").",[24,321,323],{"id":322},"the-licence-check","The licence check",[14,325,326,327,330,331,334,335,338,339,342,343,346],{},"Tabularia is AGPL-3.0: an image can carry only packages whose licence allows it.\n",[36,328,329],{},".github\u002Fscripts\u002Fimage_licence_check.py"," runs the built image, lists the Python\ndistributions inside it (backend, gateway) or the npm packages next to the server\nbundle (frontend), reads the licence each declares, and stops the release on one that\nis proprietary or incompatible: Proprietary, SSPL, BUSL, Commons Clause, Elastic\nLicense, non-commercial, GPL-2.0-only without an alternative, npm's ",[36,332,333],{},"UNLICENSED"," or\n",[36,336,337],{},"SEE LICENSE IN",". An SPDX expression with an acceptable alternative passes\n(",[36,340,341],{},"BSD-3-Clause OR GPL-2.0","); a package that declares nothing is listed as a notice.\nPackages reviewed by hand go in its ",[36,344,345],{},"REVIEWED"," list with the exact licence they\ndeclare, so that a change of licence is checked again.",[14,348,349,350,353,354,357,358,362,363,366],{},"It exists because of Microsoft's SQL Server driver: ",[36,351,352],{},"mssql-python"," depends on\n",[36,355,356],{},"mssql-python-odbc",", Microsoft's proprietary ODBC driver, so the backend is built\nwithout it and whoever needs it adds it (",[18,359,361],{"href":360},"\u002Fdocs\u002Fsqlserver","sqlserver.md","). An image\nbuilt with ",[36,364,365],{},"WITH_MSSQL=true"," fails this check, by design.",[87,368,370],{"className":89,"code":369,"language":91,"meta":92,"style":92},"python3 .github\u002Fscripts\u002Ftest_image_licence_check.py\npython3 .github\u002Fscripts\u002Fimage_licence_check.py check --kind python \u003Cbackend or gateway image>\npython3 .github\u002Fscripts\u002Fimage_licence_check.py check --kind npm \u003Cfrontend image>\n",[36,371,372,380,419],{"__ignoreMap":92},[96,373,374,377],{"class":98,"line":99},[96,375,376],{"class":102},"python3",[96,378,379],{"class":106}," .github\u002Fscripts\u002Ftest_image_licence_check.py\n",[96,381,382,384,387,390,393,396,400,403,406,409,412,416],{"class":98,"line":123},[96,383,376],{"class":102},[96,385,386],{"class":106}," .github\u002Fscripts\u002Fimage_licence_check.py",[96,388,389],{"class":106}," check",[96,391,392],{"class":110}," --kind",[96,394,395],{"class":106}," python",[96,397,399],{"class":398},"s6PUj"," \u003C",[96,401,402],{"class":106},"backend",[96,404,405],{"class":106}," or",[96,407,408],{"class":106}," gateway",[96,410,411],{"class":106}," imag",[96,413,415],{"class":414},"ssh_m","e",[96,417,418],{"class":398},">\n",[96,420,422,424,426,428,430,433,435,438,440,442],{"class":98,"line":421},3,[96,423,376],{"class":102},[96,425,386],{"class":106},[96,427,389],{"class":106},[96,429,392],{"class":110},[96,431,432],{"class":106}," npm",[96,434,399],{"class":398},[96,436,437],{"class":106},"frontend",[96,439,411],{"class":106},[96,441,415],{"class":414},[96,443,418],{"class":398},[24,445,447],{"id":446},"the-two-secret-scans","The two secret scans",[14,449,450,453,454,457],{},[158,451,452],{},"Your secrets, by fingerprint"," (",[36,455,456],{},".github\u002Fscripts\u002Fimage_secret_scan.py","). CI must not\nhold the maintainer's credentials, so it holds fingerprints instead: for each secret\nits length, 32 bits of its first 8 bytes and its SHA-256. The scanner opens the image\none layer at a time, including files a later layer deletes, the image config with its\nenvironment and history, and zip, gzip and tar archives inside it (three levels deep),\nand flags any byte sequence whose fingerprint matches. Nothing is printed but the name\nof the secret and where it was found.",[14,459,460,463,464,467],{},[158,461,462],{},"Anybody's live credentials"," (TruffleHog, pinned by digest). Any credential it can\nverify as live stops the release. For cloud providers and the services this project\nuses (AWS, Google, Azure, Scaleway, GitHub, Docker Hub, OpenAI, Anthropic…) a\ncredential it ",[251,465,466],{},"cannot"," verify, for instance because of a network error, stops it too.\nThe log shows only the kind of credential and the file: the public Actions log never\ncarries the value.",[24,469,471],{"id":470},"before-pushing-the-local-guard","Before pushing: the local guard",[14,473,474,475,478],{},"The scans above stop a secret on its way to Docker Hub, but a secret committed by\nmistake is public as soon as the commit reaches GitHub. The pre-push guard\n(",[36,476,477],{},".github\u002Fscripts\u002Fpush_guard.py",") stops it earlier, on the maintainer's machine, on the\ncommits the remote does not have yet:",[179,480,481,487],{},[32,482,483,486],{},[158,484,485],{},"your secrets, by value",", in the lines and commit messages the push adds, binary\nfiles included. The values stay on the machine, so values from 10 characters are\nchecked, shorter than the fingerprints allow;",[32,488,489,492],{},[158,490,491],{},"TruffleHog"," on the same commits, with the workflow's rules.",[14,494,495],{},"A secret the remote already has does not stop the push, so that removing it stays\npossible; it is reported. Install it once per clone and tell it where your secrets are\n(local git config, never committed):",[87,497,499],{"className":89,"code":498,"language":91,"meta":92,"style":92},"printf '#!\u002Fbin\u002Fsh\\nexec python3 \"$(git rev-parse --show-toplevel)\u002F.github\u002Fscripts\u002Fpush_guard.py\" \"$@\"\\n' \\\n    > .git\u002Fhooks\u002Fpre-push && chmod +x .git\u002Fhooks\u002Fpre-push\ngit config --add tabularia.secretFiles infrastructure\u002F.env\ngit config --add tabularia.secretFiles ~\u002Fpath\u002Fto\u002Fservice-account.json\ngit config tabularia.secretStandard true      # cloud tools' credential files and SSH keys\n",[36,500,501,513,533,549,563],{"__ignoreMap":92},[96,502,503,506,509],{"class":98,"line":99},[96,504,505],{"class":110},"printf",[96,507,508],{"class":106}," '#!\u002Fbin\u002Fsh\\nexec python3 \"$(git rev-parse --show-toplevel)\u002F.github\u002Fscripts\u002Fpush_guard.py\" \"$@\"\\n'",[96,510,512],{"class":511},"sxsTv"," \\\n",[96,514,515,518,521,524,527,530],{"class":98,"line":123},[96,516,517],{"class":398},"    >",[96,519,520],{"class":106}," .git\u002Fhooks\u002Fpre-push",[96,522,523],{"class":414}," && ",[96,525,526],{"class":102},"chmod",[96,528,529],{"class":106}," +x",[96,531,532],{"class":106}," .git\u002Fhooks\u002Fpre-push\n",[96,534,535,537,540,543,546],{"class":98,"line":421},[96,536,103],{"class":102},[96,538,539],{"class":106}," config",[96,541,542],{"class":110}," --add",[96,544,545],{"class":106}," tabularia.secretFiles",[96,547,548],{"class":106}," infrastructure\u002F.env\n",[96,550,552,554,556,558,560],{"class":98,"line":551},4,[96,553,103],{"class":102},[96,555,539],{"class":106},[96,557,542],{"class":110},[96,559,545],{"class":106},[96,561,562],{"class":106}," ~\u002Fpath\u002Fto\u002Fservice-account.json\n",[96,564,566,568,570,573,576],{"class":98,"line":565},5,[96,567,103],{"class":102},[96,569,539],{"class":106},[96,571,572],{"class":106}," tabularia.secretStandard",[96,574,575],{"class":110}," true",[96,577,579],{"class":578},"sgHix","      # cloud tools' credential files and SSH keys\n",[14,581,582,583,586],{},"It needs Docker for TruffleHog; ",[36,584,585],{},"git push --no-verify"," skips it in an emergency.",[24,588,590],{"id":589},"repository-secrets","Repository secrets",[592,593,594,610],"table",{},[595,596,597],"thead",{},[598,599,600,604,607],"tr",{},[601,602,603],"th",{},"Secret",[601,605,606],{},"What",[601,608,609],{},"Renewal",[611,612,613,634],"tbody",{},[598,614,615,621,631],{},[616,617,618],"td",{},[36,619,620],{},"DOCKERHUB_TOKEN",[616,622,623,624,627,628],{},"Docker Hub personal access token of ",[36,625,626],{},"jumpasaservice",", permission ",[251,629,630],{},"Read & Write",[616,632,633],{},"it expires (one year): generate a new one in Docker Hub → Account settings → Personal access tokens",[598,635,636,641,644],{},[616,637,638],{},[36,639,640],{},"SECRET_FINGERPRINTS",[616,642,643],{},"fingerprints of the maintainer's secrets",[616,645,646],{},"whenever a secret changes or a new one appears",[14,648,649,650,652],{},"Without ",[36,651,640],{}," the scan cannot certify anything and the release stops.",[654,655,657],"h3",{"id":656},"generating-the-fingerprints","Generating the fingerprints",[14,659,660],{},"On the maintainer's machine, never in CI:",[87,662,664],{"className":89,"code":663,"language":91,"meta":92,"style":92},"python3 .github\u002Fscripts\u002Fimage_secret_scan.py fingerprint --standard -o \u002Ftmp\u002Ffingerprints.txt \\\n    infrastructure\u002F.env infrastructure\u002Fproduction\u002F.env.prodtest path\u002Fto\u002Fservice-account.json\n",[36,665,666,687],{"__ignoreMap":92},[96,667,668,670,673,676,679,682,685],{"class":98,"line":99},[96,669,376],{"class":102},[96,671,672],{"class":106}," .github\u002Fscripts\u002Fimage_secret_scan.py",[96,674,675],{"class":106}," fingerprint",[96,677,678],{"class":110}," --standard",[96,680,681],{"class":110}," -o",[96,683,684],{"class":106}," \u002Ftmp\u002Ffingerprints.txt",[96,686,512],{"class":511},[96,688,689,692,695],{"class":98,"line":123},[96,690,691],{"class":106},"    infrastructure\u002F.env",[96,693,694],{"class":106}," infrastructure\u002Fproduction\u002F.env.prodtest",[96,696,697],{"class":106}," path\u002Fto\u002Fservice-account.json\n",[14,699,700,703,704,707,708,39,711,43,714,43,717,43,720,723,724,727,728,731,732,43,735,43,738,43,741,39,744,43,747,43,750,753],{},[36,701,702],{},"--standard"," adds the credential files of cloud and developer tools when present\n(",[36,705,706],{},"~\u002F.aws\u002Fcredentials",", gcloud application-default credentials, ",[36,709,710],{},"~\u002F.config\u002Fscw",[36,712,713],{},"~\u002F.docker\u002Fconfig.json",[36,715,716],{},"~\u002F.kube\u002Fconfig",[36,718,719],{},"~\u002F.netrc",[36,721,722],{},"~\u002F.git-credentials",", the GitHub\nCLI, OCI, doctl, hcloud) and the private SSH keys in ",[36,725,726],{},"~\u002F.ssh",". From ",[36,729,730],{},".env","-style and YAML\nfiles it takes the values whose name says secret (",[36,733,734],{},"KEY",[36,736,737],{},"SECRET",[36,739,740],{},"PASSWORD",[36,742,743],{},"TOKEN",[36,745,746],{},"CREDENTIAL",[36,748,749],{},"DSN",[36,751,752],{},"PRIVATE","), passwords inside URLs, and decodes base64 values that\nhide a service-account JSON or a PEM key; a PEM key is fingerprinted line by line.",[14,755,756,757,759],{},"Paste the file into the secret (Settings → Secrets and variables → Actions →\n",[36,758,640],{},") and delete it. The command reports, by name only:",[179,761,762,773],{},[32,763,764,765,768,769,772],{},"values ",[158,766,767],{},"already written in the repository",", left out because they are public —\nusually the defaults of a ",[36,770,771],{},".env.example","; a real secret in that list is already\npublic and must be rotated;",[32,774,764,775,778],{},[158,776,777],{},"shorter than 16 characters",", not fingerprinted: a fingerprint would let\nanyone recover them by brute force. They are still checked against the repository,\nwhich is the one way a secret reaches an image built in CI.",[24,780,782],{"id":781},"the-website-github-pages","The website (GitHub Pages)",[14,784,785,786,789,790,793,794,798,799,801,802,43,804,43,807,810,811,814,815,22],{},"The presentation site and these docs are built from ",[36,787,788],{},"website\u002F"," by\n",[36,791,792],{},".github\u002Fworkflows\u002Fwebsite.yml"," and published at\n",[18,795,796],{"href":796,"rel":797},"https:\u002F\u002Ftabularia.org\u002F",[145],". It runs on every push to ",[36,800,84],{}," that\ntouches ",[36,803,788],{},[36,805,806],{},"docs\u002F",[36,808,809],{},"brand\u002F"," or the README, and by hand from Actions → Website.\nA pull request gets the same build and checks (unit tests, ",[36,812,813],{},"nuxi generate",", the link\ncheck, which also verifies that every GitHub link names a file in the repository) and no\ndeploy. How to run it locally is in ",[18,816,819],{"href":817,"rel":818},"https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia\u002Fblob\u002Fmain\u002Fwebsite\u002FREADME.md",[145],"website\u002FREADME.md",[14,821,822,825,826,829,830,833,834,837,838,841],{},[158,823,824],{},"Once, before the first deploy",", a repository admin sets Settings → Pages → Build and\ndeployment → ",[158,827,828],{},"Source"," to ",[158,831,832],{},"GitHub Actions",". Until then the old branch build keeps\npublishing (Jekyll over the repository), and the workflow's deploy job fails. After the\nswitch Jekyll no longer runs: the ",[36,835,836],{},"raw"," \u002F ",[36,839,840],{},"endraw"," comments at the top and bottom of some\nMarkdown pages, and the push guard's Liquid check, are no longer needed for Pages; they\nare harmless and can stay.",[14,843,844,847,848,851,852,855,856,859,860,863,864,867,868,871,872,875,876,879,880,883,884,887,888,891,892,895],{},[158,845,846],{},"The domain."," ",[36,849,850],{},"tabularia.org"," (registered with Cloudflare, whose DNS serves it) is the\nrepository's Pages custom domain, set in Settings → Pages → Custom domain with\n",[158,853,854],{},"Enforce HTTPS","; the GitHub address redirects there. Its DNS records: ",[36,857,858],{},"A"," for the apex\nto 185.199.108.153, 185.199.109.153, 185.199.110.153 and 185.199.111.153, ",[36,861,862],{},"AAAA"," to\n2606:50c0:8000::153 to 2606:50c0:8003::153, and ",[36,865,866],{},"www"," as a ",[36,869,870],{},"CNAME"," to\n",[36,873,874],{},"jumpasaservice.github.io","; all of them ",[158,877,878],{},"DNS only"," (the grey cloud), or GitHub cannot\nissue the certificate. The organisation's verified domain (a ",[36,881,882],{},"TXT"," record\n",[36,885,886],{},"_github-pages-challenge-jumpasaservice",") keeps anyone else's Pages from claiming it. The\nsite is built for its root: ",[36,889,890],{},"SITE_URL"," in the workflow, the one value to change if the\ndomain ever changes. Visits are counted with Cloudflare Web Analytics (no cookies, no\npersonal data) when the repository variable ",[36,893,894],{},"SITE_ANALYTICS_TOKEN"," holds the site's token;\nwithout it the pages load no analytics.",[14,897,898,899,901],{},"The site describes the current ",[36,900,84],{}," without naming a version: publish a release first\nwhen the product changed since the last one.",[24,903,905],{"id":904},"limits","Limits",[179,907,908,911,914],{},[32,909,910],{},"Archives nested deeper than three levels, or compressed with something other than\nzip or gzip, are scanned as raw bytes only.",[32,912,913],{},"A secret stored in the image in a different encoding from the one it has on the\nmaintainer's machine (base64 of a raw value, for instance) does not match its\nfingerprint; TruffleHog still recognises the usual cloud key formats.",[32,915,916,917,43,919,921],{},"The single-architecture tags (",[36,918,212],{},[36,920,216],{},") stay on Docker Hub\nnext to the joined ones.",[923,924,925],"style",{},"html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}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);}html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .sxsTv, html code.shiki .sxsTv{--shiki-default:#F47067;--shiki-light:#005CC5}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}",{"title":92,"searchDepth":123,"depth":421,"links":927},[928,929,930,931,932,933,934,937,938],{"id":26,"depth":123,"text":27},{"id":151,"depth":123,"text":152},{"id":265,"depth":123,"text":266},{"id":322,"depth":123,"text":323},{"id":446,"depth":123,"text":447},{"id":470,"depth":123,"text":471},{"id":589,"depth":123,"text":590,"children":935},[936],{"id":656,"depth":421,"text":657},{"id":781,"depth":123,"text":782},{"id":904,"depth":123,"text":905},"How a release goes from a tag to Docker Hub. Written for whoever maintains the\nproject; installing or upgrading is in deploy\u002Frelease-checklist.md.","md",{"source":942},"docs\u002Frelease-process.md",true,"\u002Fdocs\u002Frelease-process",{"title":5,"description":939},"docs\u002Frelease-process","29zu68uFoM_Y9vhQ_NhL7crg4exKudhlXbQqANVoANw",[949,968,1024,1056,1087,1116,1142,1167],{"title":950,"icon":951,"intro":952,"items":953},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[954,956,959,962,965],{"path":955,"title":950},"\u002Fdocs\u002Fgetting-started",{"path":957,"title":958},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":960,"title":961},"\u002Fdocs\u002Fdeploy\u002Fminikube","Try it on your computer (minikube)",{"path":963,"title":964},"\u002Fdocs\u002Fconcepts","Concepts",{"path":966,"title":967},"\u002Fdocs\u002Foverview","Overview (README)",{"title":969,"icon":970,"intro":971,"items":972},"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.",[973,976,979,982,985,988,991,994,997,1000,1003,1006,1009,1012,1015,1018,1021],{"path":974,"title":975},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":977,"title":978},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":980,"title":981},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":983,"title":984},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":986,"title":987},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":989,"title":990},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":992,"title":993},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":995,"title":996},"\u002Fdocs\u002Fscripting","Python and R",{"path":998,"title":999},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":1001,"title":1002},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":1004,"title":1005},"\u002Fdocs\u002Fcontainer-node","Docker images in flows",{"path":1007,"title":1008},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":1010,"title":1011},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":1013,"title":1014},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":1016,"title":1017},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":1019,"title":1020},"\u002Fdocs\u002Fcapabilities\u002Fengines","Engines",{"path":1022,"title":1023},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":1025,"icon":1026,"intro":1027,"items":1028},"Sources","sources","Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[1029,1032,1035,1038,1041,1044,1047,1050,1053],{"path":1030,"title":1031},"\u002Fdocs\u002Fsources","All sources",{"path":1033,"title":1034},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":1036,"title":1037},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":1039,"title":1040},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":1042,"title":1043},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":1045,"title":1046},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":1048,"title":1049},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":1051,"title":1052},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":1054,"title":1055},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":1057,"icon":1058,"intro":1059,"items":1060},"Connections","connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[1061,1064,1067,1069,1072,1074,1075,1077,1078,1081,1084],{"path":1062,"title":1063},"\u002Fdocs\u002Fconnections","All connections",{"path":1065,"title":1066},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":360,"title":1068},"Microsoft SQL Server driver",{"path":1070,"title":1071},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":1042,"title":1073},"SFTP servers",{"path":1045,"title":1046},{"path":1048,"title":1076},"SharePoint",{"path":1051,"title":1052},{"path":1079,"title":1080},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":1082,"title":1083},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":1085,"title":1086},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":1088,"icon":1089,"intro":1090,"items":1091},"Integrations","integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[1092,1095,1098,1101,1104,1107,1110,1113],{"path":1093,"title":1094},"\u002Fdocs\u002Fintegrations","All integrations",{"path":1096,"title":1097},"\u002Fdocs\u002Fintegrations\u002Fai","AI: where it is used",{"path":1099,"title":1100},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":1102,"title":1103},"\u002Fdocs\u002Fdbt-export","Export to dbt",{"path":1105,"title":1106},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":1108,"title":1109},"\u002Fdocs\u002Fsso-keycloak","Single sign-on with Keycloak",{"path":1111,"title":1112},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":1114,"title":1115},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":1117,"icon":1118,"intro":1119,"items":1120},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[1121,1124,1127,1130,1133,1136,1139],{"path":1122,"title":1123},"\u002Fdocs\u002Fgovernance","Overview",{"path":1125,"title":1126},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":1128,"title":1129},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":1131,"title":1132},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":1134,"title":1135},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",{"path":1137,"title":1138},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example on a laptop",{"path":1140,"title":1141},"\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user","A ClickHouse user for the AI assistant",{"title":1143,"icon":1144,"intro":1145,"items":1146},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[1147,1150,1153,1156,1158,1161,1164],{"path":1148,"title":1149},"\u002Fdocs\u002Fdeploy\u002Fkubernetes","Kubernetes and Helm",{"path":1151,"title":1152},"\u002Fdocs\u002Fdeploy\u002Fkind","A company-like cluster on one computer (kind)",{"path":1154,"title":1155},"\u002Fdocs\u002Fdeploy\u002Fupgrading","Upgrading, backups and restore",{"path":20,"title":1157},"Install and upgrade checklist",{"path":1159,"title":1160},"\u002Fdocs\u002Fconfiguration","Configuration reference",{"path":1162,"title":1163},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":1165,"title":1166},"\u002Fdocs\u002Fdatabase-migrations","Database migrations",{"title":1168,"icon":1169,"intro":1170,"items":1171},"Reference","reference","Release process and the remaining reference pages.",[1172],{"path":944,"title":5},1793656372349]