Contributing
Kipper is open source under the Apache 2.0 license. Contributions are welcome.
Development setup
Prerequisites
- Go 1.26+
- 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. Local development uses the cluster’s Dex login, the same authentication path as production.
Testing a branch on a test cluster
To try a branch end to end before it merges, run its code on a test cluster with kip alone. Never do this on a cluster that serves real apps.
Make sure the branch carries the image workflow that keeps
:latestformain.gh workflow run --refruns the workflow file from the branch itself, and on a branch cut before that change a run publishes all its images as:latest, which every cluster pulls. Rebase ontomain, push, and check the pushed copy:bashgit fetch origin && git rebase origin/main && git push --force-with-lease git show origin/my-branch:.github/workflows/build-images.yml | grep -q "refs/heads/main" && echo "safe to build"Build
kipfrom that same revision, so the CRDs, data checks and CLI behavior match the images:bashcd kip && go build -o kip . && cd ..Then build the images. A run from a branch that passed step 1 publishes them under the commit sha only:
bashgh workflow run build-images.yml --ref my-branch gh run watchUpgrade the test cluster to that commit's console, instance proxy and data mover images. Apps using the instance proxy roll out when its image changes:
bash./kip/kip upgrade --cluster my-test-cluster --skip-system --image-tag "$(git rev-parse HEAD)"Run the scenario, or the matching script under
tests/e2e/, with./kip/kip.Return the cluster to the released images and CRDs with a released
kip:bashkip upgrade --cluster my-test-cluster --skip-system
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"> - Define specific TypeScript 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
Branch off main, and open the pull request against main. There is no long-lived integration branch, and a branch is deleted once it merges.
Format: prefix/short-description
Prefixes: feature/, bugfix/, hotfix/, refactor/, docs/, test/
main keeps a linear history and merges by rebase, so keep a branch to one piece of work and let it go when it lands. Checks run on pull requests against main, so a request aimed anywhere else gets none.
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
- Use realistic names in examples
Licensing
All contributions must be compatible with the Apache 2.0 license. No GPL dependencies. MIT and Apache 2.0 only.