Your own Gardener
paasbox garden up turns one Hetzner Cloud project into a working Gardener landscape in about 40 minutes: a k3s runtime cluster (Gardener calls it the soil), Gardener’s virtual garden, a seed on the same cluster, and the Gardener Dashboard. From there you create clusters, which Gardener calls shoots, from the dashboard, with kubectl or with the CLI, and Gardener runs them: control planes as pods on the seed, workers as Hetzner servers, upgrades, backups, certificates, DNS.
It is not a wrapper around a hosted service. Everything runs in your project, on your token, and paasbox garden teardown removes it all again. The source is on GitHub under Apache-2.0.
What you need
Section titled “What you need”- A Hetzner Cloud project and a read/write API token. This is the one precondition nothing can remove. Everything else the CLI generates or does without.
- A few tools on your machine:
kubectl,helm,hcloud,flux,git,sshand Python 3.11 or newer.paasbox doctornames each missing one with the install line for macOS and Debian or Ubuntu. - Optionally a DNS zone, an S3 bucket and an OIDC issuer. None of them on day one.
Ten minutes of typing, forty of waiting
Section titled “Ten minutes of typing, forty of waiting”curl -fsSL https://raw.githubusercontent.com/paasbox-cloud/paasbox-cli/main/install.sh | sh
paasbox doctor # what is missing, and how to install itpaasbox garden init my-garden # writes my-garden/values.yaml$EDITOR my-garden/values.yaml # name, location, DNS mode, backup switchexport HCLOUD_TOKEN=… # the only secret the simplest setup needspaasbox garden up my-garden # check → up → flux → garden → images → statusvalues.yaml is the whole configuration:
profile: solo # solo | haname: my-gardenlocation: nbg1 # nbg1 | fsn1 | hel1os: debian-13 # debian-13 | ubuntu-26.04dns: mode: my-paasbox # my-paasbox | zonebackup: enabled: falsedashboard: trueoidc: mode: "off"versions: {} # empty = the release's pinsEvery command writes its stage log to my-garden/out/<stage>.log and prints one line per stage. When up finishes you have a kubeconfig for the garden and an example shoot to apply:
export KUBECONFIG=my-garden/out/my-garden.virtual-garden.kubeconfigkubectl apply -f my-garden/examples/first-shoot.yamlOr use the CLI for the same thing:
paasbox garden shoot create my-garden first --machine cpx32 --k8s 1.36paasbox garden shoot list my-gardenpaasbox garden shoot kubeconfig my-garden first --out first.kubeconfigpaasbox garden shoot delete my-garden firstA shoot’s admin credential is not an object you can read. The garden mints it on request and it is short-lived by design, so ask again when it expires. The first shoot is ready about nine minutes after you create it.
DNS, the one decision to make up front
Section titled “DNS, the one decision to make up front”Gardener needs names: for the garden API, for the dashboard, for the seed’s ingress, and for every shoot’s API server. There are three ways to get them.
dns.mode | What it costs you | What you get | When |
|---|---|---|---|
sslip | nothing | the garden, the dashboard and the seed ingress work, but shoots get no external DNS because there is no zone to write records into | trying it out (designed, not built yet; up refuses it today) |
my-paasbox | a paasbox account and one sign-in in your browser | <team>.paasbox.app, free, with TXT records so Let’s Encrypt works | you have no domain and want a real landscape |
zone | a domain, about €10 a year, and a Hetzner DNS zone with its token in $DNS_TOKEN | your own names, shared with nobody | anything that outlives a trial |
Switching later is two lines in values.yaml and another paasbox garden up.
Signing in, only for my-paasbox
Section titled “Signing in, only for my-paasbox”dns.mode: my-paasbox needs a token for your team’s names. There is nothing to copy out of a web page: the CLI asks for it itself, and paasbox garden up starts the sign-in the first time it needs one.
$ paasbox garden up my-garden dns.mode my-paasbox and no $DNS_TOKEN — signing in to console.paasbox.com
open https://console.paasbox.com/device (opening your browser…) code FTQK-2X7M (expires in 10 minutes)
waiting… ✓ signed in — team Acme GmbH → acme.paasbox.app### check — preflight …It is the OAuth 2.0 device grant, the flow gh auth login and docker login use, so it works over SSH and on a machine with no browser: --no-browser prints the URL and you approve it on your phone. The portal’s address is compiled in and never taken from a reply; an answer that points anywhere else aborts the sign-in.
paasbox auth login # get a team token (--team acme, --no-browser)paasbox auth status # who you are, checked against the portalpaasbox auth token # just the token, for a scriptpaasbox auth logout # forget it here; revoking is done in the portalThe credential lives in ~/.config/paasbox/credentials.json with mode 0600 and is never written into a landscape directory. A token you export yourself, or keep in the landscape’s .env, always wins. sslip and zone never talk to the portal at all.
The team token can write records under your team’s name and nothing else. The zone apex and every other team’s names are refused, and reads are filtered so the rest of the zone is invisible to you.
Sizes and prices
Section titled “Sizes and prices”| Profile | Servers | Fits | List price, net |
|---|---|---|---|
solo | 1 × cpx62 (16 vCPU, 32 GB) | garden, seed and a few shoots | about €130 a month |
ha | 3 × cpx42 (8 vCPU, 16 GB) | garden, seed and about twelve shoots; survives one node | about €208 a month |
Check Hetzner’s current price list; these are list prices from the day this was written, and every shoot adds its own workers. A 16 GB server cannot run a landscape at all: Gardener’s own monitoring requests about 8 GiB, and on a 16 GB node six pods stay pending for ever. That was measured, not estimated, and it is why solo is a 32 GB server.
Each shoot’s control plane needs about 1.5 to 2 GiB and half a vCPU on the seed while it idles; that is what “fits a few shoots” means. A cloud landscape also runs its ingress on two Hetzner load balancers, because Gardener runs two istio ingress gateways, the virtual garden’s and the seed’s. Budget about €12 net a month for them.
Bring your own dedicated server
Section titled “Bring your own dedicated server”A server: block in values.yaml puts the landscape on a Hetzner dedicated server you already rent instead of a cloud server the CLI creates. The price argument in one sentence: a solo landscape needs 32 GB, which costs about €130 a month as a cloud server and starts near €60 a month as a 64 GB box from Hetzner’s Serverbörse.
server: kind: robot # a Hetzner dedicated server (Robot); the only kind today number: 1234567 # your Robot server number; the address is discovered, never configured login: admin # the per-server admin login (Robot → the server → Rescue) reimage: true # install the OS through the rescue system first. THE DISK IS WIPED, onceup then resolves the server number to an address, enters the rescue system and, with reimage: true, installs Debian 13 and waits for the machine to come back. A second up on a machine this landscape already installed reimages nothing and only verifies. A dedicated box has no cloud load balancer, so one haproxy on the node splits port 443 by server name between the two istio gateways and publishes both under one name. No Hetzner network, firewall, volume or load balancer is created at all.
Three things to know before you use it:
- Which login to give it. The per-server admin login reaches this one server and nothing else, and is the one to prefer. It needs
sshpassfor a single first connection. A Robot webservice user is the alternative; it reaches every server on the account. - The box has no cloud firewall. Filter it yourself, on the machine or with Hetzner’s Robot firewall: 22 and 6443 from your own addresses, 80 and 443 from anywhere, 8443 and 8132 for the shoots’ connection back to the seed.
- Nothing in paasbox can cancel a server. There is no such call anywhere in it.
teardownremoves the DNS records and leaves the machine untouched;teardown --wipe --yesreinstalls it. Ending the contract is something you do by hand in Robot.
You still need the Hetzner Cloud project and token: your shoots’ workers are cloud servers.
Ad-hoc or durable
Section titled “Ad-hoc or durable”Ad-hoc is the default: paasbox garden init my-garden, and Flux on the cluster syncs the public kit read-only at the release tag. Your values travel to the cluster as a ConfigMap and a Secret. There is no repository to keep.
Durable is paasbox garden init my-garden --gitops: the same landscape plus a small GitOps repository of your own, with a README, values.yaml, and a Flux GitRepository and Kustomization that point at the public kit at a tag. You push it, Flux syncs it, and you add your own manifests next to it. When a new release lands, paasbox garden update re-renders what the template owns and leaves your edits alone. This replaces forking the kit.
Day two
Section titled “Day two”paasbox garden status my-garden # the garden, the seed, the shoots, the backupspaasbox garden dashboard-token my-garden # a login token for the dashboardpaasbox garden images my-garden # the worker-image snapshots in your projectpaasbox garden upgrade my-garden # the k3s version step, one server at a timepaasbox garden restore my-garden # in place, from the newest etcd snapshot in S3paasbox garden update my-garden # a new release: re-derive, re-renderpaasbox garden teardown my-garden --yes # servers, volumes, network, firewall, recordsbackup.enabled: true writes an etcd snapshot to S3 every ten minutes and keeps the material an in-place restore needs. Off, you lose restore and the ten-minute recovery point. It needs a bucket in the same project, which Hetzner Object Storage creates in one click, and $S3_ACCESS_KEY and $S3_SECRET_KEY.
--stub on any command runs the whole sequence against an in-memory fake: no Hetzner, no SSH, no money. It is how CI rehearses a bring-up in a second, and a fair way to see what the commands do before you spend anything.
OIDC, optional
Section titled “OIDC, optional”oidc.mode: "off" is token login. dex-github runs Dex on the soil with the GitHub connector: you create one GitHub OAuth app, paste its client id and secret into the environment, and list the GitHub logins or the organisation and team that become garden admins. external points the garden at your own issuer, such as Zitadel, Keycloak, Entra or Google.
What this is not
Section titled “What this is not”- Not a managed service. Nobody watches your landscape but you. If you want clusters without a landscape, I can run them for you, on a best-effort basis.
- Not multi-tenant. One project, one garden, one seed. No billing, no portal, no quotas.
- Not a fork-me kit. The kit is pulled by tag; you keep values, not copies.
- Not supported. It is free and best effort: an open-source project that helps a team run its own Gardener. Issues are read; nothing is promised. If you want to learn it properly with me in the room, there is a course.
Working on paasbox itself
Section titled “Working on paasbox itself”The CLI is developed inside the gardener-stack monorepo, next to the deployment it drives, and published to GitHub and PyPI on each release. The public repository is a read-only snapshot with issues open; contributions come back as patches against the monorepo. Licensed under Apache-2.0.