Skip to content

Contributing

Kipper is open source under the Apache 2.0 license. Contributions are welcome.

Development setup

Prerequisites

  • Go 1.25+
  • Node.js 20+
  • golangci-lint and goimports for linting and formatting
  • A Linux host or VM to run the whole platform (k3s is Linux-only). A local VM works fine, see Running Kipper locally.

Clone and build

bash
git clone https://github.com/getkipper/kipper
cd kipper

# Build the CLI
cd kip && go build -o kip . && cd ..

# Run the console in dev mode
cd console && npm install && npm run dev && cd ..

# Run the docs site
cd docs && npm install && npm run dev && cd ..

Building kip inside WSL

A checkout on the Windows filesystem built from WSL needs two extra flags:

bash
sudo apt install -y golang-go openssh-client
cd /mnt/c/Users/<you>/kipper/kip
CGO_ENABLED=0 go build -buildvcs=false -o kip .
sudo mv kip /usr/local/bin/

-buildvcs=false skips Go's VCS stamping, which fails because git refuses to trust a repository whose owner differs across the WSL and Windows filesystem boundary. Adding the checkout to git's safe list works too:

bash
git config --global --add safe.directory /mnt/c/Users/<you>/kipper

CGO_ENABLED=0 builds a static binary, which a fresh Ubuntu WSL needs because it ships without build-essential and runtime/cgo will not compile without a C toolchain. Installing build-essential is the other way round it.

This is also the route to take when getkipper.com/install is unreachable from your network. See Installing from Windows for the rest of that path.

Running tests

bash
# Go tests (kip, console-api, gateway)
cd kip && go test ./...
cd console-api && go test ./...
cd gateway && go test ./...

# Console unit tests and type checking
cd console && npm run test && npm run type-check

Format Go imports with the project's local prefix before committing, so they group correctly:

bash
goimports -w -local github.com/getkipper/kipper ./...

Running Kipper locally

The unit tests above need no cluster and no login, so that is the fastest loop for day-to-day work. To see the whole platform running, you need a real cluster, because the console API and the controllers use the in-cluster Kubernetes config. Running a cluster is also how you dogfood Kipper: you use kip to manage it.

Full platform on a Linux VM

k3s runs on Linux and kip install bootstraps it over SSH, so on macOS or Windows run a local Ubuntu VM. OrbStack or Lima on Apple Silicon, or multipass or UTM on any machine, all work well. Give it a few cores, 8 GB of RAM, and 40 GB of disk.

bash
# Example with multipass
multipass launch 24.04 --name kipper --cpus 4 --memory 8G --disk 40G

# Build the CLI and install into the VM over SSH
cd kip && go build -o kip . && cd ..
./kip/kip install --host 203.0.113.10

kip install sets up k3s, Traefik, cert-manager, Longhorn, Dex, and the console, then prints the admin login. See Installation for the full flag reference.

Sign in to the console with those admin credentials. Authentication is a real Dex login, the same as production. There is no local bypass, so a running cluster with Dex is what you authenticate against.

Console-only iteration

For quick frontend work, run the Vite dev server on its own:

bash
cd console && npm install && npm run dev   # http://localhost:5173

It proxies /api to a console API on http://localhost:8080 by default. To work against a running cluster, forward that cluster's console API to port 8080, or set VITE_API_URL to the cluster's console API URL. Live log and terminal streaming check the browser Origin, so streaming from a localhost origin needs that origin allowed on the target cluster. The REST screens work without it.

Project structure

DirectoryLanguagePurpose
kip/GoCLI tool
console/Vue 3 + TypeScriptWeb dashboard
console-api/GoREST API for the console
gateway/Gokipper.run subdomain proxy
docs/VitePressThis documentation

CRD architecture

Kipper manages workloads through Custom Resource Definitions (kipper.run/v1alpha1). When a user deploys an app, creates a service, or adds a function, the handler creates a Custom Resource. A controller-runtime reconciler watches these CRs and ensures the underlying Kubernetes resources (Deployments, StatefulSets, Services, Ingresses) match the desired spec.

CRDReconciles into
AppDeployment + Service + Ingress + Secrets
ServiceStatefulSet + headless Service + credentials Secret
FunctionDeployment + Service + KEDA HTTPScaledObject + Ingress
ProjectNamespaces + shared storage PVCs
JobCronJob or Job
VolumePersistentVolumeClaim

Key directories:

  • console-api/api/v1alpha1/: CRD type definitions
  • console-api/controllers/: reconcilers (one per CRD)
  • console-api/handlers/: REST API handlers that create CRs
  • deploy/crds/: generated CRD YAML manifests

When adding a new feature, decide whether it needs a new CRD (owns Kubernetes resources) or can be added as a field on an existing one. Use controller-gen to regenerate manifests after changing types.

Code conventions

Go

  • Table-driven tests with testify
  • Error wrapping with context: fmt.Errorf("installing k3s: %w", err)
  • Unexported by default, only export what is used outside the package
  • Interfaces belong in the package that uses them
  • All exported types and functions need godoc comments

Vue / TypeScript

  • Composition API with <script setup lang="ts">, never Options API
  • No any, define proper types
  • Pinia stores with loading and error state
  • Tailwind utility classes only, no inline styles
  • lucide-vue-next for icons

Commits

Format: type: short description

Types: feat:, fix:, docs:, style:, refactor:, test:, chore:, perf:

Branches

Format: prefix/short-description

Prefixes: feature/, bugfix/, hotfix/, refactor/, docs/, test/

Keeping private data out

Kipper is public, so a server address, a client's name or an internal hostname in a commit is published the moment it is pushed, and the only way back is a history rewrite. A scan runs in CI on every push and pull request, but by then the content is already on GitHub. Run the same scan before the push instead by enabling the repository's hooks once per clone:

bash
git config core.hooksPath scripts/hooks

The pre-push hook runs scripts/private-data-scan.py over every commit about to leave your machine, including commits that add a value and remove it again in a later commit. It fails on:

  • a public IPv4 or IPv6 address that is not in .private-data-allowlist. Use the documentation ranges instead: 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 and 2001:db8::/32.
  • a hostname whose domain is not in .private-data-allowlist. Use example.com, example.org or a name under the reserved .example TLD.
  • a word from PRIVATE_NAME_PATTERN, when that variable is set in your environment. Maintainers keep it outside the repository.
  • text read out of a changed image, GIF, video or PDF, when tesseract is installed (ffmpeg for recordings, pdftoppm from Poppler for PDFs). An asset the tools cannot read stops the push; one the tools are missing for is listed as unscanned and needs a look by eye.

A test fixture or a comment that needs a public value on purpose carries private-data-scan:allow on that line, which exempts the line from the address and domain checks and shows the exemption in the diff. Commit messages, author identities and path names are scanned as well as file contents. A value that belongs to a public upstream (a registry, a vendor, a resolver) goes into .private-data-allowlist with a comment; a client's domain never does, replace it in the tree instead.

A maintainer can chain a review command of their own after the scan with git config kipper.pushReview /path/to/command. It receives the same range arguments as the scanner, and a non-zero exit stops the push.

PR guidelines

  • New features require unit tests covering the happy path and at least two error cases
  • Bug fixes require a test that would have caught the bug
  • Documentation changes are required in the same PR as feature changes
  • Every example must use realistic names, not foo or bar

Licensing

All contributions must be compatible with the Apache 2.0 license. No GPL dependencies. MIT and Apache 2.0 only.

Released under the Apache 2.0 License.