Setting up and building¶
Tools¶
| Tool | Version | Where it is on main |
Used for |
|---|---|---|---|
| Go | 1.27 | ~/.local/go/bin |
building, tests, generators |
| Node.js + npm | 20 | system | the UI |
| controller-gen | v0.22.0 | ~/go/bin |
deepcopy code and CRDs from Go types |
| setup-envtest | v0.25.1 | ~/go/bin |
downloads a test Kubernetes API server + etcd |
| kubectl | matches the cluster | system | everything cluster-side |
| docker | any | system | only to run the buildctl client for make image |
The Makefile puts ~/.local/go/bin and ~/go/bin on the PATH for its targets. For a new machine:
# Go
curl -sL https://go.dev/dl/go1.27.1.linux-arm64.tar.gz | tar -C ~/.local -xz
export PATH=$HOME/.local/go/bin:$HOME/go/bin:$PATH
go install sigs.k8s.io/controller-tools/cmd/[email protected]
go install sigs.k8s.io/controller-runtime/tools/[email protected]
The repository¶
rendimiento.ai/
├── cmd/rendimiento/ main.go: settings, wiring, startup (the composition root)
├── api/v1alpha1/ App and Addon custom resources (Go types → CRDs)
├── internal/
│ ├── spec/ rendimiento.yaml: types, defaults, validation
│ ├── detect/ what a repository contains (language, port, tests, needs)
│ ├── generate/ proposals from detection (+ templates/ Dockerfiles)
│ ├── github/ the GitHub App: JWT, tokens, API client, repo trees, OAuth
│ ├── platform/ orchestration: webhooks → runs → releases → App objects
│ ├── pipeline/ CI: plan, runner, Kubernetes executor (build pods)
│ ├── store/ Postgres: apps, runs, logs, releases, sessions, add-ons
│ ├── render/ spec + images → Kubernetes objects (pure)
│ ├── controller/ App and Addon controllers, hooks, needs
│ ├── addon/ add-on rendering (Helm, kustomize), git sync, catalog
│ ├── renovate/ the Renovate add-on
│ ├── catalog/ the Services page
│ ├── environment/ the Environment page
│ ├── dns/ Cloudflare records, dynamic DNS
│ ├── events/ live-update hub (SSE)
│ └── api/ HTTP API, auth, webhooks, setup, the UI server
├── web/ the React UI (src/) and its embed (embed.go)
├── templates/dockerfiles/ Dockerfile templates for detected stacks
├── deploy/ manifests for the platform itself (kubectl apply -k)
├── docs/ this book (MkDocs Material)
├── hack/ developer tools: test-remote.sh, codemap, undoc
├── Dockerfile the platform image (UI + Go, multi-stage)
├── Makefile build, test, image, deploy
└── rendimiento.yaml how rendimiento deploys this book
Make targets¶
export PATH := $(HOME)/.local/go/bin:$(HOME)/go/bin:$(PATH)
# This cluster's values (registry, deploy overlay, nodes to avoid) live in
# local.mk, which is not in the public repository.
-include local.mk
TEST_DATABASE_URL ?= postgres://postgres:[email protected]:55432/rendimiento
IMAGE ?= registry.example.lan:5000/rendimiento
DEPLOY_DIR ?= deploy
.PHONY: generate ui build test test-remote test-db itest image railpack-image deploy docs-codemap docs-check
generate: ## deepcopy + CRD from api/v1alpha1
controller-gen object paths=./api/... paths=./internal/spec/...
controller-gen crd paths=./api/... output:crd:dir=deploy/crds
ui:
cd web && npm ci && npm run build
build: ui
go build -o rendimiento ./cmd/rendimiento
test-db: ## disposable Postgres for tests
docker run -d --rm --name rendimiento-testpg -e POSTGRES_PASSWORD=test -e POSTGRES_DB=rendimiento -p 127.0.0.1:55432:5432 postgres:17-alpine
# -p 1: store, platform and api tests share one test database.
# generate first: a stale CRD makes the API server silently drop new fields.
test: generate
@# envtest control planes left by an interrupted run keep using CPU and disk.
-@pkill -f '[k]ubebuilder-envtest/k8s/.*/(kube-apiserver|etcd)' ; rm -rf /tmp/k8s_test_framework_*
go vet ./...
KUBEBUILDER_ASSETS=$$(setup-envtest use -p path) TEST_DATABASE_URL=$(TEST_DATABASE_URL) go test -p 1 ./...
cd web && npm run typecheck
# The same steps on a worker node: main is the k3s control plane, and
# compiling plus envtest there slows the API server. Prefer this one.
test-remote:
TEST_EXCLUDE_NODES="$(TEST_EXCLUDE_NODES)" hack/test-remote.sh
itest: ## real build on the cluster's buildkitd
ITEST_REGISTRY="$(ITEST_REGISTRY)" ITEST_EXCLUDE_NODES="$(ITEST_EXCLUDE_NODES)" ITEST_RAILPACK_IMAGE="$(ITEST_RAILPACK_IMAGE)" \
go test -tags integration ./internal/pipeline -run TestBuildOnCluster -v -timeout 25m
# Build on the cluster's BuildKit pool (the buildkitd Service reaches one of
# its daemons), not on this node: main is also the k3s control plane, and a
# local compile starves its SQLite datastore.
BUILDKIT_PORT ?= 12345
image:
@kubectl port-forward -n devops-tools svc/buildkitd $(BUILDKIT_PORT):1234 >/dev/null 2>&1 & pf=$$!; \
trap "kill $$pf 2>/dev/null" EXIT; sleep 3; \
docker run --rm --network host -v $(CURDIR):/src:ro --entrypoint buildctl moby/buildkit:v0.18.2 \
--addr tcp://127.0.0.1:$(BUILDKIT_PORT) build --frontend dockerfile.v0 \
--local context=/src --local dockerfile=/src \
--output type=image,name=$(IMAGE):latest,push=true,registry.insecure=true \
--import-cache type=registry,ref=$(IMAGE):buildcache,registry.insecure=true \
--export-cache type=registry,ref=$(IMAGE):buildcache,mode=max,registry.insecure=true
RAILPACK_VERSION ?= 0.40.0
railpack-image: ## the railpack CLI image build pods use (RAILPACK_IMAGE)
@kubectl port-forward -n devops-tools svc/buildkitd $(BUILDKIT_PORT):1234 >/dev/null 2>&1 & pf=$$!; \
trap "kill $$pf 2>/dev/null" EXIT; sleep 3; \
docker run --rm --network host -v $(CURDIR)/deploy/railpack:/src:ro --entrypoint buildctl moby/buildkit:v0.18.2 \
--addr tcp://127.0.0.1:$(BUILDKIT_PORT) build --frontend dockerfile.v0 \
--local context=/src --local dockerfile=/src --opt build-arg:VERSION=$(RAILPACK_VERSION) \
--output type=image,name=$(IMAGE)-railpack:$(RAILPACK_VERSION),push=true,registry.insecure=true
# DEPLOY_DIR is deploy/ (example values) or a private overlay with a real
# cluster's settings on top of it (set in local.mk).
deploy:
kubectl kustomize --load-restrictor=LoadRestrictionsNone $(DEPLOY_DIR) | kubectl apply -f -
# The book's code reference, generated from the Go and TypeScript sources.
# The book's image regenerates it on every build; run this to preview it.
docs-codemap:
go run ./hack/codemap > docs/content/reference/code-map.md
# Mermaid diagrams are drawn in the browser, so mkdocs cannot catch their
# syntax errors; this parses each one with Mermaid itself.
docs-check:
cd hack/checkdiagrams && npm install --no-audit --no-fund --loglevel=error && node check.mjs ../../docs/content
| Target | Does | Where it runs |
|---|---|---|
make generate |
deepcopy code and CRDs from api/v1alpha1 and internal/spec |
here (light) |
make test-remote |
generate, vet, all Go tests, UI typecheck | a worker node |
make test |
the same | here: avoid on main |
make test-db |
a disposable Postgres for local tests | here |
make itest |
real builds on the BuildKit pool (Dockerfile and Railpack) | the cluster |
make image |
build and push rendimiento:latest |
the BuildKit pool |
make railpack-image |
the Railpack CLI image for build pods | the BuildKit pool |
make deploy |
kubectl apply -k deploy |
— |
make docs-codemap |
regenerate docs/content/reference/code-map.md |
here |
make ui, make build |
UI and a local binary | here (heavy) |
The change loop¶
# 1. edit code (and the book)
# 2. fast feedback on the package you touched (light enough for main)
go vet ./internal/render && go test ./internal/render
# 3. everything, on a worker
make test-remote
# 4. ship
make image && make deploy && kubectl -n rendimiento-system rollout restart deploy/rendimiento
# 5. commit and push (the book redeploys itself)
Running rendimiento¶
rendimiento is a controller: never run a second copy against the production cluster. It would reconcile the same App objects as the real one, and the two would fight.
To run it outside the cluster, use a separate cluster (for example k3d or kind, on a laptop) with its own Postgres:
make test-db # Postgres on 127.0.0.1:55432
kubectl apply -f deploy/crds/ # against the test cluster
export DATABASE_URL=postgres://postgres:[email protected]:55432/rendimiento?sslmode=disable
export ALLOWED_USERS=<your-github-login> BASE_URL=http://localhost:8080 SETUP_TOKEN=dev LEADER_ELECTION=false
go run ./cmd/rendimiento # uses your current kubeconfig
Then open http://localhost:8080/api/setup/github?token=dev to create a development GitHub App. Webhooks need a public URL (a tunnel such as cloudflared), or use the Run button instead of pushes.
Working on the UI¶
cd web && npm ci
npm run dev # Vite on :5173 with hot reload; /api is proxied to localhost:8080
npm run typecheck # what the tests run
npm run build # web/dist, embedded by the Go build
The dev server needs a rendimiento API on localhost:8080, the local one above. The session cookie is set by that server's login, so sign in through http://localhost:8080 first.
Generating code¶
make generate runs controller-gen twice:
objectwriteszz_generated.deepcopy.go: theDeepCopymethods every Kubernetes type needs (the cache hands out copies);crdwritesdeploy/crds/*.yamlfrom the Go types and their// +kubebuilder:markers (validation, printer columns, scope, short names).
Both outputs are committed. After changing api/v1alpha1 or internal/spec, regenerate, apply the CRD, and restart the platform.