Skip to content

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:

  • object writes zz_generated.deepcopy.go: the DeepCopy methods every Kubernetes type needs (the cache hands out copies);
  • crd writes deploy/crds/*.yaml from 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.