Contributing
Kipper is open source under the Apache 2.0 license. Contributions are welcome.
Development setup
Prerequisites
- Go 1.25+
- Node.js 20+
golangci-lintandgoimportsfor 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
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:
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:
git config --global --add safe.directory /mnt/c/Users/<you>/kipperCGO_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
# 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-checkFormat Go imports with the project's local prefix before committing, so they group correctly:
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.
# 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.10kip 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:
cd console && npm install && npm run dev # http://localhost:5173It 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
| Directory | Language | Purpose |
|---|---|---|
kip/ | Go | CLI tool |
console/ | Vue 3 + TypeScript | Web dashboard |
console-api/ | Go | REST API for the console |
gateway/ | Go | kipper.run subdomain proxy |
docs/ | VitePress | This 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.
| CRD | Reconciles into |
|---|---|
App | Deployment + Service + Ingress + Secrets |
Service | StatefulSet + headless Service + credentials Secret |
Function | Deployment + Service + KEDA HTTPScaledObject + Ingress |
Project | Namespaces + shared storage PVCs |
Job | CronJob or Job |
Volume | PersistentVolumeClaim |
Key directories:
console-api/api/v1alpha1/: CRD type definitionsconsole-api/controllers/: reconcilers (one per CRD)console-api/handlers/: REST API handlers that create CRsdeploy/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-nextfor 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:
git config core.hooksPath scripts/hooksThe 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/24and2001:db8::/32. - a hostname whose domain is not in
.private-data-allowlist. Useexample.com,example.orgor a name under the reserved.exampleTLD. - 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
tesseractis installed (ffmpegfor recordings,pdftoppmfrom 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
fooorbar
Licensing
All contributions must be compatible with the Apache 2.0 license. No GPL dependencies. MIT and Apache 2.0 only.