[{"data":1,"prerenderedAt":5365},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fdeploy\u002Fkind":3,"docs-nav":5143},{"id":4,"title":5,"body":6,"description":16,"extension":5136,"meta":5137,"navigation":752,"path":5139,"seo":5140,"stem":5141,"__hash__":5142},"docs\u002Fdocs\u002Fdeploy\u002Fkind.md","A company-like cluster on one computer (kind)",{"type":7,"value":8,"toc":5110},"minimark",[9,13,17,48,57,60,65,128,146,150,153,262,272,276,354,364,389,392,456,460,463,673,679,683,686,775,779,782,946,953,1074,1081,1151,1154,1235,1256,1267,1331,1340,1344,1354,1575,1580,1683,1692,1696,1703,1861,1871,2071,2075,2082,2183,2186,2193,2197,2396,2411,2415,2418,2451,2524,2536,2543,2644,2647,2651,2764,2768,2771,2774,2836,2843,3146,3149,3206,3210,3213,3292,3295,3381,3398,3404,3408,3415,3476,3493,3537,3541,3544,3598,3606,3695,3698,3723,3756,3759,3763,3770,3776,3845,3852,3913,3919,3928,4142,4165,4169,4179,4182,4337,4354,4360,4495,4506,4668,4672,4799,4810,4816,4820,4831,4835,4878,4881,4885,5106],[10,11,5],"h1",{"id":12},"a-company-like-cluster-on-one-computer-kind",[14,15,16],"p",{},"This guide builds, on one computer, a Kubernetes cluster that behaves like a company's,\nand installs Tabularia on it with the same Helm chart and the same settings you would\nuse there:",[18,19,20,24,27,30,33,36,39,42,45],"ul",{},[21,22,23],"li",{},"three nodes, so pods are spread and images are pulled by more than one node;",[21,25,26],{},"network policies and Pod Security that are enforced, not just written down, with\nCalico as the network plugin (or Cilium);",[21,28,29],{},"a private image registry with TLS, signed by a certificate authority of your own (a\n\"company CA\"), and a password;",[21,31,32],{},"nodes that trust that authority, as a platform team sets them up;",[21,34,35],{},"the scripts and the Container nodes kept away from Tabularia's own services by\nnetwork policies;",[21,37,38],{},"PostgreSQL, Valkey and object storage inside the cluster, in a namespace of their own;\nor, as most companies do, the object storage of your cloud (step 7) and an external\nClickHouse for the flows (step 12);",[21,40,41],{},"Tabularia's images copied into the private registry, and Python and R scripts built\nand run through it;",[21,43,44],{},"images of your own run as steps of a flow (the Container node), from that registry;",[21,46,47],{},"if you like, the sample data of the Docker Compose stack, to play with (step 13).",[14,49,50,51,56],{},"It is meant for people who know a terminal and want to try Tabularia as it will run in\nproduction, or test a change of the chart before a customer does. To simply try\nTabularia, ",[52,53,55],"a",{"href":54},"\u002Fdocs\u002Fdeploy\u002Fminikube","the minikube guide"," is shorter and needs no Kubernetes knowledge.",[14,58,59],{},"It was written by running every command below, in this order, with kind 0.33\n(Kubernetes 1.37, containerd 2.3) and Calico 3.30.",[61,62,64],"h2",{"id":63},"what-you-need","What you need",[18,66,67,74,93],{},[21,68,69,73],{},[70,71,72],"strong",{},"A computer with 16 GB of memory at least"," (32 GB to build R environments\ncomfortably), 8 cores and 60 GB of free disk.",[21,75,76,79,80,83,84,87,88,92],{},[70,77,78],{},"Linux",", or ",[70,81,82],{},"macOS"," with Docker Desktop, or ",[70,85,86],{},"Windows"," inside WSL 2 (an Ubuntu\nthat runs on Windows: ",[52,89,91],{"href":90},"#on-windows","On Windows"," below sets it up).",[21,94,95,101,102,107,108,113,114,101,119,123,124,127],{},[52,96,100],{"href":97,"rel":98},"https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Finstall\u002F",[99],"nofollow","Docker",", ",[52,103,106],{"href":104,"rel":105},"https:\u002F\u002Fkind.sigs.k8s.io\u002Fdocs\u002Fuser\u002Fquick-start\u002F#installation",[99],"kind","\n0.27 or later, ",[52,109,112],{"href":110,"rel":111},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Ftools\u002F",[99],"kubectl",",\n",[52,115,118],{"href":116,"rel":117},"https:\u002F\u002Fhelm.sh\u002Fdocs\u002Fintro\u002Finstall\u002F",[99],"Helm",[120,121,122],"code",{},"openssl"," and ",[120,125,126],{},"git",".",[14,129,130,131,134,135,101,138,141,142,145],{},"Run every command in ",[70,132,133],{},"the same terminal",": the steps keep values in shell variables\n(",[120,136,137],{},"$WORK",[120,139,140],{},"$VERSION",", the passwords). If you open a new terminal, run\n",[120,143,144],{},"source \"$WORK\u002Fenv\""," (step 1 explains it) to get them back.",[147,148,91],"h3",{"id":149},"on-windows",[14,151,152],{},"The guide runs in an Ubuntu inside Windows (WSL 2), where every command works as on\nLinux. Set it up once:",[154,155,156,166,222,238,248],"ol",{},[21,157,158,161,162,165],{},[70,159,160],{},"Install WSL 2 with Ubuntu."," In a PowerShell opened as administrator:\n",[120,163,164],{},"wsl --install -d Ubuntu",", then restart the computer. Ubuntu asks for a user name and\na password the first time it opens.",[21,167,168,171,172,175,176,214,217,218,221],{},[70,169,170],{},"Give it memory and processors."," WSL takes half of the computer's memory by\ndefault. In PowerShell, ",[120,173,174],{},"notepad \"$env:USERPROFILE\\.wslconfig\"",", write",[177,178,183],"pre",{"className":179,"code":180,"language":181,"meta":182,"style":182},"language-ini shiki shiki-themes github-dark-dimmed github-light","[wsl2]\nmemory=24GB\nprocessors=8\n","ini","",[120,184,185,194,205],{"__ignoreMap":182},[186,187,190],"span",{"class":188,"line":189},"line",1,[186,191,193],{"class":192},"sqRhv","[wsl2]\n",[186,195,197,201],{"class":188,"line":196},2,[186,198,200],{"class":199},"s6PUj","memory",[186,202,204],{"class":203},"ssh_m","=24GB\n",[186,206,208,211],{"class":188,"line":207},3,[186,209,210],{"class":199},"processors",[186,212,213],{"class":203},"=8\n",[215,216],"br",{},"(16 GB at least; on a 32 GB computer, 24 leaves Windows enough), save, run\n",[120,219,220],{},"wsl --shutdown",", and open Ubuntu again.",[21,223,224,227,228,233,234,237],{},[70,225,226],{},"Install Docker inside Ubuntu",": Docker Engine, following\n",[52,229,232],{"href":230,"rel":231},"https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Finstall\u002Fubuntu\u002F",[99],"Docker's guide for Ubuntu",", then\n",[120,235,236],{},"sudo usermod -aG docker $USER"," and open Ubuntu again. Docker Engine inside Ubuntu\nbehaves as on Linux, ports and mounted folders included, which is what this guide\nwas run on. Docker Desktop with its WSL integration can work too, but its ports\npass through one more machine.",[21,239,240,243,244,247],{},[70,241,242],{},"Install the tools inside Ubuntu",", in their Linux versions:\n",[120,245,246],{},"sudo apt install -y openssl git",", then kind, kubectl and Helm from the links above\n(the Linux instructions).",[21,249,250,253,254,257,258,261],{},[70,251,252],{},"Work in Ubuntu's home"," (",[120,255,256],{},"cd ~","), not in ",[120,259,260],{},"\u002Fmnt\u002Fc\u002F…",": the Windows disk is much\nslower from Ubuntu, and it does not keep the permissions that protect the files of\npasswords this guide writes.",[14,263,264,265,271],{},"Run the guide in the Ubuntu terminal, unchanged. The browser of Windows opens\nTabularia at ",[70,266,267],{},[52,268,269],{"href":269,"rel":270},"http:\u002F\u002Flocalhost:8080",[99],": WSL 2 hands the ports it listens on over to\nWindows.",[61,273,275],{"id":274},"1-get-tabularia-and-a-working-folder","1. Get Tabularia and a working folder",[177,277,281],{"className":278,"code":279,"language":280,"meta":182,"style":182},"language-bash shiki shiki-themes github-dark-dimmed github-light","git clone --branch v1.3.0 https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia.git\ncd Tabularia\nexport VERSION=1.3.0 WORK=\"$HOME\u002Ftabularia-kind\"\nmkdir -p \"$WORK\"\n","bash",[120,282,283,301,309,337],{"__ignoreMap":182},[186,284,285,287,291,295,298],{"class":188,"line":189},[186,286,126],{"class":192},[186,288,290],{"class":289},"sXfbr"," clone",[186,292,294],{"class":293},"swcJU"," --branch",[186,296,297],{"class":289}," v1.3.0",[186,299,300],{"class":289}," https:\u002F\u002Fgithub.com\u002FJumpAsAService\u002FTabularia.git\n",[186,302,303,306],{"class":188,"line":196},[186,304,305],{"class":293},"cd",[186,307,308],{"class":289}," Tabularia\n",[186,310,311,314,317,320,323,326,328,331,334],{"class":188,"line":207},[186,312,313],{"class":199},"export",[186,315,316],{"class":203}," VERSION",[186,318,319],{"class":199},"=",[186,321,322],{"class":293},"1.3.0",[186,324,325],{"class":203}," WORK",[186,327,319],{"class":199},[186,329,330],{"class":289},"\"",[186,332,333],{"class":203},"$HOME",[186,335,336],{"class":289},"\u002Ftabularia-kind\"\n",[186,338,340,343,346,349,351],{"class":188,"line":339},4,[186,341,342],{"class":192},"mkdir",[186,344,345],{"class":293}," -p",[186,347,348],{"class":289}," \"",[186,350,137],{"class":203},[186,352,353],{"class":289},"\"\n",[14,355,356,357,360,361,363],{},"Every later command runs from the ",[120,358,359],{},"Tabularia"," folder. ",[120,362,137],{}," holds what the cluster\nneeds outside of it: certificates, passwords, the settings. Choose now the first\nadministrator of Tabularia:",[177,365,367],{"className":278,"code":366,"language":280,"meta":182,"style":182},"export ADMIN_EMAIL='you@example.com' ADMIN_PASSWORD='choose-a-long-password'\n",[120,368,369],{"__ignoreMap":182},[186,370,371,373,376,378,381,384,386],{"class":188,"line":189},[186,372,313],{"class":199},[186,374,375],{"class":203}," ADMIN_EMAIL",[186,377,319],{"class":199},[186,379,380],{"class":289},"'you@example.com'",[186,382,383],{"class":203}," ADMIN_PASSWORD",[186,385,319],{"class":199},[186,387,388],{"class":289},"'choose-a-long-password'\n",[14,390,391],{},"and keep every value in a file only you can read, so a new terminal finds them:",[177,393,395],{"className":278,"code":394,"language":280,"meta":182,"style":182},"umask 077\nprintf 'export VERSION=%q WORK=%q ADMIN_EMAIL=%q ADMIN_PASSWORD=%q\\n' \\\n  \"$VERSION\" \"$WORK\" \"$ADMIN_EMAIL\" \"$ADMIN_PASSWORD\" > \"$WORK\u002Fenv\"\n",[120,396,397,405,417],{"__ignoreMap":182},[186,398,399,402],{"class":188,"line":189},[186,400,401],{"class":293},"umask",[186,403,404],{"class":293}," 077\n",[186,406,407,410,413],{"class":188,"line":196},[186,408,409],{"class":293},"printf",[186,411,412],{"class":289}," 'export VERSION=%q WORK=%q ADMIN_EMAIL=%q ADMIN_PASSWORD=%q\\n'",[186,414,416],{"class":415},"sxsTv"," \\\n",[186,418,419,422,424,426,428,430,432,434,437,439,441,444,446,449,451,453],{"class":188,"line":207},[186,420,421],{"class":289},"  \"",[186,423,140],{"class":203},[186,425,330],{"class":289},[186,427,348],{"class":289},[186,429,137],{"class":203},[186,431,330],{"class":289},[186,433,348],{"class":289},[186,435,436],{"class":203},"$ADMIN_EMAIL",[186,438,330],{"class":289},[186,440,348],{"class":289},[186,442,443],{"class":203},"$ADMIN_PASSWORD",[186,445,330],{"class":289},[186,447,448],{"class":199}," >",[186,450,348],{"class":289},[186,452,137],{"class":203},[186,454,455],{"class":289},"\u002Fenv\"\n",[61,457,459],{"id":458},"2-a-certificate-authority-and-the-registrys-certificate","2. A certificate authority and the registry's certificate",[14,461,462],{},"The \"company CA\" signs the registry's certificate. In a company it already exists; here\nyou create one:",[177,464,466],{"className":278,"code":465,"language":280,"meta":182,"style":182},"openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj \"\u002FCN=Example Internal CA\" \\\n  -keyout \"$WORK\u002Fca.key\" -out \"$WORK\u002Fca.crt\"\nopenssl req -newkey rsa:2048 -nodes -subj \"\u002FCN=tabularia-registry\" \\\n  -keyout \"$WORK\u002Fregistry.key\" -out \"$WORK\u002Fregistry.csr\"\nprintf 'subjectAltName=DNS:tabularia-registry\\n' > \"$WORK\u002Fsan.ext\"\nopenssl x509 -req -in \"$WORK\u002Fregistry.csr\" -CA \"$WORK\u002Fca.crt\" -CAkey \"$WORK\u002Fca.key\" \\\n  -CAcreateserial -days 365 -extfile \"$WORK\u002Fsan.ext\" -out \"$WORK\u002Fregistry.crt\"\nopenssl verify -CAfile \"$WORK\u002Fca.crt\" \"$WORK\u002Fregistry.crt\"\n",[120,467,468,501,523,542,562,579,621,650],{"__ignoreMap":182},[186,469,470,472,475,478,481,484,487,490,493,496,499],{"class":188,"line":189},[186,471,122],{"class":192},[186,473,474],{"class":289}," req",[186,476,477],{"class":293}," -x509",[186,479,480],{"class":293}," -newkey",[186,482,483],{"class":289}," rsa:2048",[186,485,486],{"class":293}," -nodes",[186,488,489],{"class":293}," -days",[186,491,492],{"class":293}," 365",[186,494,495],{"class":293}," -subj",[186,497,498],{"class":289}," \"\u002FCN=Example Internal CA\"",[186,500,416],{"class":415},[186,502,503,506,508,510,513,516,518,520],{"class":188,"line":196},[186,504,505],{"class":293},"  -keyout",[186,507,348],{"class":289},[186,509,137],{"class":203},[186,511,512],{"class":289},"\u002Fca.key\"",[186,514,515],{"class":293}," -out",[186,517,348],{"class":289},[186,519,137],{"class":203},[186,521,522],{"class":289},"\u002Fca.crt\"\n",[186,524,525,527,529,531,533,535,537,540],{"class":188,"line":207},[186,526,122],{"class":192},[186,528,474],{"class":289},[186,530,480],{"class":293},[186,532,483],{"class":289},[186,534,486],{"class":293},[186,536,495],{"class":293},[186,538,539],{"class":289}," \"\u002FCN=tabularia-registry\"",[186,541,416],{"class":415},[186,543,544,546,548,550,553,555,557,559],{"class":188,"line":339},[186,545,505],{"class":293},[186,547,348],{"class":289},[186,549,137],{"class":203},[186,551,552],{"class":289},"\u002Fregistry.key\"",[186,554,515],{"class":293},[186,556,348],{"class":289},[186,558,137],{"class":203},[186,560,561],{"class":289},"\u002Fregistry.csr\"\n",[186,563,565,567,570,572,574,576],{"class":188,"line":564},5,[186,566,409],{"class":293},[186,568,569],{"class":289}," 'subjectAltName=DNS:tabularia-registry\\n'",[186,571,448],{"class":199},[186,573,348],{"class":289},[186,575,137],{"class":203},[186,577,578],{"class":289},"\u002Fsan.ext\"\n",[186,580,582,584,587,590,593,595,597,600,603,605,607,610,613,615,617,619],{"class":188,"line":581},6,[186,583,122],{"class":192},[186,585,586],{"class":289}," x509",[186,588,589],{"class":293}," -req",[186,591,592],{"class":293}," -in",[186,594,348],{"class":289},[186,596,137],{"class":203},[186,598,599],{"class":289},"\u002Fregistry.csr\"",[186,601,602],{"class":293}," -CA",[186,604,348],{"class":289},[186,606,137],{"class":203},[186,608,609],{"class":289},"\u002Fca.crt\"",[186,611,612],{"class":293}," -CAkey",[186,614,348],{"class":289},[186,616,137],{"class":203},[186,618,512],{"class":289},[186,620,416],{"class":415},[186,622,624,627,629,631,634,636,638,641,643,645,647],{"class":188,"line":623},7,[186,625,626],{"class":293},"  -CAcreateserial",[186,628,489],{"class":293},[186,630,492],{"class":293},[186,632,633],{"class":293}," -extfile",[186,635,348],{"class":289},[186,637,137],{"class":203},[186,639,640],{"class":289},"\u002Fsan.ext\"",[186,642,515],{"class":293},[186,644,348],{"class":289},[186,646,137],{"class":203},[186,648,649],{"class":289},"\u002Fregistry.crt\"\n",[186,651,653,655,658,661,663,665,667,669,671],{"class":188,"line":652},8,[186,654,122],{"class":192},[186,656,657],{"class":289}," verify",[186,659,660],{"class":293}," -CAfile",[186,662,348],{"class":289},[186,664,137],{"class":203},[186,666,609],{"class":289},[186,668,348],{"class":289},[186,670,137],{"class":203},[186,672,649],{"class":289},[14,674,675,676,127],{},"The last command answers ",[120,677,678],{},"registry.crt: OK",[61,680,682],{"id":681},"3-nodes-that-trust-the-authority","3. Nodes that trust the authority",[14,684,685],{},"The nodes' container runtime (containerd) downloads the images, so it must trust the\nregistry's certificate. A platform team installs the authority on its nodes; kind lets\nyou give the nodes a folder of containerd's settings:",[177,687,689],{"className":278,"code":688,"language":280,"meta":182,"style":182},"mkdir -p \"$WORK\u002Fcerts.d\u002Ftabularia-registry:5000\"\ncp \"$WORK\u002Fca.crt\" \"$WORK\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fca.crt\"\ncat > \"$WORK\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fhosts.toml\" \u003C\u003C'EOF'\nserver = \"https:\u002F\u002Ftabularia-registry:5000\"\n\n[host.\"https:\u002F\u002Ftabularia-registry:5000\"]\n  capabilities = [\"pull\", \"resolve\", \"push\"]\n  ca = \"\u002Fetc\u002Fcontainerd\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fca.crt\"\nEOF\n",[120,690,691,704,722,743,748,754,759,764,769],{"__ignoreMap":182},[186,692,693,695,697,699,701],{"class":188,"line":189},[186,694,342],{"class":192},[186,696,345],{"class":293},[186,698,348],{"class":289},[186,700,137],{"class":203},[186,702,703],{"class":289},"\u002Fcerts.d\u002Ftabularia-registry:5000\"\n",[186,705,706,709,711,713,715,717,719],{"class":188,"line":196},[186,707,708],{"class":192},"cp",[186,710,348],{"class":289},[186,712,137],{"class":203},[186,714,609],{"class":289},[186,716,348],{"class":289},[186,718,137],{"class":203},[186,720,721],{"class":289},"\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fca.crt\"\n",[186,723,724,727,729,731,733,736,739],{"class":188,"line":207},[186,725,726],{"class":192},"cat",[186,728,448],{"class":199},[186,730,348],{"class":289},[186,732,137],{"class":203},[186,734,735],{"class":289},"\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fhosts.toml\"",[186,737,738],{"class":199}," \u003C\u003C",[186,740,742],{"class":741},"sfef8","'EOF'\n",[186,744,745],{"class":188,"line":339},[186,746,747],{"class":289},"server = \"https:\u002F\u002Ftabularia-registry:5000\"\n",[186,749,750],{"class":188,"line":564},[186,751,753],{"emptyLinePlaceholder":752},true,"\n",[186,755,756],{"class":188,"line":581},[186,757,758],{"class":289},"[host.\"https:\u002F\u002Ftabularia-registry:5000\"]\n",[186,760,761],{"class":188,"line":623},[186,762,763],{"class":289},"  capabilities = [\"pull\", \"resolve\", \"push\"]\n",[186,765,766],{"class":188,"line":652},[186,767,768],{"class":289},"  ca = \"\u002Fetc\u002Fcontainerd\u002Fcerts.d\u002Ftabularia-registry:5000\u002Fca.crt\"\n",[186,770,772],{"class":188,"line":771},9,[186,773,774],{"class":741},"EOF\n",[61,776,778],{"id":777},"4-the-cluster","4. The cluster",[14,780,781],{},"One control-plane node and two workers. The control plane also receives the browser's\ntraffic: port 8080 of your computer goes to it. kind's own network plugin enforces only\nthe rules on what comes into a pod, so the cluster starts without it, and Calico, a\nnetwork plugin many companies run, takes its place: it enforces the rules on what goes\nout too.",[177,783,785],{"className":278,"code":784,"language":280,"meta":182,"style":182},"cat > \"$WORK\u002Fkind.yaml\" \u003C\u003CEOF\nkind: Cluster\napiVersion: kind.x-k8s.io\u002Fv1alpha4\nname: tabularia\nnetworking:\n  disableDefaultCNI: true\n  podSubnet: 10.244.0.0\u002F16\nnodes:\n  - role: control-plane\n    extraPortMappings:\n      - { containerPort: 80, hostPort: 8080, listenAddress: \"127.0.0.1\" }\n    extraMounts: [{ hostPath: \"$WORK\u002Fcerts.d\", containerPath: \u002Fetc\u002Fcontainerd\u002Fcerts.d }]\n  - role: worker\n    extraMounts: [{ hostPath: \"$WORK\u002Fcerts.d\", containerPath: \u002Fetc\u002Fcontainerd\u002Fcerts.d }]\n  - role: worker\n    extraMounts: [{ hostPath: \"$WORK\u002Fcerts.d\", containerPath: \u002Fetc\u002Fcontainerd\u002Fcerts.d }]\ncontainerdConfigPatches:\n  - |-\n    [plugins.\"io.containerd.cri.v1.images\".registry]\n      config_path = \"\u002Fetc\u002Fcontainerd\u002Fcerts.d\"\nEOF\nkind create cluster --config \"$WORK\u002Fkind.yaml\"\n",[120,786,787,804,809,814,819,824,829,834,839,844,850,856,867,873,882,887,896,902,908,914,920,925],{"__ignoreMap":182},[186,788,789,791,793,795,797,800,802],{"class":188,"line":189},[186,790,726],{"class":192},[186,792,448],{"class":199},[186,794,348],{"class":289},[186,796,137],{"class":203},[186,798,799],{"class":289},"\u002Fkind.yaml\"",[186,801,738],{"class":199},[186,803,774],{"class":741},[186,805,806],{"class":188,"line":196},[186,807,808],{"class":289},"kind: Cluster\n",[186,810,811],{"class":188,"line":207},[186,812,813],{"class":289},"apiVersion: kind.x-k8s.io\u002Fv1alpha4\n",[186,815,816],{"class":188,"line":339},[186,817,818],{"class":289},"name: tabularia\n",[186,820,821],{"class":188,"line":564},[186,822,823],{"class":289},"networking:\n",[186,825,826],{"class":188,"line":581},[186,827,828],{"class":289},"  disableDefaultCNI: true\n",[186,830,831],{"class":188,"line":623},[186,832,833],{"class":289},"  podSubnet: 10.244.0.0\u002F16\n",[186,835,836],{"class":188,"line":652},[186,837,838],{"class":289},"nodes:\n",[186,840,841],{"class":188,"line":771},[186,842,843],{"class":289},"  - role: control-plane\n",[186,845,847],{"class":188,"line":846},10,[186,848,849],{"class":289},"    extraPortMappings:\n",[186,851,853],{"class":188,"line":852},11,[186,854,855],{"class":289},"      - { containerPort: 80, hostPort: 8080, listenAddress: \"127.0.0.1\" }\n",[186,857,859,862,864],{"class":188,"line":858},12,[186,860,861],{"class":289},"    extraMounts: [{ hostPath: \"",[186,863,137],{"class":203},[186,865,866],{"class":289},"\u002Fcerts.d\", containerPath: \u002Fetc\u002Fcontainerd\u002Fcerts.d }]\n",[186,868,870],{"class":188,"line":869},13,[186,871,872],{"class":289},"  - role: worker\n",[186,874,876,878,880],{"class":188,"line":875},14,[186,877,861],{"class":289},[186,879,137],{"class":203},[186,881,866],{"class":289},[186,883,885],{"class":188,"line":884},15,[186,886,872],{"class":289},[186,888,890,892,894],{"class":188,"line":889},16,[186,891,861],{"class":289},[186,893,137],{"class":203},[186,895,866],{"class":289},[186,897,899],{"class":188,"line":898},17,[186,900,901],{"class":289},"containerdConfigPatches:\n",[186,903,905],{"class":188,"line":904},18,[186,906,907],{"class":289},"  - |-\n",[186,909,911],{"class":188,"line":910},19,[186,912,913],{"class":289},"    [plugins.\"io.containerd.cri.v1.images\".registry]\n",[186,915,917],{"class":188,"line":916},20,[186,918,919],{"class":289},"      config_path = \"\u002Fetc\u002Fcontainerd\u002Fcerts.d\"\n",[186,921,923],{"class":188,"line":922},21,[186,924,774],{"class":741},[186,926,928,930,933,936,939,941,943],{"class":188,"line":927},22,[186,929,106],{"class":192},[186,931,932],{"class":289}," create",[186,934,935],{"class":289}," cluster",[186,937,938],{"class":293}," --config",[186,940,348],{"class":289},[186,942,137],{"class":203},[186,944,945],{"class":289},"\u002Fkind.yaml\"\n",[14,947,948,949,952],{},"The nodes are ",[120,950,951],{},"NotReady"," until a network plugin runs. Calico, with the pods' addresses\nof the cluster above:",[177,954,956],{"className":278,"code":955,"language":280,"meta":182,"style":182},"curl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fprojectcalico\u002Fcalico\u002Fv3.30.3\u002Fmanifests\u002Fcalico.yaml \\\n  | sed -e 's|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|' \\\n        -e 's|#   value: \"192.168.0.0\u002F16\"|  value: \"10.244.0.0\u002F16\"|' > \"$WORK\u002Fcalico.yaml\"\nkubectl apply -f \"$WORK\u002Fcalico.yaml\"\nkubectl rollout status daemonset calico-node --namespace kube-system --timeout=10m\nkubectl wait --for=condition=Ready nodes --all --timeout=5m\nkubectl get nodes\n",[120,957,958,971,987,1004,1020,1045,1064],{"__ignoreMap":182},[186,959,960,963,966,969],{"class":188,"line":189},[186,961,962],{"class":192},"curl",[186,964,965],{"class":293}," -fsSL",[186,967,968],{"class":289}," https:\u002F\u002Fraw.githubusercontent.com\u002Fprojectcalico\u002Fcalico\u002Fv3.30.3\u002Fmanifests\u002Fcalico.yaml",[186,970,416],{"class":415},[186,972,973,976,979,982,985],{"class":188,"line":196},[186,974,975],{"class":199},"  |",[186,977,978],{"class":192}," sed",[186,980,981],{"class":293}," -e",[186,983,984],{"class":289}," 's|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|'",[186,986,416],{"class":415},[186,988,989,992,995,997,999,1001],{"class":188,"line":207},[186,990,991],{"class":293},"        -e",[186,993,994],{"class":289}," 's|#   value: \"192.168.0.0\u002F16\"|  value: \"10.244.0.0\u002F16\"|'",[186,996,448],{"class":199},[186,998,348],{"class":289},[186,1000,137],{"class":203},[186,1002,1003],{"class":289},"\u002Fcalico.yaml\"\n",[186,1005,1006,1008,1011,1014,1016,1018],{"class":188,"line":339},[186,1007,112],{"class":192},[186,1009,1010],{"class":289}," apply",[186,1012,1013],{"class":293}," -f",[186,1015,348],{"class":289},[186,1017,137],{"class":203},[186,1019,1003],{"class":289},[186,1021,1022,1024,1027,1030,1033,1036,1039,1042],{"class":188,"line":564},[186,1023,112],{"class":192},[186,1025,1026],{"class":289}," rollout",[186,1028,1029],{"class":289}," status",[186,1031,1032],{"class":289}," daemonset",[186,1034,1035],{"class":289}," calico-node",[186,1037,1038],{"class":293}," --namespace",[186,1040,1041],{"class":289}," kube-system",[186,1043,1044],{"class":293}," --timeout=10m\n",[186,1046,1047,1049,1052,1055,1058,1061],{"class":188,"line":581},[186,1048,112],{"class":192},[186,1050,1051],{"class":289}," wait",[186,1053,1054],{"class":293}," --for=condition=Ready",[186,1056,1057],{"class":289}," nodes",[186,1059,1060],{"class":293}," --all",[186,1062,1063],{"class":293}," --timeout=5m\n",[186,1065,1066,1068,1071],{"class":188,"line":623},[186,1067,112],{"class":192},[186,1069,1070],{"class":289}," get",[186,1072,1073],{"class":289}," nodes\n",[14,1075,1076,1077,1080],{},"Three nodes, ",[120,1078,1079],{},"Ready",". Calico enforces the network policies, Kubernetes applies Pod\nSecurity: nothing more to install for them.",[1082,1083,1084,1096,1148],"blockquote",{},[14,1085,1086,1089,1090,1095],{},[70,1087,1088],{},"Cilium instead of Calico",", if your company runs it. Install the\n",[52,1091,1094],{"href":1092,"rel":1093},"https:\u002F\u002Fdocs.cilium.io\u002Fen\u002Fstable\u002Fgettingstarted\u002Fk8s-install-default\u002F#install-the-cilium-cli",[99],"Cilium CLI",",\nthen, in place of the Calico commands above:",[177,1097,1099],{"className":278,"code":1098,"language":280,"meta":182,"style":182},"cilium install --version 1.20.3\nkubectl rollout status daemonset cilium --namespace kube-system --timeout=10m\nkubectl wait --for=condition=Ready nodes --all --timeout=5m\n",[120,1100,1101,1115,1134],{"__ignoreMap":182},[186,1102,1103,1106,1109,1112],{"class":188,"line":189},[186,1104,1105],{"class":192},"cilium",[186,1107,1108],{"class":289}," install",[186,1110,1111],{"class":293}," --version",[186,1113,1114],{"class":293}," 1.20.3\n",[186,1116,1117,1119,1121,1123,1125,1128,1130,1132],{"class":188,"line":196},[186,1118,112],{"class":192},[186,1120,1026],{"class":289},[186,1122,1029],{"class":289},[186,1124,1032],{"class":289},[186,1126,1127],{"class":289}," cilium",[186,1129,1038],{"class":293},[186,1131,1041],{"class":289},[186,1133,1044],{"class":293},[186,1135,1136,1138,1140,1142,1144,1146],{"class":188,"line":207},[186,1137,112],{"class":192},[186,1139,1051],{"class":289},[186,1141,1054],{"class":293},[186,1143,1057],{"class":289},[186,1145,1060],{"class":293},[186,1147,1063],{"class":293},[14,1149,1150],{},"Everything else in this guide stays the same: Tabularia's network policies are\nstandard Kubernetes ones, and Cilium enforces them as Calico does.",[14,1152,1153],{},"The browser reaches Tabularia through an ingress controller on the control plane:",[177,1155,1157],{"className":278,"code":1156,"language":280,"meta":182,"style":182},"kubectl label node tabularia-control-plane ingress-ready=true\nkubectl apply -f https:\u002F\u002Fkind.sigs.k8s.io\u002Fexamples\u002Fingress\u002Fdeploy-ingress-nginx.yaml\nkubectl patch deployment ingress-nginx-controller --namespace ingress-nginx \\\n  -p '{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"ingress-ready\":\"true\",\"kubernetes.io\u002Fos\":\"linux\"}}}}}'\nkubectl rollout status deployment ingress-nginx-controller --namespace ingress-nginx --timeout=5m\n",[120,1158,1159,1178,1189,1209,1217],{"__ignoreMap":182},[186,1160,1161,1163,1166,1169,1172,1175],{"class":188,"line":189},[186,1162,112],{"class":192},[186,1164,1165],{"class":289}," label",[186,1167,1168],{"class":289}," node",[186,1170,1171],{"class":289}," tabularia-control-plane",[186,1173,1174],{"class":289}," ingress-ready=",[186,1176,1177],{"class":293},"true\n",[186,1179,1180,1182,1184,1186],{"class":188,"line":196},[186,1181,112],{"class":192},[186,1183,1010],{"class":289},[186,1185,1013],{"class":293},[186,1187,1188],{"class":289}," https:\u002F\u002Fkind.sigs.k8s.io\u002Fexamples\u002Fingress\u002Fdeploy-ingress-nginx.yaml\n",[186,1190,1191,1193,1196,1199,1202,1204,1207],{"class":188,"line":207},[186,1192,112],{"class":192},[186,1194,1195],{"class":289}," patch",[186,1197,1198],{"class":289}," deployment",[186,1200,1201],{"class":289}," ingress-nginx-controller",[186,1203,1038],{"class":293},[186,1205,1206],{"class":289}," ingress-nginx",[186,1208,416],{"class":415},[186,1210,1211,1214],{"class":188,"line":339},[186,1212,1213],{"class":293},"  -p",[186,1215,1216],{"class":289}," '{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"ingress-ready\":\"true\",\"kubernetes.io\u002Fos\":\"linux\"}}}}}'\n",[186,1218,1219,1221,1223,1225,1227,1229,1231,1233],{"class":188,"line":564},[186,1220,112],{"class":192},[186,1222,1026],{"class":289},[186,1224,1029],{"class":289},[186,1226,1198],{"class":289},[186,1228,1201],{"class":289},[186,1230,1038],{"class":293},[186,1232,1206],{"class":289},[186,1234,1063],{"class":293},[14,1236,1237,1238,1241,1242,1245,1248,1249,1252,1253,127],{},"The ",[120,1239,1240],{},"patch"," keeps the controller on the control plane, the only node that receives port\n8080: kind's manifest lets it start on any of the three nodes. ",[70,1243,1244],{},"Check:",[120,1246,1247],{},"curl -s -o \u002Fdev\u002Fnull -w '%{http_code}\\n' http:\u002F\u002Flocalhost:8080\u002F"," answers ",[120,1250,1251],{},"404"," (nothing\nis installed behind it yet), not ",[120,1254,1255],{},"000",[14,1257,1258,1259,1262,1263,1266],{},"CPU and memory of the pods, for ",[120,1260,1261],{},"kubectl top"," and for Tabularia's ",[70,1264,1265],{},"Administration →\nCluster",", come from metrics-server. kind's nodes have self-signed certificates, which\nmetrics-server is told to accept:",[177,1268,1270],{"className":278,"code":1269,"language":280,"meta":182,"style":182},"kubectl apply -f https:\u002F\u002Fgithub.com\u002Fkubernetes-sigs\u002Fmetrics-server\u002Freleases\u002Fdownload\u002Fv0.7.2\u002Fcomponents.yaml\nkubectl patch deployment metrics-server --namespace kube-system --type json \\\n  -p '[{\"op\":\"add\",\"path\":\"\u002Fspec\u002Ftemplate\u002Fspec\u002Fcontainers\u002F0\u002Fargs\u002F-\",\"value\":\"--kubelet-insecure-tls\"}]'\nkubectl rollout status deployment metrics-server --namespace kube-system --timeout=5m\n",[120,1271,1272,1283,1306,1313],{"__ignoreMap":182},[186,1273,1274,1276,1278,1280],{"class":188,"line":189},[186,1275,112],{"class":192},[186,1277,1010],{"class":289},[186,1279,1013],{"class":293},[186,1281,1282],{"class":289}," https:\u002F\u002Fgithub.com\u002Fkubernetes-sigs\u002Fmetrics-server\u002Freleases\u002Fdownload\u002Fv0.7.2\u002Fcomponents.yaml\n",[186,1284,1285,1287,1289,1291,1294,1296,1298,1301,1304],{"class":188,"line":196},[186,1286,112],{"class":192},[186,1288,1195],{"class":289},[186,1290,1198],{"class":289},[186,1292,1293],{"class":289}," metrics-server",[186,1295,1038],{"class":293},[186,1297,1041],{"class":289},[186,1299,1300],{"class":293}," --type",[186,1302,1303],{"class":289}," json",[186,1305,416],{"class":415},[186,1307,1308,1310],{"class":188,"line":207},[186,1309,1213],{"class":293},[186,1311,1312],{"class":289}," '[{\"op\":\"add\",\"path\":\"\u002Fspec\u002Ftemplate\u002Fspec\u002Fcontainers\u002F0\u002Fargs\u002F-\",\"value\":\"--kubelet-insecure-tls\"}]'\n",[186,1314,1315,1317,1319,1321,1323,1325,1327,1329],{"class":188,"line":339},[186,1316,112],{"class":192},[186,1318,1026],{"class":289},[186,1320,1029],{"class":289},[186,1322,1198],{"class":289},[186,1324,1293],{"class":289},[186,1326,1038],{"class":293},[186,1328,1041],{"class":289},[186,1330,1063],{"class":293},[14,1332,1333,1335,1336,1339],{},[70,1334,1244],{}," after a minute, ",[120,1337,1338],{},"kubectl top nodes"," lists the three nodes with their CPU and\nmemory.",[61,1341,1343],{"id":1342},"5-the-private-registry","5. The private registry",[14,1345,1346,1347,1350,1351,127],{},"A registry with TLS and a user, on the same Docker network as the nodes, so they reach\nit as ",[120,1348,1349],{},"tabularia-registry:5000",". Your computer reaches it at ",[120,1352,1353],{},"127.0.0.1:5443",[177,1355,1357],{"className":278,"code":1356,"language":280,"meta":182,"style":182},"export REGISTRY_PASSWORD=\"$(openssl rand -hex 16)\"\necho \"export REGISTRY_PASSWORD=$REGISTRY_PASSWORD\" >> \"$WORK\u002Fenv\"\nmkdir -p \"$WORK\u002Fauth\"\ndocker run --rm --entrypoint htpasswd httpd:2.4 -Bbn tabularia \"$REGISTRY_PASSWORD\" > \"$WORK\u002Fauth\u002Fhtpasswd\"\ndocker run -d --name tabularia-registry --network kind --restart unless-stopped -p 127.0.0.1:5443:5000 \\\n  -v \"$WORK\u002Fregistry.crt:\u002Fcerts\u002Fregistry.crt:ro\" -v \"$WORK\u002Fregistry.key:\u002Fcerts\u002Fregistry.key:ro\" \\\n  -v \"$WORK\u002Fauth:\u002Fauth:ro\" \\\n  -e REGISTRY_HTTP_TLS_CERTIFICATE=\u002Fcerts\u002Fregistry.crt -e REGISTRY_HTTP_TLS_KEY=\u002Fcerts\u002Fregistry.key \\\n  -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM=tabularia \\\n  -e REGISTRY_AUTH_HTPASSWD_PATH=\u002Fauth\u002Fhtpasswd \\\n  registry:2.8.3\n",[120,1358,1359,1385,1407,1420,1461,1495,1519,1532,1547,1561,1570],{"__ignoreMap":182},[186,1360,1361,1363,1366,1368,1371,1373,1376,1379,1382],{"class":188,"line":189},[186,1362,313],{"class":199},[186,1364,1365],{"class":203}," REGISTRY_PASSWORD",[186,1367,319],{"class":199},[186,1369,1370],{"class":289},"\"$(",[186,1372,122],{"class":192},[186,1374,1375],{"class":289}," rand ",[186,1377,1378],{"class":293},"-hex",[186,1380,1381],{"class":293}," 16",[186,1383,1384],{"class":289},")\"\n",[186,1386,1387,1390,1393,1396,1398,1401,1403,1405],{"class":188,"line":196},[186,1388,1389],{"class":293},"echo",[186,1391,1392],{"class":289}," \"export REGISTRY_PASSWORD=",[186,1394,1395],{"class":203},"$REGISTRY_PASSWORD",[186,1397,330],{"class":289},[186,1399,1400],{"class":199}," >>",[186,1402,348],{"class":289},[186,1404,137],{"class":203},[186,1406,455],{"class":289},[186,1408,1409,1411,1413,1415,1417],{"class":188,"line":207},[186,1410,342],{"class":192},[186,1412,345],{"class":293},[186,1414,348],{"class":289},[186,1416,137],{"class":203},[186,1418,1419],{"class":289},"\u002Fauth\"\n",[186,1421,1422,1425,1428,1431,1434,1437,1440,1443,1446,1448,1450,1452,1454,1456,1458],{"class":188,"line":339},[186,1423,1424],{"class":192},"docker",[186,1426,1427],{"class":289}," run",[186,1429,1430],{"class":293}," --rm",[186,1432,1433],{"class":293}," --entrypoint",[186,1435,1436],{"class":289}," htpasswd",[186,1438,1439],{"class":289}," httpd:2.4",[186,1441,1442],{"class":293}," -Bbn",[186,1444,1445],{"class":289}," tabularia",[186,1447,348],{"class":289},[186,1449,1395],{"class":203},[186,1451,330],{"class":289},[186,1453,448],{"class":199},[186,1455,348],{"class":289},[186,1457,137],{"class":203},[186,1459,1460],{"class":289},"\u002Fauth\u002Fhtpasswd\"\n",[186,1462,1463,1465,1467,1470,1473,1476,1479,1482,1485,1488,1490,1493],{"class":188,"line":564},[186,1464,1424],{"class":192},[186,1466,1427],{"class":289},[186,1468,1469],{"class":293}," -d",[186,1471,1472],{"class":293}," --name",[186,1474,1475],{"class":289}," tabularia-registry",[186,1477,1478],{"class":293}," --network",[186,1480,1481],{"class":289}," kind",[186,1483,1484],{"class":293}," --restart",[186,1486,1487],{"class":289}," unless-stopped",[186,1489,345],{"class":293},[186,1491,1492],{"class":289}," 127.0.0.1:5443:5000",[186,1494,416],{"class":415},[186,1496,1497,1500,1502,1504,1507,1510,1512,1514,1517],{"class":188,"line":581},[186,1498,1499],{"class":293},"  -v",[186,1501,348],{"class":289},[186,1503,137],{"class":203},[186,1505,1506],{"class":289},"\u002Fregistry.crt:\u002Fcerts\u002Fregistry.crt:ro\"",[186,1508,1509],{"class":293}," -v",[186,1511,348],{"class":289},[186,1513,137],{"class":203},[186,1515,1516],{"class":289},"\u002Fregistry.key:\u002Fcerts\u002Fregistry.key:ro\"",[186,1518,416],{"class":415},[186,1520,1521,1523,1525,1527,1530],{"class":188,"line":623},[186,1522,1499],{"class":293},[186,1524,348],{"class":289},[186,1526,137],{"class":203},[186,1528,1529],{"class":289},"\u002Fauth:\u002Fauth:ro\"",[186,1531,416],{"class":415},[186,1533,1534,1537,1540,1542,1545],{"class":188,"line":652},[186,1535,1536],{"class":293},"  -e",[186,1538,1539],{"class":289}," REGISTRY_HTTP_TLS_CERTIFICATE=\u002Fcerts\u002Fregistry.crt",[186,1541,981],{"class":293},[186,1543,1544],{"class":289}," REGISTRY_HTTP_TLS_KEY=\u002Fcerts\u002Fregistry.key",[186,1546,416],{"class":415},[186,1548,1549,1551,1554,1556,1559],{"class":188,"line":771},[186,1550,1536],{"class":293},[186,1552,1553],{"class":289}," REGISTRY_AUTH=htpasswd",[186,1555,981],{"class":293},[186,1557,1558],{"class":289}," REGISTRY_AUTH_HTPASSWD_REALM=tabularia",[186,1560,416],{"class":415},[186,1562,1563,1565,1568],{"class":188,"line":846},[186,1564,1536],{"class":293},[186,1566,1567],{"class":289}," REGISTRY_AUTH_HTPASSWD_PATH=\u002Fauth\u002Fhtpasswd",[186,1569,416],{"class":415},[186,1571,1572],{"class":188,"line":852},[186,1573,1574],{"class":289},"  registry:2.8.3\n",[14,1576,1577,1579],{},[70,1578,1244],{}," without the password the registry refuses, with it it answers (the first\ncommand waits a few seconds for the registry to start).",[177,1581,1583],{"className":278,"code":1582,"language":280,"meta":182,"style":182},"curl -s -o \u002Fdev\u002Fnull -w '%{http_code}\\n' --retry 10 --retry-all-errors --retry-delay 1 --cacert \"$WORK\u002Fca.crt\" \\\n  --resolve tabularia-registry:5443:127.0.0.1 https:\u002F\u002Ftabularia-registry:5443\u002Fv2\u002F\ncurl -s -o \u002Fdev\u002Fnull -w '%{http_code}\\n' --cacert \"$WORK\u002Fca.crt\" -u \"tabularia:$REGISTRY_PASSWORD\" \\\n  --resolve tabularia-registry:5443:127.0.0.1 https:\u002F\u002Ftabularia-registry:5443\u002Fv2\u002F\n",[120,1584,1585,1630,1641,1675],{"__ignoreMap":182},[186,1586,1587,1589,1592,1595,1598,1601,1604,1607,1610,1613,1616,1619,1622,1624,1626,1628],{"class":188,"line":189},[186,1588,962],{"class":192},[186,1590,1591],{"class":293}," -s",[186,1593,1594],{"class":293}," -o",[186,1596,1597],{"class":289}," \u002Fdev\u002Fnull",[186,1599,1600],{"class":293}," -w",[186,1602,1603],{"class":289}," '%{http_code}\\n'",[186,1605,1606],{"class":293}," --retry",[186,1608,1609],{"class":293}," 10",[186,1611,1612],{"class":293}," --retry-all-errors",[186,1614,1615],{"class":293}," --retry-delay",[186,1617,1618],{"class":293}," 1",[186,1620,1621],{"class":293}," --cacert",[186,1623,348],{"class":289},[186,1625,137],{"class":203},[186,1627,609],{"class":289},[186,1629,416],{"class":415},[186,1631,1632,1635,1638],{"class":188,"line":196},[186,1633,1634],{"class":293},"  --resolve",[186,1636,1637],{"class":289}," tabularia-registry:5443:127.0.0.1",[186,1639,1640],{"class":289}," https:\u002F\u002Ftabularia-registry:5443\u002Fv2\u002F\n",[186,1642,1643,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1666,1669,1671,1673],{"class":188,"line":207},[186,1644,962],{"class":192},[186,1646,1591],{"class":293},[186,1648,1594],{"class":293},[186,1650,1597],{"class":289},[186,1652,1600],{"class":293},[186,1654,1603],{"class":289},[186,1656,1621],{"class":293},[186,1658,348],{"class":289},[186,1660,137],{"class":203},[186,1662,609],{"class":289},[186,1664,1665],{"class":293}," -u",[186,1667,1668],{"class":289}," \"tabularia:",[186,1670,1395],{"class":203},[186,1672,330],{"class":289},[186,1674,416],{"class":415},[186,1676,1677,1679,1681],{"class":188,"line":339},[186,1678,1634],{"class":293},[186,1680,1637],{"class":289},[186,1682,1640],{"class":289},[14,1684,1685,1688,1689,127],{},[120,1686,1687],{},"401",", then ",[120,1690,1691],{},"200",[61,1693,1695],{"id":1694},"6-tabularias-images-in-the-private-registry","6. Tabularia's images in the private registry",[14,1697,1698,1699,1702],{},"A company copies the images it runs into its own registry. Do the same with the\npublished ones. The login goes in a Docker settings folder of its own, not in your\n",[120,1700,1701],{},"~\u002F.docker",":",[177,1704,1706],{"className":278,"code":1705,"language":280,"meta":182,"style":182},"export DOCKER_CONFIG=\"$WORK\u002Fdocker\"\necho \"$REGISTRY_PASSWORD\" | docker login 127.0.0.1:5443 -u tabularia --password-stdin\nfor c in backend gateway frontend; do\n  docker pull \"jumpasaservice\u002Ftabularia-${c}:${VERSION}\"\n  docker tag \"jumpasaservice\u002Ftabularia-${c}:${VERSION}\" \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-${c}:${VERSION}\"\n  docker push \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-${c}:${VERSION}\"\ndone\nunset DOCKER_CONFIG\n",[120,1707,1708,1724,1753,1779,1802,1831,1848,1853],{"__ignoreMap":182},[186,1709,1710,1712,1715,1717,1719,1721],{"class":188,"line":189},[186,1711,313],{"class":199},[186,1713,1714],{"class":203}," DOCKER_CONFIG",[186,1716,319],{"class":199},[186,1718,330],{"class":289},[186,1720,137],{"class":203},[186,1722,1723],{"class":289},"\u002Fdocker\"\n",[186,1725,1726,1728,1730,1732,1734,1737,1740,1743,1746,1748,1750],{"class":188,"line":196},[186,1727,1389],{"class":293},[186,1729,348],{"class":289},[186,1731,1395],{"class":203},[186,1733,330],{"class":289},[186,1735,1736],{"class":199}," |",[186,1738,1739],{"class":192}," docker",[186,1741,1742],{"class":289}," login",[186,1744,1745],{"class":289}," 127.0.0.1:5443",[186,1747,1665],{"class":293},[186,1749,1445],{"class":289},[186,1751,1752],{"class":293}," --password-stdin\n",[186,1754,1755,1758,1761,1764,1767,1770,1773,1776],{"class":188,"line":207},[186,1756,1757],{"class":199},"for",[186,1759,1760],{"class":203}," c ",[186,1762,1763],{"class":199},"in",[186,1765,1766],{"class":289}," backend",[186,1768,1769],{"class":289}," gateway",[186,1771,1772],{"class":289}," frontend",[186,1774,1775],{"class":203},"; ",[186,1777,1778],{"class":199},"do\n",[186,1780,1781,1784,1787,1790,1793,1796,1799],{"class":188,"line":339},[186,1782,1783],{"class":192},"  docker",[186,1785,1786],{"class":289}," pull",[186,1788,1789],{"class":289}," \"jumpasaservice\u002Ftabularia-${",[186,1791,1792],{"class":203},"c",[186,1794,1795],{"class":289},"}:${",[186,1797,1798],{"class":203},"VERSION",[186,1800,1801],{"class":289},"}\"\n",[186,1803,1804,1806,1809,1811,1813,1815,1817,1820,1823,1825,1827,1829],{"class":188,"line":564},[186,1805,1783],{"class":192},[186,1807,1808],{"class":289}," tag",[186,1810,1789],{"class":289},[186,1812,1792],{"class":203},[186,1814,1795],{"class":289},[186,1816,1798],{"class":203},[186,1818,1819],{"class":289},"}\"",[186,1821,1822],{"class":289}," \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-${",[186,1824,1792],{"class":203},[186,1826,1795],{"class":289},[186,1828,1798],{"class":203},[186,1830,1801],{"class":289},[186,1832,1833,1835,1838,1840,1842,1844,1846],{"class":188,"line":581},[186,1834,1783],{"class":192},[186,1836,1837],{"class":289}," push",[186,1839,1822],{"class":289},[186,1841,1792],{"class":203},[186,1843,1795],{"class":289},[186,1845,1798],{"class":203},[186,1847,1801],{"class":289},[186,1849,1850],{"class":188,"line":623},[186,1851,1852],{"class":199},"done\n",[186,1854,1855,1858],{"class":188,"line":652},[186,1856,1857],{"class":293},"unset",[186,1859,1860],{"class":289}," DOCKER_CONFIG\n",[14,1862,1863,1864,1867,1868,1870],{},"Docker trusts registries on ",[120,1865,1866],{},"127.0.0.1"," without checking their certificate, so your\ncomputer needs no change. The nodes download from ",[120,1869,1349],{}," and check\nit, with the authority of step 3.",[1082,1872,1873,1883,2068],{},[14,1874,1875,1878,1879,1882],{},[70,1876,1877],{},"The code of a branch instead of a release"," (to try a change before it is released):\nclone without ",[120,1880,1881],{},"--branch",", take the version from the chart, and build the three images\nfrom the clone instead of pulling them. Then go on with step 7.",[177,1884,1886],{"className":278,"code":1885,"language":280,"meta":182,"style":182},"export VERSION=\"$(sed -n 's\u002F^appVersion: \"\\([^\"]*\\)\".*\u002F\\1\u002Fp' infrastructure\u002Fhelm\u002Ftabularia\u002FChart.yaml)\"\necho \"export VERSION=$VERSION\" >> \"$WORK\u002Fenv\"\nexport DOCKER_CONFIG=\"$WORK\u002Fdocker\"\necho \"$REGISTRY_PASSWORD\" | docker login 127.0.0.1:5443 -u tabularia --password-stdin\ndocker build -t \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-backend:${VERSION}\" backend\u002F\ndocker build -t \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-gateway:${VERSION}\" gateway\u002F\ndocker build -f frontend\u002FDockerfile.prod -t \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-frontend:${VERSION}\" frontend\u002F\nfor c in backend gateway frontend; do docker push \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-${c}:${VERSION}\"; done\nunset DOCKER_CONFIG\n",[120,1887,1888,1907,1926,1940,1964,1984,2002,2025,2062],{"__ignoreMap":182},[186,1889,1890,1892,1894,1896,1898,1901,1904],{"class":188,"line":189},[186,1891,313],{"class":199},[186,1893,316],{"class":203},[186,1895,319],{"class":199},[186,1897,1370],{"class":289},[186,1899,1900],{"class":192},"sed",[186,1902,1903],{"class":293}," -n",[186,1905,1906],{"class":289}," 's\u002F^appVersion: \"\\([^\"]*\\)\".*\u002F\\1\u002Fp' infrastructure\u002Fhelm\u002Ftabularia\u002FChart.yaml)\"\n",[186,1908,1909,1911,1914,1916,1918,1920,1922,1924],{"class":188,"line":196},[186,1910,1389],{"class":293},[186,1912,1913],{"class":289}," \"export VERSION=",[186,1915,140],{"class":203},[186,1917,330],{"class":289},[186,1919,1400],{"class":199},[186,1921,348],{"class":289},[186,1923,137],{"class":203},[186,1925,455],{"class":289},[186,1927,1928,1930,1932,1934,1936,1938],{"class":188,"line":207},[186,1929,313],{"class":199},[186,1931,1714],{"class":203},[186,1933,319],{"class":199},[186,1935,330],{"class":289},[186,1937,137],{"class":203},[186,1939,1723],{"class":289},[186,1941,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962],{"class":188,"line":339},[186,1943,1389],{"class":293},[186,1945,348],{"class":289},[186,1947,1395],{"class":203},[186,1949,330],{"class":289},[186,1951,1736],{"class":199},[186,1953,1739],{"class":192},[186,1955,1742],{"class":289},[186,1957,1745],{"class":289},[186,1959,1665],{"class":293},[186,1961,1445],{"class":289},[186,1963,1752],{"class":293},[186,1965,1966,1968,1971,1974,1977,1979,1981],{"class":188,"line":564},[186,1967,1424],{"class":192},[186,1969,1970],{"class":289}," build",[186,1972,1973],{"class":293}," -t",[186,1975,1976],{"class":289}," \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-backend:${",[186,1978,1798],{"class":203},[186,1980,1819],{"class":289},[186,1982,1983],{"class":289}," backend\u002F\n",[186,1985,1986,1988,1990,1992,1995,1997,1999],{"class":188,"line":581},[186,1987,1424],{"class":192},[186,1989,1970],{"class":289},[186,1991,1973],{"class":293},[186,1993,1994],{"class":289}," \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-gateway:${",[186,1996,1798],{"class":203},[186,1998,1819],{"class":289},[186,2000,2001],{"class":289}," gateway\u002F\n",[186,2003,2004,2006,2008,2010,2013,2015,2018,2020,2022],{"class":188,"line":623},[186,2005,1424],{"class":192},[186,2007,1970],{"class":289},[186,2009,1013],{"class":293},[186,2011,2012],{"class":289}," frontend\u002FDockerfile.prod",[186,2014,1973],{"class":293},[186,2016,2017],{"class":289}," \"127.0.0.1:5443\u002Ftabularia\u002Ftabularia-frontend:${",[186,2019,1798],{"class":203},[186,2021,1819],{"class":289},[186,2023,2024],{"class":289}," frontend\u002F\n",[186,2026,2027,2029,2031,2033,2035,2037,2039,2041,2044,2046,2048,2050,2052,2054,2056,2058,2060],{"class":188,"line":652},[186,2028,1757],{"class":199},[186,2030,1760],{"class":203},[186,2032,1763],{"class":199},[186,2034,1766],{"class":289},[186,2036,1769],{"class":289},[186,2038,1772],{"class":289},[186,2040,1775],{"class":203},[186,2042,2043],{"class":199},"do",[186,2045,1739],{"class":192},[186,2047,1837],{"class":289},[186,2049,1822],{"class":289},[186,2051,1792],{"class":203},[186,2053,1795],{"class":289},[186,2055,1798],{"class":203},[186,2057,1819],{"class":289},[186,2059,1775],{"class":203},[186,2061,1852],{"class":199},[186,2063,2064,2066],{"class":188,"line":771},[186,2065,1857],{"class":293},[186,2067,1860],{"class":289},[14,2069,2070],{},"The builds take a while the first time (the backend image is about 800 MB).",[61,2072,2074],{"id":2073},"7-postgresql-valkey-and-the-object-storage","7. PostgreSQL, Valkey and the object storage",[14,2076,2077,2078,2081],{},"PostgreSQL and Valkey run in the namespace ",[120,2079,2080],{},"tabularia-deps",", with a password made now:",[177,2083,2085],{"className":278,"code":2084,"language":280,"meta":182,"style":182},"export DB_PASSWORD=\"$(openssl rand -hex 32)\"\nprintf 'export DB_PASSWORD=%s\\n' \"$DB_PASSWORD\" >> \"$WORK\u002Fenv\"\nkubectl create namespace tabularia-deps\nkubectl create secret generic postgres --namespace tabularia-deps --from-literal=password=\"$DB_PASSWORD\"\nkubectl apply -f infrastructure\u002Fminikube\u002Fdependencies.yaml\n",[120,2086,2087,2109,2131,2143,2172],{"__ignoreMap":182},[186,2088,2089,2091,2094,2096,2098,2100,2102,2104,2107],{"class":188,"line":189},[186,2090,313],{"class":199},[186,2092,2093],{"class":203}," DB_PASSWORD",[186,2095,319],{"class":199},[186,2097,1370],{"class":289},[186,2099,122],{"class":192},[186,2101,1375],{"class":289},[186,2103,1378],{"class":293},[186,2105,2106],{"class":293}," 32",[186,2108,1384],{"class":289},[186,2110,2111,2113,2116,2118,2121,2123,2125,2127,2129],{"class":188,"line":196},[186,2112,409],{"class":293},[186,2114,2115],{"class":289}," 'export DB_PASSWORD=%s\\n'",[186,2117,348],{"class":289},[186,2119,2120],{"class":203},"$DB_PASSWORD",[186,2122,330],{"class":289},[186,2124,1400],{"class":199},[186,2126,348],{"class":289},[186,2128,137],{"class":203},[186,2130,455],{"class":289},[186,2132,2133,2135,2137,2140],{"class":188,"line":207},[186,2134,112],{"class":192},[186,2136,932],{"class":289},[186,2138,2139],{"class":289}," namespace",[186,2141,2142],{"class":289}," tabularia-deps\n",[186,2144,2145,2147,2149,2152,2155,2158,2160,2163,2166,2168,2170],{"class":188,"line":339},[186,2146,112],{"class":192},[186,2148,932],{"class":289},[186,2150,2151],{"class":289}," secret",[186,2153,2154],{"class":289}," generic",[186,2156,2157],{"class":289}," postgres",[186,2159,1038],{"class":293},[186,2161,2162],{"class":289}," tabularia-deps",[186,2164,2165],{"class":293}," --from-literal=password=",[186,2167,330],{"class":289},[186,2169,2120],{"class":203},[186,2171,353],{"class":289},[186,2173,2174,2176,2178,2180],{"class":188,"line":564},[186,2175,112],{"class":192},[186,2177,1010],{"class":289},[186,2179,1013],{"class":293},[186,2181,2182],{"class":289}," infrastructure\u002Fminikube\u002Fdependencies.yaml\n",[14,2184,2185],{},"The file also holds a network policy: only Tabularia's namespace (created in step 9)\nreaches the database and the queue. The scripts and the Container nodes may reach this\nnamespace, for the scripts bucket when it is MinIO, so without that policy a script\ncould write into the workers' queue.",[14,2187,2188,2189,2192],{},"Tabularia keeps its datasets, uploads and results in an S3-compatible bucket, and the\nPython and R scripts exchange their data through a second one. Choose ",[70,2190,2191],{},"one"," of the\ntwo ways below: MinIO inside the cluster, to have everything on this computer, or the\nobject storage of your cloud, as a company runs it.",[147,2194,2196],{"id":2195},"either-minio-inside-the-cluster","Either: MinIO inside the cluster",[177,2198,2200],{"className":278,"code":2199,"language":280,"meta":182,"style":182},"export MINIO_PASSWORD=\"$(openssl rand -hex 24)\" SCRIPTS_KEY=\"$(openssl rand -hex 24)\"\nkubectl create secret generic minio --namespace tabularia-deps \\\n  --from-literal=root-password=\"$MINIO_PASSWORD\" --from-literal=scripts-key=\"$SCRIPTS_KEY\"\nkubectl apply -f infrastructure\u002Fkind\u002Fstorage.yaml\nkubectl wait --namespace tabularia-deps --for=condition=complete job\u002Fminio-setup --timeout=5m\nexport STORAGE_ENDPOINT=\"http:\u002F\u002Fminio.tabularia-deps.svc.cluster.local:9000\" STORAGE_REGION=us-east-1 \\\n  STORAGE_BUCKET=tabularia STORAGE_ACCESS_KEY=root STORAGE_SECRET_KEY=\"$MINIO_PASSWORD\" \\\n  SCRIPTS_ENDPOINT=\"http:\u002F\u002Fminio.tabularia-deps.svc.cluster.local:9000\" SCRIPTS_REGION=us-east-1 \\\n  SCRIPTS_BUCKET=tabularia-scripts SCRIPTS_ACCESS_KEY=scripts SCRIPTS_SECRET_KEY=\"$SCRIPTS_KEY\"\n",[120,2201,2202,2242,2261,2283,2294,2312,2332,2357,2373],{"__ignoreMap":182},[186,2203,2204,2206,2209,2211,2213,2215,2217,2219,2222,2225,2228,2230,2232,2234,2236,2238,2240],{"class":188,"line":189},[186,2205,313],{"class":199},[186,2207,2208],{"class":203}," MINIO_PASSWORD",[186,2210,319],{"class":199},[186,2212,1370],{"class":289},[186,2214,122],{"class":192},[186,2216,1375],{"class":289},[186,2218,1378],{"class":293},[186,2220,2221],{"class":293}," 24",[186,2223,2224],{"class":289},")\"",[186,2226,2227],{"class":203}," SCRIPTS_KEY",[186,2229,319],{"class":199},[186,2231,1370],{"class":289},[186,2233,122],{"class":192},[186,2235,1375],{"class":289},[186,2237,1378],{"class":293},[186,2239,2221],{"class":293},[186,2241,1384],{"class":289},[186,2243,2244,2246,2248,2250,2252,2255,2257,2259],{"class":188,"line":196},[186,2245,112],{"class":192},[186,2247,932],{"class":289},[186,2249,2151],{"class":289},[186,2251,2154],{"class":289},[186,2253,2254],{"class":289}," minio",[186,2256,1038],{"class":293},[186,2258,2162],{"class":289},[186,2260,416],{"class":415},[186,2262,2263,2266,2268,2271,2273,2276,2278,2281],{"class":188,"line":207},[186,2264,2265],{"class":293},"  --from-literal=root-password=",[186,2267,330],{"class":289},[186,2269,2270],{"class":203},"$MINIO_PASSWORD",[186,2272,330],{"class":289},[186,2274,2275],{"class":293}," --from-literal=scripts-key=",[186,2277,330],{"class":289},[186,2279,2280],{"class":203},"$SCRIPTS_KEY",[186,2282,353],{"class":289},[186,2284,2285,2287,2289,2291],{"class":188,"line":339},[186,2286,112],{"class":192},[186,2288,1010],{"class":289},[186,2290,1013],{"class":293},[186,2292,2293],{"class":289}," infrastructure\u002Fkind\u002Fstorage.yaml\n",[186,2295,2296,2298,2300,2302,2304,2307,2310],{"class":188,"line":564},[186,2297,112],{"class":192},[186,2299,1051],{"class":289},[186,2301,1038],{"class":293},[186,2303,2162],{"class":289},[186,2305,2306],{"class":293}," --for=condition=complete",[186,2308,2309],{"class":289}," job\u002Fminio-setup",[186,2311,1063],{"class":293},[186,2313,2314,2316,2319,2321,2324,2327,2329],{"class":188,"line":581},[186,2315,313],{"class":199},[186,2317,2318],{"class":203}," STORAGE_ENDPOINT",[186,2320,319],{"class":199},[186,2322,2323],{"class":289},"\"http:\u002F\u002Fminio.tabularia-deps.svc.cluster.local:9000\"",[186,2325,2326],{"class":203}," STORAGE_REGION",[186,2328,319],{"class":199},[186,2330,2331],{"class":203},"us-east-1 \\\n",[186,2333,2334,2337,2339,2342,2344,2347,2349,2351,2353,2355],{"class":188,"line":623},[186,2335,2336],{"class":203},"  STORAGE_BUCKET",[186,2338,319],{"class":199},[186,2340,2341],{"class":203},"tabularia STORAGE_ACCESS_KEY",[186,2343,319],{"class":199},[186,2345,2346],{"class":203},"root STORAGE_SECRET_KEY",[186,2348,319],{"class":199},[186,2350,330],{"class":289},[186,2352,2270],{"class":203},[186,2354,330],{"class":289},[186,2356,416],{"class":203},[186,2358,2359,2362,2364,2366,2369,2371],{"class":188,"line":652},[186,2360,2361],{"class":203},"  SCRIPTS_ENDPOINT",[186,2363,319],{"class":199},[186,2365,2323],{"class":289},[186,2367,2368],{"class":203}," SCRIPTS_REGION",[186,2370,319],{"class":199},[186,2372,2331],{"class":203},[186,2374,2375,2378,2380,2383,2385,2388,2390,2392,2394],{"class":188,"line":771},[186,2376,2377],{"class":203},"  SCRIPTS_BUCKET",[186,2379,319],{"class":199},[186,2381,2382],{"class":203},"tabularia-scripts SCRIPTS_ACCESS_KEY",[186,2384,319],{"class":199},[186,2386,2387],{"class":203},"scripts SCRIPTS_SECRET_KEY",[186,2389,319],{"class":199},[186,2391,330],{"class":289},[186,2393,2280],{"class":203},[186,2395,353],{"class":289},[14,2397,2398,2399,2402,2403,2406,2407,2410],{},"The Job creates the two buckets: ",[120,2400,2401],{},"tabularia"," for Tabularia's data, and\n",[120,2404,2405],{},"tabularia-scripts"," with a key of its own (",[120,2408,2409],{},"scripts",") for the scripts.",[147,2412,2414],{"id":2413},"or-the-object-storage-of-your-cloud","Or: the object storage of your cloud",[14,2416,2417],{},"In your cloud provider's console:",[154,2419,2420,2434,2445],{},[21,2421,2422,2425,2426,2429,2430,2433],{},[70,2423,2424],{},"Create two private buckets"," in the same region, for example\n",[120,2427,2428],{},"tabularia-yourcompany"," for Tabularia's data and ",[120,2431,2432],{},"tabularia-yourcompany-scripts"," for\nthe scripts. On the scripts bucket, add a rule that deletes its objects after one day.",[21,2435,2436,2439,2440,2444],{},[70,2437,2438],{},"Create two access keys",": one that reads, writes, lists and deletes objects in the\ndata bucket; one that reads, writes and deletes objects in the scripts bucket, without\nlisting it. ",[52,2441,2443],{"href":2442},"\u002Fdocs\u002Fscripting#the-scripts-bucket","The scripts bucket"," shows the policies\nfor each provider.",[21,2446,2447,2450],{},[70,2448,2449],{},"Note the endpoint and the region"," of the buckets:",[2452,2453,2454,2470],"table",{},[2455,2456,2457],"thead",{},[2458,2459,2460,2464,2467],"tr",{},[2461,2462,2463],"th",{},"Provider",[2461,2465,2466],{},"Endpoint",[2461,2468,2469],{},"Region, for example",[2471,2472,2473,2497,2513],"tbody",{},[2458,2474,2475,2479,2492],{},[2476,2477,2478],"td",{},"Scaleway",[2476,2480,2481,2484,2485,101,2488,2491],{},[120,2482,2483],{},"https:\u002F\u002Fs3.fr-par.scw.cloud"," (or ",[120,2486,2487],{},"nl-ams",[120,2489,2490],{},"pl-waw",")",[2476,2493,2494],{},[120,2495,2496],{},"fr-par",[2458,2498,2499,2502,2508],{},[2476,2500,2501],{},"AWS",[2476,2503,2504,2507],{},[120,2505,2506],{},"https:\u002F\u002Fs3.eu-west-1.amazonaws.com"," (your region)",[2476,2509,2510],{},[120,2511,2512],{},"eu-west-1",[2458,2514,2515,2518,2521],{},[2476,2516,2517],{},"Other S3-compatible",[2476,2519,2520],{},"the S3 address in your provider's documentation",[2476,2522,2523],{},"its region",[1082,2525,2526],{},[14,2527,2528,2531,2532,2535],{},[70,2529,2530],{},"Scaleway:"," an API key works on its ",[70,2533,2534],{},"preferred project"," for Object Storage. When\nyou create the key (IAM → API keys), choose the project that holds the buckets.",[14,2537,2538,2539,2542],{},"Then give the values to the terminal (replace every ",[120,2540,2541],{},"\u003C…>","; the quotes stay):",[177,2544,2546],{"className":278,"code":2545,"language":280,"meta":182,"style":182},"export STORAGE_ENDPOINT=\"\u003Chttps:\u002F\u002Fs3.fr-par.scw.cloud>\" STORAGE_REGION=\"\u003Cfr-par>\" \\\n  STORAGE_BUCKET=\"\u003Ctabularia-yourcompany>\" \\\n  STORAGE_ACCESS_KEY=\"\u003Caccess key of the data bucket>\" STORAGE_SECRET_KEY=\"\u003Cits secret key>\" \\\n  SCRIPTS_ENDPOINT=\"\u003Chttps:\u002F\u002Fs3.fr-par.scw.cloud>\" SCRIPTS_REGION=\"\u003Cfr-par>\" \\\n  SCRIPTS_BUCKET=\"\u003Ctabularia-yourcompany-scripts>\" \\\n  SCRIPTS_ACCESS_KEY=\"\u003Caccess key of the scripts bucket>\" SCRIPTS_SECRET_KEY=\"\u003Cits secret key>\"\n",[120,2547,2548,2568,2579,2599,2615,2626],{"__ignoreMap":182},[186,2549,2550,2552,2554,2556,2559,2561,2563,2566],{"class":188,"line":189},[186,2551,313],{"class":199},[186,2553,2318],{"class":203},[186,2555,319],{"class":199},[186,2557,2558],{"class":289},"\"\u003Chttps:\u002F\u002Fs3.fr-par.scw.cloud>\"",[186,2560,2326],{"class":203},[186,2562,319],{"class":199},[186,2564,2565],{"class":289},"\"\u003Cfr-par>\"",[186,2567,416],{"class":203},[186,2569,2570,2572,2574,2577],{"class":188,"line":196},[186,2571,2336],{"class":203},[186,2573,319],{"class":199},[186,2575,2576],{"class":289},"\"\u003Ctabularia-yourcompany>\"",[186,2578,416],{"class":203},[186,2580,2581,2584,2586,2589,2592,2594,2597],{"class":188,"line":207},[186,2582,2583],{"class":203},"  STORAGE_ACCESS_KEY",[186,2585,319],{"class":199},[186,2587,2588],{"class":289},"\"\u003Caccess key of the data bucket>\"",[186,2590,2591],{"class":203}," STORAGE_SECRET_KEY",[186,2593,319],{"class":199},[186,2595,2596],{"class":289},"\"\u003Cits secret key>\"",[186,2598,416],{"class":203},[186,2600,2601,2603,2605,2607,2609,2611,2613],{"class":188,"line":339},[186,2602,2361],{"class":203},[186,2604,319],{"class":199},[186,2606,2558],{"class":289},[186,2608,2368],{"class":203},[186,2610,319],{"class":199},[186,2612,2565],{"class":289},[186,2614,416],{"class":203},[186,2616,2617,2619,2621,2624],{"class":188,"line":564},[186,2618,2377],{"class":203},[186,2620,319],{"class":199},[186,2622,2623],{"class":289},"\"\u003Ctabularia-yourcompany-scripts>\"",[186,2625,416],{"class":203},[186,2627,2628,2631,2633,2636,2639,2641],{"class":188,"line":581},[186,2629,2630],{"class":203},"  SCRIPTS_ACCESS_KEY",[186,2632,319],{"class":199},[186,2634,2635],{"class":289},"\"\u003Caccess key of the scripts bucket>\"",[186,2637,2638],{"class":203}," SCRIPTS_SECRET_KEY",[186,2640,319],{"class":199},[186,2642,2643],{"class":289},"\"\u003Cits secret key>\"\n",[14,2645,2646],{},"Nothing about the bucket is in the cluster: the workers, the scripts and the Container\nnodes reach it over the internet, which the network policies of step 8 allow.",[147,2648,2650],{"id":2649},"both-ways-keep-the-values","Both ways: keep the values",[177,2652,2654],{"className":278,"code":2653,"language":280,"meta":182,"style":182},"printf 'export STORAGE_ENDPOINT=%q STORAGE_REGION=%q STORAGE_BUCKET=%q STORAGE_ACCESS_KEY=%q STORAGE_SECRET_KEY=%q\\n' \\\n  \"$STORAGE_ENDPOINT\" \"$STORAGE_REGION\" \"$STORAGE_BUCKET\" \"$STORAGE_ACCESS_KEY\" \"$STORAGE_SECRET_KEY\" >> \"$WORK\u002Fenv\"\nprintf 'export SCRIPTS_ENDPOINT=%q SCRIPTS_REGION=%q SCRIPTS_BUCKET=%q SCRIPTS_ACCESS_KEY=%q SCRIPTS_SECRET_KEY=%q\\n' \\\n  \"$SCRIPTS_ENDPOINT\" \"$SCRIPTS_REGION\" \"$SCRIPTS_BUCKET\" \"$SCRIPTS_ACCESS_KEY\" \"$SCRIPTS_SECRET_KEY\" >> \"$WORK\u002Fenv\"\n",[120,2655,2656,2665,2710,2719],{"__ignoreMap":182},[186,2657,2658,2660,2663],{"class":188,"line":189},[186,2659,409],{"class":293},[186,2661,2662],{"class":289}," 'export STORAGE_ENDPOINT=%q STORAGE_REGION=%q STORAGE_BUCKET=%q STORAGE_ACCESS_KEY=%q STORAGE_SECRET_KEY=%q\\n'",[186,2664,416],{"class":415},[186,2666,2667,2669,2672,2674,2676,2679,2681,2683,2686,2688,2690,2693,2695,2697,2700,2702,2704,2706,2708],{"class":188,"line":196},[186,2668,421],{"class":289},[186,2670,2671],{"class":203},"$STORAGE_ENDPOINT",[186,2673,330],{"class":289},[186,2675,348],{"class":289},[186,2677,2678],{"class":203},"$STORAGE_REGION",[186,2680,330],{"class":289},[186,2682,348],{"class":289},[186,2684,2685],{"class":203},"$STORAGE_BUCKET",[186,2687,330],{"class":289},[186,2689,348],{"class":289},[186,2691,2692],{"class":203},"$STORAGE_ACCESS_KEY",[186,2694,330],{"class":289},[186,2696,348],{"class":289},[186,2698,2699],{"class":203},"$STORAGE_SECRET_KEY",[186,2701,330],{"class":289},[186,2703,1400],{"class":199},[186,2705,348],{"class":289},[186,2707,137],{"class":203},[186,2709,455],{"class":289},[186,2711,2712,2714,2717],{"class":188,"line":207},[186,2713,409],{"class":293},[186,2715,2716],{"class":289}," 'export SCRIPTS_ENDPOINT=%q SCRIPTS_REGION=%q SCRIPTS_BUCKET=%q SCRIPTS_ACCESS_KEY=%q SCRIPTS_SECRET_KEY=%q\\n'",[186,2718,416],{"class":415},[186,2720,2721,2723,2726,2728,2730,2733,2735,2737,2740,2742,2744,2747,2749,2751,2754,2756,2758,2760,2762],{"class":188,"line":339},[186,2722,421],{"class":289},[186,2724,2725],{"class":203},"$SCRIPTS_ENDPOINT",[186,2727,330],{"class":289},[186,2729,348],{"class":289},[186,2731,2732],{"class":203},"$SCRIPTS_REGION",[186,2734,330],{"class":289},[186,2736,348],{"class":289},[186,2738,2739],{"class":203},"$SCRIPTS_BUCKET",[186,2741,330],{"class":289},[186,2743,348],{"class":289},[186,2745,2746],{"class":203},"$SCRIPTS_ACCESS_KEY",[186,2748,330],{"class":289},[186,2750,348],{"class":289},[186,2752,2753],{"class":203},"$SCRIPTS_SECRET_KEY",[186,2755,330],{"class":289},[186,2757,1400],{"class":199},[186,2759,348],{"class":289},[186,2761,137],{"class":203},[186,2763,455],{"class":289},[61,2765,2767],{"id":2766},"8-tabularias-settings","8. Tabularia's settings",[14,2769,2770],{},"The values a company would write: images from its registry, the dependencies in their\nnamespace, network policies on, scripting with the company's authority, the Container\nnode on.",[14,2772,2773],{},"The image builds push to the registry, and on this computer the registry has a private\naddress on kind's Docker network. The scripts' network policy closes private addresses\nunless they are listed, so read the registry's:",[177,2775,2777],{"className":278,"code":2776,"language":280,"meta":182,"style":182},"export REGISTRY_IP=\"$(docker exec tabularia-control-plane getent ahostsv4 tabularia-registry | awk 'NR==1 {print $1}')\"\necho \"$REGISTRY_IP\"\nprintf 'export REGISTRY_IP=%s\\n' \"$REGISTRY_IP\" >> \"$WORK\u002Fenv\"\n",[120,2778,2779,2804,2815],{"__ignoreMap":182},[186,2780,2781,2783,2786,2788,2790,2792,2795,2798,2801],{"class":188,"line":189},[186,2782,313],{"class":199},[186,2784,2785],{"class":203}," REGISTRY_IP",[186,2787,319],{"class":199},[186,2789,1370],{"class":289},[186,2791,1424],{"class":192},[186,2793,2794],{"class":289}," exec tabularia-control-plane getent ahostsv4 tabularia-registry ",[186,2796,2797],{"class":199},"|",[186,2799,2800],{"class":192}," awk",[186,2802,2803],{"class":289}," 'NR==1 {print $1}')\"\n",[186,2805,2806,2808,2810,2813],{"class":188,"line":196},[186,2807,1389],{"class":293},[186,2809,348],{"class":289},[186,2811,2812],{"class":203},"$REGISTRY_IP",[186,2814,353],{"class":289},[186,2816,2817,2819,2822,2824,2826,2828,2830,2832,2834],{"class":188,"line":207},[186,2818,409],{"class":293},[186,2820,2821],{"class":289}," 'export REGISTRY_IP=%s\\n'",[186,2823,348],{"class":289},[186,2825,2812],{"class":203},[186,2827,330],{"class":289},[186,2829,1400],{"class":199},[186,2831,348],{"class":289},[186,2833,137],{"class":203},[186,2835,455],{"class":289},[14,2837,2838,2839,2842],{},"It prints one address, such as ",[120,2840,2841],{},"172.18.0.5",". Then the settings:",[177,2844,2846],{"className":278,"code":2845,"language":280,"meta":182,"style":182},"cat > \"$WORK\u002Fvalues.yaml\" \u003C\u003CEOF\nimage:\n  registry: \"tabularia-registry:5000\u002Ftabularia\"\n  pullSecrets: [{ name: registry }]\nexternalServices:\n  postgres: { host: postgres.tabularia-deps.svc.cluster.local }\n  redis: { host: valkey.tabularia-deps.svc.cluster.local }\n  storage: { endpoint: \"$STORAGE_ENDPOINT\", bucket: \"$STORAGE_BUCKET\", region: \"$STORAGE_REGION\" }\nsecrets:\n  fernetKey: \"$(openssl rand -base64 32 | tr '+\u002F' '-_')\"\n  jwtSecret: \"$(openssl rand -hex 32)\"\n  dbPassword: \"$DB_PASSWORD\"\n  storageAccessKey: \"$STORAGE_ACCESS_KEY\"\n  storageSecretKey: \"$STORAGE_SECRET_KEY\"\n  adminPassword: \"$ADMIN_PASSWORD\"\nauth:\n  adminEmail: \"$ADMIN_EMAIL\"\ningress:\n  hosts:\n    - host: \"\"\n      paths: [{ path: \u002F, pathType: Prefix, service: frontend }]\nnetworkPolicy:\n  enabled: true\n  egressCIDRs: [\"0.0.0.0\u002F0\"]\n  userCodePrivateCIDRs: [\"$REGISTRY_IP\u002F32\"]\nscripts:\n  enabled: true\n  registry:\n    repository: \"tabularia-registry:5000\u002Ftabularia-envs\"\n    pushSecret: registry\n    pullSecret: registry\n    caCert: |\n$(sed 's\u002F^\u002F      \u002F' \"$WORK\u002Fca.crt\")\n  notebooks:\n    host: notebooks.localhost\n    origin: http:\u002F\u002Fnotebooks.localhost:8080\ncontainers:\n  enabled: true\nEOF\n",[120,2847,2848,2865,2870,2875,2880,2885,2890,2895,2915,2920,2942,2957,2966,2975,2984,2993,2998,3007,3012,3017,3022,3027,3032,3038,3044,3055,3061,3066,3072,3078,3084,3090,3096,3112,3118,3124,3130,3136,3141],{"__ignoreMap":182},[186,2849,2850,2852,2854,2856,2858,2861,2863],{"class":188,"line":189},[186,2851,726],{"class":192},[186,2853,448],{"class":199},[186,2855,348],{"class":289},[186,2857,137],{"class":203},[186,2859,2860],{"class":289},"\u002Fvalues.yaml\"",[186,2862,738],{"class":199},[186,2864,774],{"class":741},[186,2866,2867],{"class":188,"line":196},[186,2868,2869],{"class":289},"image:\n",[186,2871,2872],{"class":188,"line":207},[186,2873,2874],{"class":289},"  registry: \"tabularia-registry:5000\u002Ftabularia\"\n",[186,2876,2877],{"class":188,"line":339},[186,2878,2879],{"class":289},"  pullSecrets: [{ name: registry }]\n",[186,2881,2882],{"class":188,"line":564},[186,2883,2884],{"class":289},"externalServices:\n",[186,2886,2887],{"class":188,"line":581},[186,2888,2889],{"class":289},"  postgres: { host: postgres.tabularia-deps.svc.cluster.local }\n",[186,2891,2892],{"class":188,"line":623},[186,2893,2894],{"class":289},"  redis: { host: valkey.tabularia-deps.svc.cluster.local }\n",[186,2896,2897,2900,2902,2905,2907,2910,2912],{"class":188,"line":652},[186,2898,2899],{"class":289},"  storage: { endpoint: \"",[186,2901,2671],{"class":203},[186,2903,2904],{"class":289},"\", bucket: \"",[186,2906,2685],{"class":203},[186,2908,2909],{"class":289},"\", region: \"",[186,2911,2678],{"class":203},[186,2913,2914],{"class":289},"\" }\n",[186,2916,2917],{"class":188,"line":771},[186,2918,2919],{"class":289},"secrets:\n",[186,2921,2922,2925,2927,2929,2932,2934,2936,2939],{"class":188,"line":846},[186,2923,2924],{"class":289},"  fernetKey: \"$(",[186,2926,122],{"class":192},[186,2928,1375],{"class":289},[186,2930,2931],{"class":293},"-base64",[186,2933,2106],{"class":293},[186,2935,1736],{"class":199},[186,2937,2938],{"class":192}," tr",[186,2940,2941],{"class":289}," '+\u002F' '-_')\"\n",[186,2943,2944,2947,2949,2951,2953,2955],{"class":188,"line":852},[186,2945,2946],{"class":289},"  jwtSecret: \"$(",[186,2948,122],{"class":192},[186,2950,1375],{"class":289},[186,2952,1378],{"class":293},[186,2954,2106],{"class":293},[186,2956,1384],{"class":289},[186,2958,2959,2962,2964],{"class":188,"line":858},[186,2960,2961],{"class":289},"  dbPassword: \"",[186,2963,2120],{"class":203},[186,2965,353],{"class":289},[186,2967,2968,2971,2973],{"class":188,"line":869},[186,2969,2970],{"class":289},"  storageAccessKey: \"",[186,2972,2692],{"class":203},[186,2974,353],{"class":289},[186,2976,2977,2980,2982],{"class":188,"line":875},[186,2978,2979],{"class":289},"  storageSecretKey: \"",[186,2981,2699],{"class":203},[186,2983,353],{"class":289},[186,2985,2986,2989,2991],{"class":188,"line":884},[186,2987,2988],{"class":289},"  adminPassword: \"",[186,2990,443],{"class":203},[186,2992,353],{"class":289},[186,2994,2995],{"class":188,"line":889},[186,2996,2997],{"class":289},"auth:\n",[186,2999,3000,3003,3005],{"class":188,"line":898},[186,3001,3002],{"class":289},"  adminEmail: \"",[186,3004,436],{"class":203},[186,3006,353],{"class":289},[186,3008,3009],{"class":188,"line":904},[186,3010,3011],{"class":289},"ingress:\n",[186,3013,3014],{"class":188,"line":910},[186,3015,3016],{"class":289},"  hosts:\n",[186,3018,3019],{"class":188,"line":916},[186,3020,3021],{"class":289},"    - host: \"\"\n",[186,3023,3024],{"class":188,"line":922},[186,3025,3026],{"class":289},"      paths: [{ path: \u002F, pathType: Prefix, service: frontend }]\n",[186,3028,3029],{"class":188,"line":927},[186,3030,3031],{"class":289},"networkPolicy:\n",[186,3033,3035],{"class":188,"line":3034},23,[186,3036,3037],{"class":289},"  enabled: true\n",[186,3039,3041],{"class":188,"line":3040},24,[186,3042,3043],{"class":289},"  egressCIDRs: [\"0.0.0.0\u002F0\"]\n",[186,3045,3047,3050,3052],{"class":188,"line":3046},25,[186,3048,3049],{"class":289},"  userCodePrivateCIDRs: [\"",[186,3051,2812],{"class":203},[186,3053,3054],{"class":289},"\u002F32\"]\n",[186,3056,3058],{"class":188,"line":3057},26,[186,3059,3060],{"class":289},"scripts:\n",[186,3062,3064],{"class":188,"line":3063},27,[186,3065,3037],{"class":289},[186,3067,3069],{"class":188,"line":3068},28,[186,3070,3071],{"class":289},"  registry:\n",[186,3073,3075],{"class":188,"line":3074},29,[186,3076,3077],{"class":289},"    repository: \"tabularia-registry:5000\u002Ftabularia-envs\"\n",[186,3079,3081],{"class":188,"line":3080},30,[186,3082,3083],{"class":289},"    pushSecret: registry\n",[186,3085,3087],{"class":188,"line":3086},31,[186,3088,3089],{"class":289},"    pullSecret: registry\n",[186,3091,3093],{"class":188,"line":3092},32,[186,3094,3095],{"class":289},"    caCert: |\n",[186,3097,3099,3102,3104,3107,3109],{"class":188,"line":3098},33,[186,3100,3101],{"class":289},"$(",[186,3103,1900],{"class":192},[186,3105,3106],{"class":289}," 's\u002F^\u002F      \u002F' \"",[186,3108,137],{"class":203},[186,3110,3111],{"class":289},"\u002Fca.crt\")\n",[186,3113,3115],{"class":188,"line":3114},34,[186,3116,3117],{"class":289},"  notebooks:\n",[186,3119,3121],{"class":188,"line":3120},35,[186,3122,3123],{"class":289},"    host: notebooks.localhost\n",[186,3125,3127],{"class":188,"line":3126},36,[186,3128,3129],{"class":289},"    origin: http:\u002F\u002Fnotebooks.localhost:8080\n",[186,3131,3133],{"class":188,"line":3132},37,[186,3134,3135],{"class":289},"containers:\n",[186,3137,3139],{"class":188,"line":3138},38,[186,3140,3037],{"class":289},[186,3142,3144],{"class":188,"line":3143},39,[186,3145,774],{"class":741},[14,3147,3148],{},"What some of them do:",[18,3150,3151,3160,3166,3172,3189],{},[21,3152,3153,3156,3157,3159],{},[120,3154,3155],{},"networkPolicy.egressCIDRs: [\"0.0.0.0\u002F0\"]"," turns the policy on: the workers reach the\ninternet (a cloud bucket, an external ClickHouse) and your databases, but never the\ncloud metadata address. A company lists its own ranges instead. The namespace\n",[120,3158,2080],{}," is allowed from the dependencies' host names.",[21,3161,3162,3165],{},[120,3163,3164],{},"networkPolicy.userCodePrivateCIDRs"," is for the scripts, their image builds and the\nContainer nodes. Their namespaces get a policy of their own: they reach the internet\nand the other namespaces of the cluster, never Tabularia's namespace, and no private\naddress outside the cluster unless it is listed here. List the registry's address\nonly, not kind's whole network: that network holds the nodes too.",[21,3167,3168,3171],{},[120,3169,3170],{},"scripts.registry.caCert"," is the authority of step 2: the image builds verify the\nregistry with it.",[21,3173,3174,3177,3178,3181,3182,1775,3185,3188],{},[120,3175,3176],{},"scripts.notebooks"," is the address of their own where the marimo notebooks' apps and\neditors open (step 10): a second host of the ingress, towards the gateway alone.\nBrowsers find ",[120,3179,3180],{},"notebooks.localhost"," on this computer by themselves, without DNS or\n",[120,3183,3184],{},"\u002Fetc\u002Fhosts",[120,3186,3187],{},"origin"," adds kind's port 8080.",[21,3190,3191,3194,3195,123,3198,3201,3202,3205],{},[120,3192,3193],{},"containers.enabled"," creates the namespaces where the Container nodes run,\n",[120,3196,3197],{},"tabularia-containers",[120,3199,3200],{},"tabularia-containers-dev",", at the ",[120,3203,3204],{},"baseline"," Pod Security\nlevel.",[61,3207,3209],{"id":3208},"9-install","9. Install",[14,3211,3212],{},"Tabularia's namespace, with the registry's password for the image downloads, then the\nchart:",[177,3214,3216],{"className":278,"code":3215,"language":280,"meta":182,"style":182},"kubectl create namespace tabularia\nkubectl create secret docker-registry registry --namespace tabularia \\\n  --docker-server=tabularia-registry:5000 --docker-username=tabularia --docker-password=\"$REGISTRY_PASSWORD\"\nhelm install tabularia infrastructure\u002Fhelm\u002Ftabularia --namespace tabularia --values \"$WORK\u002Fvalues.yaml\"\n",[120,3217,3218,3229,3249,3266],{"__ignoreMap":182},[186,3219,3220,3222,3224,3226],{"class":188,"line":189},[186,3221,112],{"class":192},[186,3223,932],{"class":289},[186,3225,2139],{"class":289},[186,3227,3228],{"class":289}," tabularia\n",[186,3230,3231,3233,3235,3237,3240,3243,3245,3247],{"class":188,"line":196},[186,3232,112],{"class":192},[186,3234,932],{"class":289},[186,3236,2151],{"class":289},[186,3238,3239],{"class":289}," docker-registry",[186,3241,3242],{"class":289}," registry",[186,3244,1038],{"class":293},[186,3246,1445],{"class":289},[186,3248,416],{"class":415},[186,3250,3251,3254,3257,3260,3262,3264],{"class":188,"line":207},[186,3252,3253],{"class":293},"  --docker-server=tabularia-registry:5000",[186,3255,3256],{"class":293}," --docker-username=tabularia",[186,3258,3259],{"class":293}," --docker-password=",[186,3261,330],{"class":289},[186,3263,1395],{"class":203},[186,3265,353],{"class":289},[186,3267,3268,3271,3273,3275,3278,3280,3282,3285,3287,3289],{"class":188,"line":339},[186,3269,3270],{"class":192},"helm",[186,3272,1108],{"class":289},[186,3274,1445],{"class":289},[186,3276,3277],{"class":289}," infrastructure\u002Fhelm\u002Ftabularia",[186,3279,1038],{"class":293},[186,3281,1445],{"class":289},[186,3283,3284],{"class":293}," --values",[186,3286,348],{"class":289},[186,3288,137],{"class":203},[186,3290,3291],{"class":289},"\u002Fvalues.yaml\"\n",[14,3293,3294],{},"The chart creates the three namespaces of the scripts (and the two of the Container\nnodes, step 11). The scripts' ones need the registry's password\ntoo: the builds push with it, the scripts pull with it.",[177,3296,3298],{"className":278,"code":3297,"language":280,"meta":182,"style":182},"for ns in tabularia-scripts tabularia-scripts-dev tabularia-script-builds; do\n  kubectl create secret docker-registry registry --namespace \"$ns\" \\\n    --docker-server=tabularia-registry:5000 --docker-username=tabularia --docker-password=\"$REGISTRY_PASSWORD\"\ndone\nkubectl get pods --namespace tabularia --watch\n",[120,3299,3300,3322,3346,3361,3365],{"__ignoreMap":182},[186,3301,3302,3304,3307,3309,3312,3315,3318,3320],{"class":188,"line":189},[186,3303,1757],{"class":199},[186,3305,3306],{"class":203}," ns ",[186,3308,1763],{"class":199},[186,3310,3311],{"class":289}," tabularia-scripts",[186,3313,3314],{"class":289}," tabularia-scripts-dev",[186,3316,3317],{"class":289}," tabularia-script-builds",[186,3319,1775],{"class":203},[186,3321,1778],{"class":199},[186,3323,3324,3327,3329,3331,3333,3335,3337,3339,3342,3344],{"class":188,"line":196},[186,3325,3326],{"class":192},"  kubectl",[186,3328,932],{"class":289},[186,3330,2151],{"class":289},[186,3332,3239],{"class":289},[186,3334,3242],{"class":289},[186,3336,1038],{"class":293},[186,3338,348],{"class":289},[186,3340,3341],{"class":203},"$ns",[186,3343,330],{"class":289},[186,3345,416],{"class":415},[186,3347,3348,3351,3353,3355,3357,3359],{"class":188,"line":207},[186,3349,3350],{"class":293},"    --docker-server=tabularia-registry:5000",[186,3352,3256],{"class":293},[186,3354,3259],{"class":293},[186,3356,330],{"class":289},[186,3358,1395],{"class":203},[186,3360,353],{"class":289},[186,3362,3363],{"class":188,"line":339},[186,3364,1852],{"class":199},[186,3366,3367,3369,3371,3374,3376,3378],{"class":188,"line":564},[186,3368,112],{"class":192},[186,3370,1070],{"class":289},[186,3372,3373],{"class":289}," pods",[186,3375,1038],{"class":293},[186,3377,1445],{"class":289},[186,3379,3380],{"class":293}," --watch\n",[14,3382,3383,3384,3387,3388,3393,3394,123,3396,127],{},"When every pod is ",[120,3385,3386],{},"1\u002F1 Running",", press Ctrl+C and open ",[70,3389,3390],{},[52,3391,269],{"href":269,"rel":3392},[99],". Sign in\nwith ",[120,3395,436],{},[120,3397,443],{},[14,3399,3400,3403],{},[70,3401,3402],{},"Administration → Cluster"," shows, refreshed every three seconds, the pods of Tabularia\nand, from step 10, the Jobs of the scripts and containers while they run, each linked to\nthe flow or script that started it: their state, restarts, CPU and memory against their\nlimits, and the cluster's warning events.",[61,3405,3407],{"id":3406},"10-python-and-r-scripts","10. Python and R scripts",[14,3409,3410,3411,3414],{},"In ",[70,3412,3413],{},"Administration → Scripting environments",", the scripts bucket of step 7:",[2452,3416,3417,3430],{},[2455,3418,3419],{},[2458,3420,3421,3424],{},[2461,3422,3423],{},"Field",[2461,3425,3426,3427,3429],{},"Value (",[120,3428,1389],{}," it in the terminal)",[2471,3431,3432,3440,3449,3458,3467],{},[2458,3433,3434,3436],{},[2476,3435,2466],{},[2476,3437,3438],{},[120,3439,2725],{},[2458,3441,3442,3445],{},[2476,3443,3444],{},"Region",[2476,3446,3447],{},[120,3448,2732],{},[2458,3450,3451,3454],{},[2476,3452,3453],{},"Bucket",[2476,3455,3456],{},[120,3457,2739],{},[2458,3459,3460,3463],{},[2476,3461,3462],{},"Access key",[2476,3464,3465],{},[120,3466,2746],{},[2458,3468,3469,3472],{},[2476,3470,3471],{},"Secret key",[2476,3473,3474],{},[120,3475,2753],{},[14,3477,3478,3481,3482,3484,3485,3489,3490,127],{},[70,3479,3480],{},"Test"," answers that it wrote, read and deleted a probe object, and that the key cannot\nlist the bucket. Then create an environment, for example Python 3.12. Its image is built\nin the cluster, pushed to the private registry with the company's authority, checked,\nand marked ",[70,3483,1079],{}," after a few minutes. Script nodes can use it: see\n",[52,3486,3488],{"href":3487},"\u002Fdocs\u002Fscripting","Python and R in flows",". Scripts can also be kept on their own, next to\nflows and views: ",[70,3491,3492],{},"Explore → New → Script",[14,3494,3495,3496,3499,3500,1688,3503,3506,3507,3510,3511,3513,3514,3517,3518,3522,3523,3525,3526,3529,3530,3532,3533,3536],{},"A Python environment also runs ",[70,3497,3498],{},"marimo notebooks",": ",[70,3501,3502],{},"Explore → New → Notebook\n(marimo)",[70,3504,3505],{},"Open app"," or ",[70,3508,3509],{},"Open editor",". Tabularia's page at\n",[120,3512,269],{}," shows the notebook in a frame from\n",[120,3515,3516],{},"http:\u002F\u002Fnotebooks.localhost:8080",", the address of step 8: a notebook's code runs there,\naway from your Tabularia session (",[52,3519,3521],{"href":3520},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks#an-address-of-their-own","An address of their own",").\n",[70,3524,1244],{}," ",[120,3527,3528],{},"curl -s -o \u002Fdev\u002Fnull -w '%{http_code}\\n' http:\u002F\u002Fnotebooks.localhost:8080\u002Fapi\u002Fhealth","\nanswers ",[120,3531,1251],{}," (the gateway answers there only for the notebooks), and so does\n",[120,3534,3535],{},"http:\u002F\u002Fnotebooks.localhost:8080\u002F"," (no interface on that host).",[61,3538,3540],{"id":3539},"11-images-of-your-own","11. Images of your own",[14,3542,3543],{},"The Container node runs an image of yours as a step of a flow. Put one in the registry,\nfor example a small one to begin with:",[177,3545,3547],{"className":278,"code":3546,"language":280,"meta":182,"style":182},"export DOCKER_CONFIG=\"$WORK\u002Fdocker\"\ndocker pull busybox:1.36.1\ndocker tag busybox:1.36.1 127.0.0.1:5443\u002Fteam\u002Fbusybox:1.36\ndocker push 127.0.0.1:5443\u002Fteam\u002Fbusybox:1.36\nunset DOCKER_CONFIG\n",[120,3548,3549,3563,3572,3584,3592],{"__ignoreMap":182},[186,3550,3551,3553,3555,3557,3559,3561],{"class":188,"line":189},[186,3552,313],{"class":199},[186,3554,1714],{"class":203},[186,3556,319],{"class":199},[186,3558,330],{"class":289},[186,3560,137],{"class":203},[186,3562,1723],{"class":289},[186,3564,3565,3567,3569],{"class":188,"line":196},[186,3566,1424],{"class":192},[186,3568,1786],{"class":289},[186,3570,3571],{"class":289}," busybox:1.36.1\n",[186,3573,3574,3576,3578,3581],{"class":188,"line":207},[186,3575,1424],{"class":192},[186,3577,1808],{"class":289},[186,3579,3580],{"class":289}," busybox:1.36.1",[186,3582,3583],{"class":289}," 127.0.0.1:5443\u002Fteam\u002Fbusybox:1.36\n",[186,3585,3586,3588,3590],{"class":188,"line":339},[186,3587,1424],{"class":192},[186,3589,1837],{"class":289},[186,3591,3583],{"class":289},[186,3593,3594,3596],{"class":188,"line":564},[186,3595,1857],{"class":293},[186,3597,1860],{"class":289},[14,3599,3410,3600,101,3603,1702],{},[70,3601,3602],{},"Administration → Container registries",[70,3604,3605],{},"New registry",[2452,3607,3608,3617],{},[2455,3609,3610],{},[2458,3611,3612,3614],{},[2461,3613,3423],{},[2461,3615,3616],{},"Value",[2471,3618,3619,3629,3638,3646,3663,3672,3685],{},[2458,3620,3621,3624],{},[2476,3622,3623],{},"Name",[2476,3625,3626],{},[120,3627,3628],{},"Company registry",[2458,3630,3631,3634],{},[2476,3632,3633],{},"Address",[2476,3635,3636],{},[120,3637,1349],{},[2458,3639,3640,3643],{},[2476,3641,3642],{},"Protocol",[2476,3644,3645],{},"HTTPS",[2458,3647,3648,3651],{},[2476,3649,3650],{},"Certificate",[2476,3652,3653,3656,3657,253,3660,2491],{},[70,3654,3655],{},"Company authority",", and paste the content of ",[120,3658,3659],{},"$WORK\u002Fca.crt",[120,3661,3662],{},"cat \"$WORK\u002Fca.crt\"",[2458,3664,3665,3668],{},[2476,3666,3667],{},"User",[2476,3669,3670],{},[120,3671,2401],{},[2458,3673,3674,3677],{},[2476,3675,3676],{},"Password or token",[2476,3678,3679,3680,253,3682,2491],{},"the value of ",[120,3681,1395],{},[120,3683,3684],{},"echo \"$REGISTRY_PASSWORD\"",[2458,3686,3687,3690],{},[2476,3688,3689],{},"Allowed repositories",[2476,3691,3692],{},[120,3693,3694],{},"team\u002F*",[14,3696,3697],{},"Then, on the registry's page:",[18,3699,3700,3708],{},[21,3701,3702,3704,3705,3707],{},[70,3703,3480],{}," answers that the registry lets Tabularia in as ",[120,3706,2401],{},";",[21,3709,3710,3713,3714,123,3717,3720,3721,127],{},[70,3711,3712],{},"Try in the cluster",", with ",[120,3715,3716],{},"team\u002Fbusybox",[120,3718,3719],{},"1.36",", answers that a node of the\ncluster downloaded it: the nodes trust the authority (step 3) and Tabularia gave them\nthe password, in a pull Secret of its own in ",[120,3722,3200],{},[14,3724,3725,3726,3729,3730,3732,3733,3735,3736,123,3739,3742,3743,3746,3747,3750,3751,3755],{},"In a flow, the palette now offers ",[70,3727,3728],{},"Container",": choose the registry, the image\n",[120,3731,3716],{},", the tag ",[120,3734,3719],{},", and as command ",[120,3737,3738],{},"sh",[120,3740,3741],{},"-c"," (one per line) with the\nargument ",[120,3744,3745],{},"echo hello from $HOSTNAME",". ",[70,3748,3749],{},"Run"," in its panel runs it, and its log shows the\ngreeting. ",[52,3752,3754],{"href":3753},"\u002Fdocs\u002Fcontainer-node","The Container node"," explains its input and output,\nits variables and the rest.",[14,3757,3758],{},"Tabularia needs no Secret of yours in the containers' namespaces: it writes the\nregistry's own from the credentials above, right before each run.",[61,3760,3762],{"id":3761},"12-optional-flows-on-an-external-clickhouse","12. Optional: flows on an external ClickHouse",[14,3764,3765,3766,3769],{},"Tabularia can send the work of a flow to a ClickHouse service instead of doing it in its\nworkers. ClickHouse then reads and writes the files of the bucket directly, so this\ngoes with ",[70,3767,3768],{},"the object storage of your cloud"," (step 7), with the ClickHouse service in\nthe same cloud and region. A ClickHouse service reachable over HTTPS (port 8443), a user\nand its password are needed.",[14,3771,3772,3775],{},[70,3773,3774],{},"In ClickHouse",", in the service's SQL console, as its administrator:",[177,3777,3781],{"className":3778,"code":3779,"language":3780,"meta":182,"style":182},"language-sql shiki shiki-themes github-dark-dimmed github-light","CREATE DATABASE IF NOT EXISTS tabularia;\nCREATE NAMED COLLECTION tabularia_s3 AS\n    access_key_id = '\u003Caccess key of the data bucket>',\n    secret_access_key = '\u003Cits secret key>';\n","sql",[120,3782,3783,3804,3820,3832],{"__ignoreMap":182},[186,3784,3785,3788,3791,3795,3798,3801],{"class":188,"line":189},[186,3786,3787],{"class":199},"CREATE",[186,3789,3790],{"class":199}," DATABASE",[186,3792,3794],{"class":3793},"saVmf"," IF",[186,3796,3797],{"class":199}," NOT",[186,3799,3800],{"class":199}," EXISTS",[186,3802,3803],{"class":203}," tabularia;\n",[186,3805,3806,3808,3811,3814,3817],{"class":188,"line":196},[186,3807,3787],{"class":199},[186,3809,3810],{"class":203}," NAMED ",[186,3812,3813],{"class":199},"COLLECTION",[186,3815,3816],{"class":203}," tabularia_s3 ",[186,3818,3819],{"class":199},"AS\n",[186,3821,3822,3825,3827,3830],{"class":188,"line":207},[186,3823,3824],{"class":203},"    access_key_id ",[186,3826,319],{"class":199},[186,3828,3829],{"class":289}," '\u003Caccess key of the data bucket>'",[186,3831,113],{"class":203},[186,3833,3834,3837,3839,3842],{"class":188,"line":339},[186,3835,3836],{"class":203},"    secret_access_key ",[186,3838,319],{"class":199},[186,3840,3841],{"class":289}," '\u003Cits secret key>'",[186,3843,3844],{"class":203},";\n",[14,3846,1237,3847,3851],{},[3848,3849,3850],"em",{},"named collection"," keeps the bucket's keys inside ClickHouse, so they never travel\nin the queries Tabularia sends. Tabularia is better given a user of its own than the\nadministrator; these grants are enough for it:",[177,3853,3855],{"className":3778,"code":3854,"language":3780,"meta":182,"style":182},"GRANT CREATE TEMPORARY TABLE, S3 ON *.* TO `\u003Cclickhouse-user>`;\nGRANT NAMED COLLECTION ON tabularia_s3 TO `\u003Cclickhouse-user>`;\n",[120,3856,3857,3893],{"__ignoreMap":182},[186,3858,3859,3862,3865,3868,3871,3874,3877,3880,3882,3885,3888,3891],{"class":188,"line":189},[186,3860,3861],{"class":199},"GRANT",[186,3863,3864],{"class":199}," CREATE",[186,3866,3867],{"class":203}," TEMPORARY ",[186,3869,3870],{"class":199},"TABLE",[186,3872,3873],{"class":203},", S3 ",[186,3875,3876],{"class":199},"ON",[186,3878,3879],{"class":199}," *",[186,3881,127],{"class":203},[186,3883,3884],{"class":199},"*",[186,3886,3887],{"class":199}," TO",[186,3889,3890],{"class":289}," `\u003Cclickhouse-user>`",[186,3892,3844],{"class":203},[186,3894,3895,3897,3899,3901,3904,3906,3909,3911],{"class":188,"line":196},[186,3896,3861],{"class":199},[186,3898,3810],{"class":203},[186,3900,3813],{"class":199},[186,3902,3903],{"class":199}," ON",[186,3905,3816],{"class":203},[186,3907,3908],{"class":199},"TO",[186,3910,3890],{"class":289},[186,3912,3844],{"class":203},[14,3914,3915,3916,127],{},"With those alone the Viewer reads the bucket's files at every query. To let it keep a\ncopy of the datasets over five million rows inside ClickHouse, which is faster, also\n",[120,3917,3918],{},"GRANT CREATE TABLE, DROP TABLE, INSERT, SELECT ON tabularia.* TO \u003Cclickhouse-user>;",[14,3920,3921,3924,3925,3927],{},[70,3922,3923],{},"In the terminal",", the service and the user (replace every ",[120,3926,2541],{},"), then the settings\nadded to those of step 8:",[177,3929,3931],{"className":278,"code":3930,"language":280,"meta":182,"style":182},"export CLICKHOUSE_HOST=\"\u003Cyour-service.clickhouse.example>\" CLICKHOUSE_USER=\"\u003Cclickhouse-user>\" \\\n  CLICKHOUSE_PASSWORD=\"\u003Cits password>\"\nprintf 'export CLICKHOUSE_HOST=%q CLICKHOUSE_USER=%q CLICKHOUSE_PASSWORD=%q\\n' \\\n  \"$CLICKHOUSE_HOST\" \"$CLICKHOUSE_USER\" \"$CLICKHOUSE_PASSWORD\" >> \"$WORK\u002Fenv\"\ncat > \"$WORK\u002Fclickhouse.yaml\" \u003C\u003CEOF\nexternalServices:\n  clickhouse:\n    enabled: true\n    host: \"$CLICKHOUSE_HOST\"\n    port: 8443\n    secure: true\n    database: tabularia\n    username: \"$CLICKHOUSE_USER\"\n    transport: s3\n    s3Endpoint: \"$STORAGE_ENDPOINT\"\n    s3NamedCollection: tabularia_s3\nsecrets:\n  clickhousePassword: \"$CLICKHOUSE_PASSWORD\"\nEOF\nhelm upgrade tabularia infrastructure\u002Fhelm\u002Ftabularia --namespace tabularia \\\n  --values \"$WORK\u002Fvalues.yaml\" --values \"$WORK\u002Fclickhouse.yaml\"\n",[120,3932,3933,3955,3965,3974,4005,4022,4026,4031,4036,4045,4050,4055,4060,4069,4074,4083,4088,4092,4101,4105,4122],{"__ignoreMap":182},[186,3934,3935,3937,3940,3942,3945,3948,3950,3953],{"class":188,"line":189},[186,3936,313],{"class":199},[186,3938,3939],{"class":203}," CLICKHOUSE_HOST",[186,3941,319],{"class":199},[186,3943,3944],{"class":289},"\"\u003Cyour-service.clickhouse.example>\"",[186,3946,3947],{"class":203}," CLICKHOUSE_USER",[186,3949,319],{"class":199},[186,3951,3952],{"class":289},"\"\u003Cclickhouse-user>\"",[186,3954,416],{"class":203},[186,3956,3957,3960,3962],{"class":188,"line":196},[186,3958,3959],{"class":203},"  CLICKHOUSE_PASSWORD",[186,3961,319],{"class":199},[186,3963,3964],{"class":289},"\"\u003Cits password>\"\n",[186,3966,3967,3969,3972],{"class":188,"line":207},[186,3968,409],{"class":293},[186,3970,3971],{"class":289}," 'export CLICKHOUSE_HOST=%q CLICKHOUSE_USER=%q CLICKHOUSE_PASSWORD=%q\\n'",[186,3973,416],{"class":415},[186,3975,3976,3978,3981,3983,3985,3988,3990,3992,3995,3997,3999,4001,4003],{"class":188,"line":339},[186,3977,421],{"class":289},[186,3979,3980],{"class":203},"$CLICKHOUSE_HOST",[186,3982,330],{"class":289},[186,3984,348],{"class":289},[186,3986,3987],{"class":203},"$CLICKHOUSE_USER",[186,3989,330],{"class":289},[186,3991,348],{"class":289},[186,3993,3994],{"class":203},"$CLICKHOUSE_PASSWORD",[186,3996,330],{"class":289},[186,3998,1400],{"class":199},[186,4000,348],{"class":289},[186,4002,137],{"class":203},[186,4004,455],{"class":289},[186,4006,4007,4009,4011,4013,4015,4018,4020],{"class":188,"line":564},[186,4008,726],{"class":192},[186,4010,448],{"class":199},[186,4012,348],{"class":289},[186,4014,137],{"class":203},[186,4016,4017],{"class":289},"\u002Fclickhouse.yaml\"",[186,4019,738],{"class":199},[186,4021,774],{"class":741},[186,4023,4024],{"class":188,"line":581},[186,4025,2884],{"class":289},[186,4027,4028],{"class":188,"line":623},[186,4029,4030],{"class":289},"  clickhouse:\n",[186,4032,4033],{"class":188,"line":652},[186,4034,4035],{"class":289},"    enabled: true\n",[186,4037,4038,4041,4043],{"class":188,"line":771},[186,4039,4040],{"class":289},"    host: \"",[186,4042,3980],{"class":203},[186,4044,353],{"class":289},[186,4046,4047],{"class":188,"line":846},[186,4048,4049],{"class":289},"    port: 8443\n",[186,4051,4052],{"class":188,"line":852},[186,4053,4054],{"class":289},"    secure: true\n",[186,4056,4057],{"class":188,"line":858},[186,4058,4059],{"class":289},"    database: tabularia\n",[186,4061,4062,4065,4067],{"class":188,"line":869},[186,4063,4064],{"class":289},"    username: \"",[186,4066,3987],{"class":203},[186,4068,353],{"class":289},[186,4070,4071],{"class":188,"line":875},[186,4072,4073],{"class":289},"    transport: s3\n",[186,4075,4076,4079,4081],{"class":188,"line":884},[186,4077,4078],{"class":289},"    s3Endpoint: \"",[186,4080,2671],{"class":203},[186,4082,353],{"class":289},[186,4084,4085],{"class":188,"line":889},[186,4086,4087],{"class":289},"    s3NamedCollection: tabularia_s3\n",[186,4089,4090],{"class":188,"line":898},[186,4091,2919],{"class":289},[186,4093,4094,4097,4099],{"class":188,"line":904},[186,4095,4096],{"class":289},"  clickhousePassword: \"",[186,4098,3994],{"class":203},[186,4100,353],{"class":289},[186,4102,4103],{"class":188,"line":910},[186,4104,774],{"class":741},[186,4106,4107,4109,4112,4114,4116,4118,4120],{"class":188,"line":916},[186,4108,3270],{"class":192},[186,4110,4111],{"class":289}," upgrade",[186,4113,1445],{"class":289},[186,4115,3277],{"class":289},[186,4117,1038],{"class":293},[186,4119,1445],{"class":289},[186,4121,416],{"class":415},[186,4123,4124,4127,4129,4131,4133,4135,4137,4139],{"class":188,"line":922},[186,4125,4126],{"class":293},"  --values",[186,4128,348],{"class":289},[186,4130,137],{"class":203},[186,4132,2860],{"class":289},[186,4134,3284],{"class":293},[186,4136,348],{"class":289},[186,4138,137],{"class":203},[186,4140,4141],{"class":289},"\u002Fclickhouse.yaml\"\n",[14,4143,4144,4145,4148,4149,4152,4153,4156,4157,4160,4161,4164],{},"After a minute, ",[70,4146,4147],{},"Administration → Engines"," lists ClickHouse. Choose it as your\n",[70,4150,4151],{},"Preferred engine"," in ",[70,4154,4155],{},"Settings",", or as the engine of a flow, and open a datasource in\nthe ",[70,4158,4159],{},"Viewer",": its rows come from ClickHouse. Every later ",[120,4162,4163],{},"helm upgrade"," takes both\nfiles, in this order.",[61,4166,4168],{"id":4167},"13-optional-play-with-sample-data","13. Optional: play with sample data",[14,4170,4171,4172,4175,4176,4178],{},"The Docker Compose stack comes with a fictional cured-meats company to try Tabularia on:\nan order system on PostgreSQL, a CRM on ClickHouse, and a ",[120,4173,4174],{},"Sample"," folder in Tabularia\nbuilt on them. The same on this cluster: the two databases run in ",[120,4177,2080],{},", next\nto Tabularia's PostgreSQL and Valkey, and a Job fills them, then builds the folder\nthrough Tabularia's API, signed in as the administrator of step 1.",[14,4180,4181],{},"The Job's image is built from the clone and handed to the nodes directly, without the\nregistry: it runs once and is never downloaded again.",[177,4183,4185],{"className":278,"code":4184,"language":280,"meta":182,"style":182},"docker build -t tabularia-sampledb:local infrastructure\u002Fsampledb\nkind load docker-image tabularia-sampledb:local --name tabularia\nkubectl create secret generic sampledb-admin --namespace tabularia-deps \\\n  --from-literal=email=\"$ADMIN_EMAIL\" --from-literal=password=\"$ADMIN_PASSWORD\"\nkubectl apply -f infrastructure\u002Fkind\u002Fsamples.yaml\nuntil kubectl get job sampledb-init --namespace tabularia-deps -o jsonpath='{.status.conditions[*].type}' \\\n  | grep -qE 'Complete|Failed'; do sleep 5; done\nkubectl logs --namespace tabularia-deps job\u002Fsampledb-init --tail=1\n",[120,4186,4187,4201,4217,4236,4255,4266,4293,4320],{"__ignoreMap":182},[186,4188,4189,4191,4193,4195,4198],{"class":188,"line":189},[186,4190,1424],{"class":192},[186,4192,1970],{"class":289},[186,4194,1973],{"class":293},[186,4196,4197],{"class":289}," tabularia-sampledb:local",[186,4199,4200],{"class":289}," infrastructure\u002Fsampledb\n",[186,4202,4203,4205,4208,4211,4213,4215],{"class":188,"line":196},[186,4204,106],{"class":192},[186,4206,4207],{"class":289}," load",[186,4209,4210],{"class":289}," docker-image",[186,4212,4197],{"class":289},[186,4214,1472],{"class":293},[186,4216,3228],{"class":289},[186,4218,4219,4221,4223,4225,4227,4230,4232,4234],{"class":188,"line":207},[186,4220,112],{"class":192},[186,4222,932],{"class":289},[186,4224,2151],{"class":289},[186,4226,2154],{"class":289},[186,4228,4229],{"class":289}," sampledb-admin",[186,4231,1038],{"class":293},[186,4233,2162],{"class":289},[186,4235,416],{"class":415},[186,4237,4238,4241,4243,4245,4247,4249,4251,4253],{"class":188,"line":339},[186,4239,4240],{"class":293},"  --from-literal=email=",[186,4242,330],{"class":289},[186,4244,436],{"class":203},[186,4246,330],{"class":289},[186,4248,2165],{"class":293},[186,4250,330],{"class":289},[186,4252,443],{"class":203},[186,4254,353],{"class":289},[186,4256,4257,4259,4261,4263],{"class":188,"line":564},[186,4258,112],{"class":192},[186,4260,1010],{"class":289},[186,4262,1013],{"class":293},[186,4264,4265],{"class":289}," infrastructure\u002Fkind\u002Fsamples.yaml\n",[186,4267,4268,4271,4274,4276,4279,4282,4284,4286,4288,4291],{"class":188,"line":581},[186,4269,4270],{"class":199},"until",[186,4272,4273],{"class":192}," kubectl",[186,4275,1070],{"class":289},[186,4277,4278],{"class":289}," job",[186,4280,4281],{"class":289}," sampledb-init",[186,4283,1038],{"class":293},[186,4285,2162],{"class":289},[186,4287,1594],{"class":293},[186,4289,4290],{"class":289}," jsonpath='{.status.conditions[*].type}'",[186,4292,416],{"class":415},[186,4294,4295,4297,4300,4303,4306,4308,4310,4313,4316,4318],{"class":188,"line":623},[186,4296,975],{"class":199},[186,4298,4299],{"class":192}," grep",[186,4301,4302],{"class":293}," -qE",[186,4304,4305],{"class":289}," 'Complete|Failed'",[186,4307,1775],{"class":203},[186,4309,2043],{"class":199},[186,4311,4312],{"class":192}," sleep",[186,4314,4315],{"class":293}," 5",[186,4317,1775],{"class":203},[186,4319,1852],{"class":199},[186,4321,4322,4324,4327,4329,4331,4334],{"class":188,"line":652},[186,4323,112],{"class":192},[186,4325,4326],{"class":289}," logs",[186,4328,1038],{"class":293},[186,4330,2162],{"class":289},[186,4332,4333],{"class":289}," job\u002Fsampledb-init",[186,4335,4336],{"class":293}," --tail=1\n",[14,4338,1237,4339,4341,4342,4345,4346,4349,4350,4353],{},[120,4340,4270],{}," waits for the Job to end; ",[120,4343,4344],{},"kubectl logs --namespace tabularia-deps --follow job\u002Fsampledb-init",", in another terminal, shows it working. The data takes seconds at the\n",[120,4347,4348],{},"small"," scale; the Job then imports the tables, runs the eleven sample flows once and\nmakes the views, the reports and the dashboard, about two and a half minutes in all. The\nlast line is ",[120,4351,4352],{},"DONE. The «Sample» folder is ready: …","; a flow that failed is named instead,\nand running the Job again (below) retries only those.",[14,4355,4356,4357,4359],{},"Reload Explore. The ",[120,4358,4174],{}," folder holds:",[2452,4361,4362,4375],{},[2455,4363,4364],{},[2458,4365,4366,4369,4372],{},[2461,4367,4368],{},"What",[2461,4370,4371],{},"Where",[2461,4373,4374],{},"What to expect",[2471,4376,4377,4398,4421,4436,4474],{},[2458,4378,4379,4382,4386],{},[2476,4380,4381],{},"Two connections",[2476,4383,4384],{},[120,4385,4174],{},[2476,4387,4388,123,4391,3499,4394,4397],{},[3848,4389,4390],{},"Sample ERP (Postgres)",[3848,4392,4393],{},"Sample CRM (ClickHouse)",[70,4395,4396],{},"Connections"," lists them, and a table can be imported from them",[2458,4399,4400,4403,4407],{},[2476,4401,4402],{},"Three folders",[2476,4404,4405],{},[120,4406,4174],{},[2476,4408,4409,4412,4413,4416,4417,4420],{},[3848,4410,4411],{},"Flows"," (eleven flows, three of them scheduled), ",[3848,4414,4415],{},"Private tables"," (the company's tables and the flows' results), ",[3848,4418,4419],{},"Public tables"," (a calendar, the regions)",[2458,4422,4423,4426,4430],{},[2476,4424,4425],{},"Four saved views",[2476,4427,4428],{},[120,4429,4174],{},[2476,4431,4432,4433,4435],{},"a click opens each in the ",[70,4434,4159],{},": the categories under 30% of margin, the margin as a matrix, the channels that cost the most to serve, the agents of Nord-Ovest",[2458,4437,4438,4441,4449],{},[2476,4439,4440],{},"Two reports",[2476,4442,4443,79,4446,4448],{},[70,4444,4445],{},"Analysis → Reports",[120,4447,4174],{}," in Explore",[2476,4450,4451,4454,4455,4458,4459,123,4462,4465,4466,4469,4470],{},[3848,4452,4453],{},"Margin review",": two pages, 2025 against 2024, with a list of categories and the months; ",[3848,4456,4457],{},"Agent scorecard",": three pages, with an Area list and an Agent list that follows it. ",[70,4460,4461],{},"PDF",[70,4463,4464],{},"Excel"," download them, ",[70,4467,4468],{},"Edit"," opens them in the designer: ",[52,4471,4473],{"href":4472},"\u002Fdocs\u002Fcapabilities\u002Freports#the-sample-reports","what to look at in them",[2458,4475,4476,4479,4486],{},[2476,4477,4478],{},"A dashboard",[2476,4480,4481,79,4484,4448],{},[70,4482,4483],{},"Analysis → Dashboards",[120,4485,4174],{},[2476,4487,4488,3499,4491],{},[3848,4489,4490],{},"Sales overview",[52,4492,4494],{"href":4493},"\u002Fdocs\u002Fcapabilities\u002Fdashboards#example-sales-overview","tiles that answer a click",[14,4496,4497,4498,4502,4503,4505],{},"The reports draw their charts with the chart service, a sidecar of the worker pods: if one says\nthe service does not answer, those pods are not ready yet. As on Docker Compose, the folder has no\npermissions on it: other people see it once the administrator shares it. A colleague's\naccount starts with their ",[52,4499,4501],{"href":4500},"\u002Fdocs\u002Fgovernance\u002Fpermissions#personal-folders","personal folder","\nalone, where they can build but not create connections: to try the samples they need VIEW\non ",[120,4504,4174],{},", and CONNECT too to import from its two connections.",[18,4507,4508,4530,4635,4652],{},[21,4509,4510,3499,4513,4152,4516,4519,4520,3844,4522,4525,4526,4529],{},[70,4511,4512],{},"More data",[120,4514,4515],{},"SAMPLES_SCALE",[120,4517,4518],{},"infrastructure\u002Fkind\u002Fsamples.yaml"," is ",[120,4521,4348],{},[120,4523,4524],{},"medium"," (3.5 million order lines) takes a few minutes. Choose it before the first\n",[120,4527,4528],{},"apply",": the Job generates the data only into empty databases.",[21,4531,4532,4535,4536,4539,4540,123,4543,4546,4547],{},[70,4533,4534],{},"Run it again"," (a flow that failed, a folder deleted by mistake, or a new release\nof Tabularia that brings new samples): it duplicates nothing and only adds what is\nmissing. ",[120,4537,4538],{},"kubectl apply"," alone does not start it again, since the Job of the first\ntime is still there. For a new release, run the ",[120,4541,4542],{},"docker build",[120,4544,4545],{},"kind load"," above\nfirst.",[177,4548,4550],{"className":278,"code":4549,"language":280,"meta":182,"style":182},"kubectl delete job sampledb-init --namespace tabularia-deps\nkubectl apply -f infrastructure\u002Fkind\u002Fsamples.yaml\nuntil kubectl get job sampledb-init --namespace tabularia-deps -o jsonpath='{.status.conditions[*].type}' \\\n  | grep -qE 'Complete|Failed'; do sleep 5; done\nkubectl logs --namespace tabularia-deps job\u002Fsampledb-init --tail=1\n",[120,4551,4552,4567,4577,4599,4621],{"__ignoreMap":182},[186,4553,4554,4556,4559,4561,4563,4565],{"class":188,"line":189},[186,4555,112],{"class":192},[186,4557,4558],{"class":289}," delete",[186,4560,4278],{"class":289},[186,4562,4281],{"class":289},[186,4564,1038],{"class":293},[186,4566,2142],{"class":289},[186,4568,4569,4571,4573,4575],{"class":188,"line":196},[186,4570,112],{"class":192},[186,4572,1010],{"class":289},[186,4574,1013],{"class":293},[186,4576,4265],{"class":289},[186,4578,4579,4581,4583,4585,4587,4589,4591,4593,4595,4597],{"class":188,"line":207},[186,4580,4270],{"class":199},[186,4582,4273],{"class":192},[186,4584,1070],{"class":289},[186,4586,4278],{"class":289},[186,4588,4281],{"class":289},[186,4590,1038],{"class":293},[186,4592,2162],{"class":289},[186,4594,1594],{"class":293},[186,4596,4290],{"class":289},[186,4598,416],{"class":415},[186,4600,4601,4603,4605,4607,4609,4611,4613,4615,4617,4619],{"class":188,"line":339},[186,4602,975],{"class":199},[186,4604,4299],{"class":192},[186,4606,4302],{"class":293},[186,4608,4305],{"class":289},[186,4610,1775],{"class":203},[186,4612,2043],{"class":199},[186,4614,4312],{"class":192},[186,4616,4315],{"class":293},[186,4618,1775],{"class":203},[186,4620,1852],{"class":199},[186,4622,4623,4625,4627,4629,4631,4633],{"class":188,"line":564},[186,4624,112],{"class":192},[186,4626,4326],{"class":289},[186,4628,1038],{"class":293},[186,4630,2162],{"class":289},[186,4632,4333],{"class":289},[186,4634,4336],{"class":293},[21,4636,4637,4640,4641,4643,4644,4647,4648,4651],{},[70,4638,4639],{},"Who reaches the databases",": Tabularia's workers, which already reach\n",[120,4642,2080],{}," (step 8), at ",[120,4645,4646],{},"sampledb-postgres.tabularia-deps.svc.cluster.local:5432","\nand ",[120,4649,4650],{},"sampledb-clickhouse.tabularia-deps.svc.cluster.local:8123",". A network policy keeps\nthe scripts and the Container nodes out, as for Tabularia's own database.",[21,4653,4654,3499,4657,4660,4661,4664,4665,4667],{},[70,4655,4656],{},"Remove them",[120,4658,4659],{},"kubectl delete -f infrastructure\u002Fkind\u002Fsamples.yaml"," and\n",[120,4662,4663],{},"kubectl delete secret sampledb-admin --namespace tabularia-deps",". The ",[120,4666,4174],{}," folder\nstays in Tabularia, with its tables, until you delete it in Explore; its connections\nand the flows that read them then fail.",[61,4669,4671],{"id":4670},"what-this-cluster-shows","What this cluster shows",[2452,4673,4674,4684],{},[2455,4675,4676],{},[2458,4677,4678,4681],{},[2461,4679,4680],{},"Here",[2461,4682,4683],{},"In a company's cluster",[2471,4685,4686,4697,4705,4716,4727,4740,4751,4763,4773,4784],{},[2458,4687,4688,4694],{},[2476,4689,4690,4691],{},"metrics-server with ",[120,4692,4693],{},"--kubelet-insecure-tls",[2476,4695,4696],{},"the metrics-server of the cluster, usually already there on a managed one",[2458,4698,4699,4702],{},[2476,4700,4701],{},"Calico (or Cilium) in place of kind's network plugin",[2476,4703,4704],{},"the network plugin of the company's cluster: Calico, Cilium or the cloud provider's, with network policies on",[2458,4706,4707,4713],{},[2476,4708,4709,4712],{},[120,4710,4711],{},"certs.d"," mounted on the kind nodes",[2476,4714,4715],{},"the authority installed on the nodes by the platform team (node image, DaemonSet)",[2458,4717,4718,4724],{},[2476,4719,4720,4723],{},[120,4721,4722],{},"registry:2.8.3"," with a password",[2476,4725,4726],{},"Harbor, GitLab, the cloud provider's registry",[2458,4728,4729,4737],{},[2476,4730,4731,4152,4734],{},[120,4732,4733],{},"0.0.0.0\u002F0",[120,4735,4736],{},"egressCIDRs",[2476,4738,4739],{},"the ranges of the company's databases and services",[2458,4741,4742,4748],{},[2476,4743,4744,4745],{},"the registry's address in ",[120,4746,4747],{},"userCodePrivateCIDRs",[2476,4749,4750],{},"the private ranges of the company's registry and services the scripts must reach; none when they are public",[2458,4752,4753,4758],{},[2476,4754,4755,4756],{},"the policy in ",[120,4757,2080],{},[2476,4759,4760,4761],{},"a managed database and queue on private addresses, which the scripts cannot reach unless listed in ",[120,4762,4747],{},[2458,4764,4765,4770],{},[2476,4766,4767,4768],{},"PostgreSQL, Valkey and MinIO in ",[120,4769,2080],{},[2476,4771,4772],{},"managed services, or the company's own charts; the cloud bucket of step 7 is already what a company uses",[2458,4774,4775,4781],{},[2476,4776,4777,4778],{},"the ingress on ",[120,4779,4780],{},"localhost:8080",[2476,4782,4783],{},"the company's ingress, with TLS and a host name",[2458,4785,4786,4792],{},[2476,4787,4788,4789],{},"the notebooks on ",[120,4790,4791],{},"notebooks.localhost:8080",[2476,4793,4794,4795,4798],{},"a host name of their own next to the interface's (",[120,4796,4797],{},"notebooks.example.com","), with its certificate",[14,4800,4801,4802,4805,4806,4809],{},"PostgreSQL here keeps its data on a volume of the node it runs on (kind's local storage):\ndraining that node (",[120,4803,4804],{},"kubectl drain",") stops it, and Tabularia with it, until the node is\nback (",[120,4807,4808],{},"kubectl uncordon","); the sign-in then answers that the database is not answering.\nTabularia's own components move from node to node one pod at a time, as their\nPodDisruptionBudgets allow. A company's PostgreSQL is a managed service, or replicated by\nan operator, and survives a drain.",[14,4811,4812,4813,4815],{},"When something is refused, Tabularia says which side: a build that cannot push names\n",[120,4814,3170],{},"; a script whose image a node cannot download fails in about\nhalf a minute saying whether the credentials, the certificate or the image is the\nproblem.",[61,4817,4819],{"id":4818},"upgrade-it","Upgrade it",[14,4821,4822,4823,4827,4828,4830],{},"Build and push the new release's images as in step 6, then follow\n",[52,4824,4826],{"href":4825},"\u002Fdocs\u002Fdeploy\u002Fupgrading","Upgrading Tabularia",": the commands of its minikube part work here too,\nsince PostgreSQL is in the same namespace ",[120,4829,2080],{},". MinIO's volume, like\nPostgreSQL's, lives as long as the cluster.",[61,4832,4834],{"id":4833},"remove-it","Remove it",[177,4836,4838],{"className":278,"code":4837,"language":280,"meta":182,"style":182},"kind delete cluster --name tabularia\ndocker rm -f tabularia-registry\nrm -rf \"$WORK\"\n",[120,4839,4840,4852,4864],{"__ignoreMap":182},[186,4841,4842,4844,4846,4848,4850],{"class":188,"line":189},[186,4843,106],{"class":192},[186,4845,4558],{"class":289},[186,4847,935],{"class":289},[186,4849,1472],{"class":293},[186,4851,3228],{"class":289},[186,4853,4854,4856,4859,4861],{"class":188,"line":196},[186,4855,1424],{"class":192},[186,4857,4858],{"class":289}," rm",[186,4860,1013],{"class":293},[186,4862,4863],{"class":289}," tabularia-registry\n",[186,4865,4866,4869,4872,4874,4876],{"class":188,"line":207},[186,4867,4868],{"class":192},"rm",[186,4870,4871],{"class":293}," -rf",[186,4873,348],{"class":289},[186,4875,137],{"class":203},[186,4877,353],{"class":289},[14,4879,4880],{},"kind clusters are made to be thrown away: if one does not come back after the computer\nrestarts, remove it and run the guide again.",[61,4882,4884],{"id":4883},"when-something-goes-wrong","When something goes wrong",[18,4886,4887,4905,4921,4930,4950,4980,4996,5019,5032,5055,5073,5086],{},[21,4888,4889,4894,4895,4898,4899,2484,4902,4904],{},[70,4890,4891,4892],{},"The nodes stay ",[120,4893,951],{},": the network plugin is not running yet.\n",[120,4896,4897],{},"kubectl get pods --namespace kube-system"," shows the ",[120,4900,4901],{},"calico-node",[120,4903,1105],{},") pods\nand why they wait; an image still downloading takes a few minutes.",[21,4906,4907,4913,4914,4152,4917,4920],{},[70,4908,4909,4912],{},[120,4910,4911],{},"kind create cluster"," fails on the port",": something already listens on 8080. Change\n",[120,4915,4916],{},"hostPort",[120,4918,4919],{},"$WORK\u002Fkind.yaml"," and open that port instead.",[21,4922,4923,4926,4927,4929],{},[70,4924,4925],{},"A Container node, or Try in the cluster, says the nodes do not trust the\nregistry's certificate",": the authority is missing from ",[120,4928,4711],{}," for that address\n(step 3); a registry under another name needs a folder of its own there.",[21,4931,4932,4938,4939,4942,4943,4946,4947,4949],{},[70,4933,4934,4937],{},[52,4935,269],{"href":269,"rel":4936},[99]," does not answer"," while every pod is ",[120,4940,4941],{},"Running",": the ingress\ncontroller runs on a worker. ",[120,4944,4945],{},"kubectl get pods --namespace ingress-nginx -o wide"," shows\nits node; run the ",[120,4948,1240],{}," of step 4 again.",[21,4951,4952,3499,4958,4961,4962,4965,4966,4969,4970,3506,4972,4975,4976,4979],{},[70,4953,4954,4955],{},"Pods in ",[120,4956,4957],{},"ErrImagePull",[120,4959,4960],{},"kubectl describe pod \u003Cname> --namespace \u003Cnamespace>"," says\nwhy. ",[3848,4963,4964],{},"x509"," means the nodes do not trust the registry (step 3 and the ",[120,4967,4968],{},"extraMounts","\nof step 4); ",[3848,4971,1687],{},[3848,4973,4974],{},"unauthorized",", a missing or wrong ",[120,4977,4978],{},"registry"," Secret in that\nnamespace.",[21,4981,4982,4985,4986,4988,4989,4991,4992,4995],{},[70,4983,4984],{},"A build fails with \"not pushed\"",": the message says the certificate\n(",[120,4987,3170],{},") or the credentials (the ",[120,4990,4978],{}," Secret of\n",[120,4993,4994],{},"tabularia-script-builds",").",[21,4997,4998,5005,5006,5009,5010,4152,5012,5015,5016,5018],{},[70,4999,5000,5001,5004],{},"A build fails and the end of its log shows ",[120,5002,5003],{},"dial tcp"," to the registry's address\nwith a timeout",": the scripts' network policy does not let the builds reach it. The\nregistry's address can change when the computer restarts: run the ",[120,5007,5008],{},"REGISTRY_IP","\ncommand of step 8 again, fix ",[120,5011,4747],{},[120,5013,5014],{},"$WORK\u002Fvalues.yaml",", and run\n",[120,5017,4163],{}," again.",[21,5020,5021,5024,5025,5028,5029,127],{},[70,5022,5023],{},"Workers time out on Valkey or MinIO",": a network policy blocks them. The chart allows\nthe namespaces of the hosts in ",[120,5026,5027],{},"externalServices","; check that those hosts end in\n",[120,5030,5031],{},".svc.cluster.local",[21,5033,5034,253,5037,101,5040,113,5043,101,5046,5049,5050,5052,5053,5018],{},[70,5035,5036],{},"Uploads or previews fail with a storage error",[3848,5038,5039],{},"403",[3848,5041,5042],{},"Access Denied",[3848,5044,5045],{},"SignatureDoesNotMatch",[3848,5047,5048],{},"NoSuchBucket",") with a cloud bucket: check the endpoint,\nregion, bucket and both keys of step 7 (and, on Scaleway, the key's preferred\nproject), fix ",[120,5051,5014],{},", and run ",[120,5054,4163],{},[21,5056,5057,253,5060,101,5062,5065,5066,5068,5069,5072],{},[70,5058,5059],{},"Flows on ClickHouse fail reading the data",[3848,5061,5042],{},[3848,5063,5064],{},"named collection does\nnot exist","): check the named collection of step 12 and its ",[120,5067,3861],{},", and that\n",[120,5070,5071],{},"s3Endpoint"," is the bucket's address as the ClickHouse service sees it.",[21,5074,5075,5078,5079,5081,5082,5085],{},[70,5076,5077],{},"A variable is empty in a new terminal",": run ",[120,5080,144],{}," (and set ",[120,5083,5084],{},"WORK","\nfirst if it is empty too).",[21,5087,5088,5094,5095,5097,5098,5101,5102,5105],{},[70,5089,5090,5091],{},"The sample Job's pod is in ",[120,5092,5093],{},"ErrImageNeverPull",": the image is not on that node.\nRun the ",[120,5096,4545],{}," of step 13 again, with the cluster's name, then delete the Job and\napply the file again. It waits in ",[120,5099,5100],{},"Init"," while the databases start: ",[120,5103,5104],{},"kubectl logs --namespace tabularia-deps job\u002Fsampledb-init -c wait-for-databases"," says which one.",[5107,5108,5109],"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 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 .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 .sxsTv, html code.shiki .sxsTv{--shiki-default:#F47067;--shiki-light:#005CC5}html pre.shiki code .sfef8, html code.shiki .sfef8{--shiki-default:#ADBAC7;--shiki-light:#032F62}html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}",{"title":182,"searchDepth":196,"depth":207,"links":5111},[5112,5115,5116,5117,5118,5119,5120,5121,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135],{"id":63,"depth":196,"text":64,"children":5113},[5114],{"id":149,"depth":207,"text":91},{"id":274,"depth":196,"text":275},{"id":458,"depth":196,"text":459},{"id":681,"depth":196,"text":682},{"id":777,"depth":196,"text":778},{"id":1342,"depth":196,"text":1343},{"id":1694,"depth":196,"text":1695},{"id":2073,"depth":196,"text":2074,"children":5122},[5123,5124,5125],{"id":2195,"depth":207,"text":2196},{"id":2413,"depth":207,"text":2414},{"id":2649,"depth":207,"text":2650},{"id":2766,"depth":196,"text":2767},{"id":3208,"depth":196,"text":3209},{"id":3406,"depth":196,"text":3407},{"id":3539,"depth":196,"text":3540},{"id":3761,"depth":196,"text":3762},{"id":4167,"depth":196,"text":4168},{"id":4670,"depth":196,"text":4671},{"id":4818,"depth":196,"text":4819},{"id":4833,"depth":196,"text":4834},{"id":4883,"depth":196,"text":4884},"md",{"source":5138},"docs\u002Fdeploy\u002Fkind.md","\u002Fdocs\u002Fdeploy\u002Fkind",{"title":5,"description":16},"docs\u002Fdeploy\u002Fkind","l1QLY-qIqB05deOBlJM0qG7fwOZhghGni0DZkZVtd2A",[5144,5162,5216,5248,5279,5308,5334,5357],{"title":5145,"icon":5146,"intro":5147,"items":5148},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[5149,5151,5154,5156,5159],{"path":5150,"title":5145},"\u002Fdocs\u002Fgetting-started",{"path":5152,"title":5153},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":54,"title":5155},"Try it on your computer (minikube)",{"path":5157,"title":5158},"\u002Fdocs\u002Fconcepts","Concepts",{"path":5160,"title":5161},"\u002Fdocs\u002Foverview","Overview (README)",{"title":5163,"icon":5164,"intro":5165,"items":5166},"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.",[5167,5170,5173,5176,5179,5182,5185,5188,5190,5193,5196,5198,5201,5204,5207,5210,5213],{"path":5168,"title":5169},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":5171,"title":5172},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":5174,"title":5175},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":5177,"title":5178},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":5180,"title":5181},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":5183,"title":5184},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":5186,"title":5187},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":3487,"title":5189},"Python and R",{"path":5191,"title":5192},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":5194,"title":5195},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":3753,"title":5197},"Docker images in flows",{"path":5199,"title":5200},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":5202,"title":5203},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":5205,"title":5206},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":5208,"title":5209},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":5211,"title":5212},"\u002Fdocs\u002Fcapabilities\u002Fengines","Engines",{"path":5214,"title":5215},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":5217,"icon":5218,"intro":5219,"items":5220},"Sources","sources","Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[5221,5224,5227,5230,5233,5236,5239,5242,5245],{"path":5222,"title":5223},"\u002Fdocs\u002Fsources","All sources",{"path":5225,"title":5226},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":5228,"title":5229},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":5231,"title":5232},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":5234,"title":5235},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":5237,"title":5238},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":5240,"title":5241},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":5243,"title":5244},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":5246,"title":5247},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":4396,"icon":5249,"intro":5250,"items":5251},"connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[5252,5255,5258,5261,5264,5266,5267,5269,5270,5273,5276],{"path":5253,"title":5254},"\u002Fdocs\u002Fconnections","All connections",{"path":5256,"title":5257},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":5259,"title":5260},"\u002Fdocs\u002Fsqlserver","Microsoft SQL Server driver",{"path":5262,"title":5263},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":5234,"title":5265},"SFTP servers",{"path":5237,"title":5238},{"path":5240,"title":5268},"SharePoint",{"path":5243,"title":5244},{"path":5271,"title":5272},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":5274,"title":5275},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":5277,"title":5278},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":5280,"icon":5281,"intro":5282,"items":5283},"Integrations","integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[5284,5287,5290,5293,5296,5299,5302,5305],{"path":5285,"title":5286},"\u002Fdocs\u002Fintegrations","All integrations",{"path":5288,"title":5289},"\u002Fdocs\u002Fintegrations\u002Fai","AI: where it is used",{"path":5291,"title":5292},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":5294,"title":5295},"\u002Fdocs\u002Fdbt-export","Export to dbt",{"path":5297,"title":5298},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":5300,"title":5301},"\u002Fdocs\u002Fsso-keycloak","Single sign-on with Keycloak",{"path":5303,"title":5304},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":5306,"title":5307},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":5309,"icon":5310,"intro":5311,"items":5312},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[5313,5316,5319,5322,5325,5328,5331],{"path":5314,"title":5315},"\u002Fdocs\u002Fgovernance","Overview",{"path":5317,"title":5318},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":5320,"title":5321},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":5323,"title":5324},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":5326,"title":5327},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",{"path":5329,"title":5330},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example on a laptop",{"path":5332,"title":5333},"\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user","A ClickHouse user for the AI assistant",{"title":5335,"icon":5336,"intro":5337,"items":5338},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[5339,5342,5343,5345,5348,5351,5354],{"path":5340,"title":5341},"\u002Fdocs\u002Fdeploy\u002Fkubernetes","Kubernetes and Helm",{"path":5139,"title":5},{"path":4825,"title":5344},"Upgrading, backups and restore",{"path":5346,"title":5347},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","Install and upgrade checklist",{"path":5349,"title":5350},"\u002Fdocs\u002Fconfiguration","Configuration reference",{"path":5352,"title":5353},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":5355,"title":5356},"\u002Fdocs\u002Fdatabase-migrations","Database migrations",{"title":5358,"icon":5359,"intro":5360,"items":5361},"Reference","reference","Release process and the remaining reference pages.",[5362],{"path":5363,"title":5364},"\u002Fdocs\u002Frelease-process","Release process",1793656372225]