Skip to content

Building

Prerequisites

  • Go 1.23+
  • wireguard-tools (for testing)
  • podman or docker (for container images)

Build Binaries

make build

Or build individual components:

make build-agent
make build-relay
make build-wirekubectl

Cross-Compilation

The agent uses Linux-specific netlink APIs. When building on macOS, always cross-compile:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
  go build -ldflags="-s -w" -o bin/agent-linux-amd64 ./cmd/agent/

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
  go build -ldflags="-s -w" -o bin/agent-linux-arm64 ./cmd/agent/

CGO_ENABLED=0 produces a fully static binary with no external dependencies.

Build Container Image

make docker-build

Or manually:

podman build --platform linux/amd64,linux/arm64 \
  --manifest inerplat/wirekube:latest .

podman manifest push inerplat/wirekube:latest \
  docker://docker.io/inerplat/wirekube:latest

Single Architecture

docker build -t inerplat/wirekube:latest .
docker push inerplat/wirekube:latest

CI/CD

Tag builds run formatting, vet, lint, unit tests, and the TCP/WSS disposable-cluster E2E matrix before publishing anything. After those gates pass, the workflow publishes the multi-architecture image, builds four standalone wirekubectl binaries, injects the version, commit, build date, and immutable image digest, and creates a GitHub Release with SHA256 checksums.

Cutting a release

The manifests under config/ and the chart's appVersion pin a release tag, and the image for that tag does not exist until the tag is pushed. So the pin lands first and is verified afterwards:

  1. Update config/** image tags, charts/wirekube/Chart.yaml appVersion, and the VERSION= example in docs/getting-started/installation.md to the tag you are about to cut. Commit to main.
  2. Push the tag. release.yml publishes the image, then its verify-pins job re-checks every pin with verifyflags -strict.

CI's manifest-images job runs the same check on main without -strict, so an as-yet-unpublished tag reports PENDING rather than failing. Anything else it reports is real: REJECT means the pinned binary does not define a flag the manifest passes, which is a CrashLoopBackOff on install.

CONTAINER_ENGINE=podman go run ./hack/verifyflags $(git ls-files 'config/**/*.yaml')

The E2E suite overrides the image with the one it just built, so it cannot catch a stale pin. This check is the only thing that does.

Homebrew tap

The public formula lives in inerplat/homebrew-tap as Formula/wirekube.rb. After publishing a WireKube release, update the formula's version, four platform URLs, and four SHA-256 values from wirekubectl-checksums.txt, then verify it on a clean tap checkout:

brew style inerplat/tap/wirekube
brew audit --strict inerplat/tap/wirekube
brew install inerplat/tap/wirekube
brew test inerplat/tap/wirekube
wirekubectl version

Do not point the formula at latest, a mutable container tag, or an unverified binary. The released CLI embeds the matching immutable WireKube container image digest used by wirekubectl install and wirekubectl upgrade.

Dockerfile

The multi-stage Dockerfile:

  1. Builder stage: Go 1.23 Alpine, builds agent, relay, operator, and wirekubectl
  2. Runtime stage: Alpine 3.21 with wireguard-tools, iptables, iproute2

Run Tests

make test          # go test ./... -v
make vet           # go vet ./...
make fmt           # go fmt ./...

go test -v ./pkg/agent/...          # specific package
go test -v -run TestEndpointDiscovery ./pkg/agent/...  # specific test

Code Generation

After modifying types in pkg/api/v1alpha1/ (especially +kubebuilder: markers):

make generate      # deepcopy functions
make manifests     # CRD YAML from types

Generated files in config/crd/ must be committed alongside type changes.

Project Structure

wirekube/
├── cmd/
│   ├── agent/           # Agent entrypoint
│   ├── admin-web/       # External-peer management web UI
│   ├── relay/           # Relay server entrypoint
│   ├── stun-server/     # Development STUN server
│   └── wirekubectl/     # CLI entrypoint
├── pkg/
│   ├── agent/           # Agent logic (endpoint discovery, peer sync)
│   │   ├── nat/         # STUN and UPnP endpoint discovery
│   │   └── relay/       # Relay client, Bind delivery fallback proxy, relay pool
│   │       ├── client.go   # TCP client with auto-reconnect
│   │       ├── proxy.go    # Legacy/fallback per-peer UDP proxy
│   │       └── pool.go     # Multi-instance relay pool
│   ├── api/v1alpha1/    # Mesh, peer, gateway, and external-peer CRD types
│   ├── controller/      # Embedded external-peer reconciler and stub legacy reconcilers
│   ├── relay/           # Relay server and wire protocol
│   └── wireguard/       # WireGuard interface, routing, xfrm bypass
├── config/
│   ├── agent/           # Separate RBAC, DaemonSet, and ServiceMonitor manifests
│   ├── crd/             # CustomResourceDefinition YAMLs (generated)
│   ├── relay/           # Relay deployment + service examples
│   └── examples/         # WireKubeMesh and EKS Hybrid Node examples
├── charts/wirekube/     # Helm chart (appVersion tracks the latest release tag)
├── docs/                # Documentation (MkDocs Material)
├── hack/
│   └── verifyflags/     # Checks pinned images accept their manifest args
├── .github/workflows/   # CI (tag-triggered build + test)
├── Dockerfile
├── Makefile
├── mkdocs.yml
└── go.mod