Heroku-like DX on vanilla Kubernetes.
CLI + SDK. Zero server-side components. Open source.
You bring a Kubernetes cluster. Kuberoku gives you apps:create, deploy, config:set, services:logs --tail, and everything else you loved about Heroku — without vendor lock-in, without YAML, without installing anything on your cluster.
pipx install kuberoku # Python (recommended)
pip install kuberoku # Python (alternative)# Linux / macOS binary
curl -fsSL https://github.com/amanjain/kuberoku/releases/latest/download/install.sh | sh# Windows (PowerShell)
irm https://github.com/amanjain/kuberoku/releases/latest/download/install.ps1 | iexPre-built binaries for Linux, macOS, and Windows are available on the GitHub Releases page.
Requires kubectl configured with a valid kubeconfig. The Python install requires Python 3.11+; the binary install has no Python dependency.
From zero to a running app accessible on the internet:
kuberoku clusters:doctor # verify your cluster is ready
kuberoku apps:create myapi
kuberoku deploy --app myapi --image nginx:1.27 --expose web:80/http
# → https://myapi.apps.mycluster.com (auto-domain when base_domain is configured)
kuberoku config:set --app myapi GREETING=hello SECRET_KEY=abc123
kuberoku services:logs --app myapi --tailThat's it. No Deployments, Services, ConfigMaps, or Ingress manifests. Kuberoku created them all.
How ports work
| Suffix | Meaning | What happens on deploy |
|---|---|---|
/http |
HTTP service | Auto-creates a domain via Ingress (myapp.apps.example.com) with TLS |
/https |
Same as /http |
Alias — TLS is always via Ingress + cert-manager |
/tcp |
Raw TCP | ClusterIP Service; use services:expose:on for external IP |
/udp |
Raw UDP | ClusterIP Service; use services:expose:on for external IP |
If you don't specify --expose, kuberoku defaults to a single web process on 8080/http.
Local (fastest way to try):
We recommend k3d — it's the fastest to start and lightest on resources.
macOS
# k3d (recommended)
brew install k3d && k3d cluster create dev
# or kind
brew install kind && kind create cluster
# or minikube
brew install minikube && minikube startUbuntu / Debian
# k3d (recommended) — requires Docker
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
k3d cluster create dev
# or kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
kind create cluster
# or minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube startFedora / RHEL
# k3d (recommended) — requires Docker or Podman
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
k3d cluster create dev
# or kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
kind create clusterArch Linux
# k3d (recommended)
yay -S k3d-bin && k3d cluster create dev
# or kind
pacman -S kind && kind create clusterThen run the commands above.
Cloud (DigitalOcean, Linode, AWS, GCP, Azure, etc.)
Three steps — same for every provider:
1. Download your kubeconfig
Every managed K8s provider gives you a kubeconfig file. Download it from your dashboard:
| Provider | Where to find it |
|---|---|
| DigitalOcean | Kubernetes → your cluster → Download Config |
| Linode | Kubernetes → your cluster → Download kubeconfig |
| AWS (EKS) | aws eks update-kubeconfig --name my-cluster |
| GCP (GKE) | gcloud container clusters get-credentials my-cluster |
| Azure (AKS) | az aks get-credentials -g my-group -n my-cluster |
AWS/GCP/Azure CLI commands auto-merge into ~/.kube/config. For DigitalOcean and Linode (or any provider that gives you a file), merge it yourself:
# First time — just copy it
mkdir -p ~/.kube
cp ~/Downloads/your-kubeconfig.yaml ~/.kube/config# Already have a config? Merge the new one in (this MUST be one command):
KUBECONFIG=~/.kube/config:~/Downloads/new-kubeconfig.yaml kubectl config view --flatten > /tmp/merged && mv /tmp/merged ~/.kube/configCommon mistake: Running
KUBECONFIG=...on its own line does nothing — it must be on the same line as thekubectlcommand.
2. Verify you can connect
kubectl get nodesIf you see your nodes listed, you're connected. If not, double-check your kubeconfig.
3. Run kuberoku
kuberoku clusters:setup # checks your cluster and fixes what it can
kuberoku apps:create myapi
kuberoku deploy --app myapi --image nginx:1.27 --expose web:80/httpThat's it. Kuberoku works on any conformant K8s cluster (1.33+). No special setup required.
Multiple clusters?
If you have more than one cluster (e.g. staging + production):
# See your available kubectl contexts
kubectl config get-contexts
# Register them in kuberoku
kuberoku clusters:add staging --context do-nyc1-staging --namespace kuberoku
kuberoku clusters:add production --context lke-prod --namespace kuberoku --default
# Switch between them
kuberoku clusters:switch staging
kuberoku clusters:switch productionkuberoku clusters:add production --context prod-eks
kuberoku clusters:add staging --context staging-k3d
kuberoku clusters:switch production
kuberoku clusters:doctor # API, RBAC, CNI, Ingress, cert-manager, ...
kuberoku clusters:doctor --fix # generate RBAC YAML for missing permissions
kuberoku clusters:setup --base-domain apps.myteam.dev # configure auto-domains + DNS instructionsDoctor validates everything kuberoku needs: K8s API, namespace access, CRUD for all resource types, StorageClass, NetworkPolicy CNI, Ingress controller, cert-manager, LoadBalancer support, RBAC permissions, and base domain configuration.
# Simple: one process, default port 8080/http
kuberoku deploy --app myapi --image myapi:v2
# Multi-process: each --expose declares a process type and its ports
kuberoku deploy --app myapi --image myapi:v2 \
--expose web:8080/http \
--expose api:3000/http
# Push a local Docker image to the registry and deploy
kuberoku deploy --app myapi --image myapi:v2 --local
kuberoku ps:scale --app myapi web=3 worker=2 # scale independently
kuberoku ps:restart --app myapi # rolling restart
kuberoku ps:type --app myapi web --cpu 500m --memory 512Mikuberoku config:set --app myapi DATABASE_URL=postgres://... API_KEY=secret
kuberoku config:set --app myapi --secret STRIPE_KEY=sk_live_...
kuberoku config --app myapi # secrets are masked
kuberoku config:unset --app myapi API_KEY
# Or set config inline during deploy (one atomic operation, one restart):
kuberoku deploy --app myapi --image myapi:v2 \
--env DATABASE_URL=postgres://... \
--secret-env STRIPE_KEY=sk_live_...Config changes automatically trigger a rolling restart and create a new release.
kuberoku services:logs --app myapi --tail # stream logs
kuberoku services:logs --app myapi --type worker # filter by process type
kuberoku services:exec --app myapi -- bash # shell into a running pod
kuberoku services:connect --app myapi # port-forward to localhost
kuberoku services:open --app myapi # open in browserExpose a process to the internet:
kuberoku services:expose:on --app myapi web # HTTP: auto-domain via Ingress
kuberoku services:expose:on --app myapi game \
--method loadbalancer # TCP/UDP: external LoadBalancer IP
kuberoku services:expose:off --app myapi web # revert to internal-onlyManage ports without redeploying:
kuberoku services:ports:add 9090/tcp --app myapi # add a port
kuberoku services:ports:remove 9090 --app myapi # remove a portkuberoku domains:add myapi.example.com --app myapi # custom domain with auto-TLS
kuberoku domains:add api.example.com --app myapi --type api # route to specific process
kuberoku domains:add internal.example.com --app myapi --no-tls # HTTP only, no TLS
kuberoku domains --app myapi # list all domains
kuberoku domains:remove myapi.example.com --app myapi
kuberoku domains:clear --app myapi # remove all custom domainsWhen you deploy with /http ports, kuberoku auto-creates a domain (myapp.apps.example.com) with TLS via cert-manager. Custom domains added with domains:add also get auto-TLS by default.
kuberoku releases --app myapi # list all releases
kuberoku releases:info --app myapi 3 # inspect a specific release
kuberoku releases:rollback --app myapi # roll back to previous
kuberoku releases:rollback --app myapi 2 # roll back to specific version
kuberoku releases:prune --app myapi --keep 10 # clean up old releasesEvery deploy, config change, and rollback creates an immutable release. Full audit trail.
kuberoku addons:create --app myapi postgres # PostgreSQL 16
kuberoku addons:create --app myapi redis # Redis 7
kuberoku addons:create --app myapi redis --as cache # named instances
kuberoku addons:credentials --app myapi postgres # show connection string
kuberoku addons:backup --app myapi postgres # pg_dump backup
kuberoku addons:exec --app myapi postgres # shell into addon podDATABASE_URL and REDIS_URL are injected automatically. Addons run as StatefulSets with persistent storage.
Maintenance mode
kuberoku apps:maintenance:on --app myapi # scale all processes to 0, preserve replica counts
kuberoku apps:maintenance:off --app myapi # restore previous replica countsDeploy while in maintenance mode to update the image without running pods. When maintenance is turned off, the new image starts.
Non-HTTP services
Not everything is a web app. Deploy SMTP servers, game servers, DNS, gRPC — anything TCP/UDP:
kuberoku apps:create gameserver
kuberoku deploy --app gameserver --image game:v1 \
--expose game:7777/udp,7778/tcp
kuberoku services:expose:on --app gameserver game --method loadbalancer
# External IP: 34.123.45.67 :7777/udp :7778/tcp/tcp and /udp ports get a ClusterIP by default — use services:expose:on for an external LoadBalancer IP. /http ports auto-create a domain via Ingress (no expose:on needed).
Spin up a full copy of your app for every pull request — from any CI provider:
kuberoku preview:deploy \
--app myapi-pr-42 \
--image myapi:pr-42 \
--expose web:8080/http \
--addon postgres \
--env RAILS_ENV=staging \
--ttl 48h \
--output jsonEach preview gets its own database, its own URL, and auto-destroys after the TTL expires:
kuberoku preview:list # see all active previews
kuberoku preview:status --app myapi-pr-42 # inspect one
kuberoku preview:destroy --app myapi-pr-42 # tear down manually
kuberoku preview:cleanup # destroy all expiredSet up your cluster's base domain for preview URLs:
kuberoku clusters:setup --base-domain apps.myteam.devThis configures the base domain for auto-generated preview URLs and shows DNS setup instructions. Each preview app gets a URL like myapi-pr-42.apps.myteam.dev.
Tab-complete commands, options, and arguments:
eval "$(kuberoku completions bash)" # bash — add to ~/.bashrc
eval "$(kuberoku completions zsh)" # zsh — add to ~/.zshrc
kuberoku completions fish | source # fish — add to config.fishEvery CLI command maps 1:1 to a Python method:
from kuberoku import Kuberoku
k = Kuberoku()
# Create and deploy
app = k.apps.create("myapi")
k.config.set("myapi", {"DATABASE_URL": "postgres://..."})
release = k.deploy.deploy("myapi", image="myapi:v2") # defaults to web:8080/http
# Inspect
for app in k.apps.list():
print(f"{app.name} v{app.release_version} ({'maintenance' if app.maintenance else 'active'})")
# Scale and rollback
k.ps.scale("myapi", {"web": 3})
k.releases.rollback("myapi")All methods return frozen dataclasses. No K8s types leak into your code.
| Group | Commands |
|---|---|
| clusters | add remove switch current info doctor setup registry:add registry:remove |
| apps | create destroy info rename status link:add link:remove maintenance:on maintenance:off |
| completions | bash zsh fish |
| deploy | deploy (image or build-from-git, --env, --secret-env) |
| config | set get unset |
| ps | scale restart stop type set commands |
| services | expose:on expose:off open connect ports:add ports:remove logs exec maintenance:on maintenance:off |
| domains | add remove clear |
| releases | info rollback prune |
| addons | create destroy info scale migrate migrate-rollback credentials credentials-rotate exec backup expose:on expose:off connect |
| preview | deploy destroy list cleanup status |
| plugins | (list) install uninstall search |
Run kuberoku <group> --help for details on any command.
Kuberoku stores all state in standard Kubernetes resources — ConfigMaps, Secrets, Deployments, Services, Ingress, NetworkPolicies. No CRDs, no operators, no server-side components. Uninstall kuberoku and your apps keep running.
You ──> kuberoku CLI (Click) ──> SDK (business logic) ──> K8s API
|
also importable as Python SDK
The SDK is a strict three-layer architecture. The CLI never touches K8s directly. A typing.Protocol abstracts the K8s client, making the entire stack testable with an in-memory fake.
See docs/NORTHSTAR.txt for the complete specification.
Alpha. Kuberoku is under active development. The core commands work and are well-tested, but the API may change between releases. Use it for development, staging, and side projects. Evaluate thoroughly before using in production.
If something breaks, please open an issue.
If you discover a security vulnerability, please report it responsibly via GitHub's private security reporting instead of opening a public issue.
Contributions welcome. Start with the NORTHSTAR spec to understand the architecture.
git clone https://github.com/amanjain/kuberoku.git
cd kuberoku
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v
mypy src/kuberoku/ --strict # strict type checking
ruff check src/ tests/ # lintBuilt by Aman Kumar Jain.