Refactor Dockerfile to build Go binary outside of Docker (#218)

This change moves building the Go binary out of the Dockerfile, and also
fixes some issues in the makefile. These things will speed up build
times and avoid rebuilding when not needed. Additionally it will make
getting version data easier to use as part of the user agent.

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2026-05-04 14:44:10 +02:00
committed by GitHub
parent 74012deb27
commit 59e0ee8daa
9 changed files with 79 additions and 157 deletions
+2 -3
View File
@@ -1,3 +1,2 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file **
# Ignore build and test binaries. !bin/*/netbird-operator
bin/
+1 -5
View File
@@ -25,11 +25,7 @@ jobs:
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c #v6.4.0 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c #v6.4.0
with: with:
go-version-file: go.mod go-version-file: go.mod
- name: Verify manifests are generated - name: Verify everything has been generated.
run: |
make manifests
git diff --exit-code
- name: Verify code is generated
run: | run: |
make generate make generate
git diff --exit-code git diff --exit-code
-38
View File
@@ -9,41 +9,3 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Lint Helm Chart - name: Lint Helm Chart
run: helm lint ./helm/kubernetes-operator run: helm lint ./helm/kubernetes-operator
test:
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c #v6.4.0
with:
go-version-file: go.mod
- name: Install the latest version of kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
- name: Verify kind installation
run: kind version
- name: Create kind cluster
run: kind create cluster
- name: Prepare operator
run: |
make docker-build IMG=netbirdio/kubernetes-operator:debug
kind load docker-image netbirdio/kubernetes-operator:debug
- name: Install cert-manager via Helm
run: |
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
- name: Wait for cert-manager to be ready
run: |
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook
- name: Install Helm chart for project
run: |
helm install test-chart --create-namespace --namespace netbird --set 'operator.image.tag=debug' ./helm/kubernetes-operator
- name: Check Helm release status
run: |
helm status test-chart --namespace netbird
+20 -26
View File
@@ -13,35 +13,29 @@ jobs:
packages: write packages: write
id-token: write id-token: write
steps: steps:
- name: Docker meta - name: Clone the code
id: meta uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf #v6.0.0 - name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c #v6.4.0
with: with:
images: | go-version-file: go.mod
netbirdio/kubernetes-operator - name: Set up Docker Buildx
tags: | uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0
type=ref,event=pr
type=ref,event=branch
type=semver,pattern={{version}}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }} password: ${{ secrets.DOCKER_TOKEN }}
- name: Set up QEMU - name: Build image
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a #v4.0.0 id: build
- name: Set up Docker Buildx run: |
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd #v4.0.0 if [[ "${{ github.ref_type }}" == "tag" ]]; then
- name: Build and push IMG_TAG="${GITHUB_REF_NAME#v}"
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f #v7.1.0 else
with: IMG_TAG="${GITHUB_SHA::7}"
platforms: linux/amd64,linux/arm64 fi
push: true IMG=$(make build-image-multiarch) || exit 1
tags: ${{ steps.meta.outputs.tags }} echo "IMG=${IMG}" >> $GITHUB_OUTPUT
labels: | - name: Push image
"org.opencontainers.image.created={{.Date}}" run: |
"org.opencontainers.image.title={{.ProjectName}}" docker push ${{ steps.build.outputs.IMG }}
"org.opencontainers.image.version={{.Version}}"
"org.opencontainers.image.revision={{.FullCommit}}"
"org.opencontainers.image.version={{.Version}}"
"maintainer=dev@netbird.io"
-1
View File
@@ -6,7 +6,6 @@ linters:
- dupl - dupl
- errcheck - errcheck
- ginkgolinter - ginkgolinter
- goconst
- gocyclo - gocyclo
- govet - govet
- ineffassign - ineffassign
+8 -33
View File
@@ -1,36 +1,11 @@
# This dockerfile is used for tests and local builds FROM gcr.io/distroless/static:nonroot
# Build the manager binary
FROM docker.io/golang:1.26 AS builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
LABEL org.opencontainers.image.title="NetBird Operator" \
WORKDIR /workspace org.opencontainers.image.description="Kubernetes operator for NetBird." \
# Copy the Go Modules manifests org.opencontainers.image.source="https://github.com/netbirdio/kubernetes-operator" \
COPY go.mod go.mod org.opencontainers.image.vendor="NetBird" \
COPY go.sum go.sum org.opencontainers.image.licenses="BSD-3-Clause"
# cache deps before building and copying source so that we don't need to re-download as much COPY bin/${TARGETOS}-${TARGETARCH}/netbird-operator .
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/ internal/
COPY pkg/ pkg/
# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags="-w -s" -a -o manager cmd/main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532 USER 65532:65532
ENTRYPOINT ["/netbird-operator"]
ENTRYPOINT ["/manager"]
-8
View File
@@ -1,8 +0,0 @@
# This dockerfile is used for goreleaser
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
+46 -41
View File
@@ -1,40 +1,47 @@
# Image URL to use all building/pushing image targets # Setting SHELL to bash allows bash commands to be executed by recipes.
IMG ?= docker.io/netbirdio/kubernetes-operator:latest # Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) GOARCH = $(shell go env GOARCH)
ifeq (,$(shell go env GOBIN)) ifeq (,$(shell go env GOBIN))
GOBIN = $(shell go env GOPATH)/bin GOBIN = $(shell go env GOPATH)/bin
else else
GOBIN = $(shell go env GOBIN) GOBIN = $(shell go env GOBIN)
endif endif
# Setting SHELL to bash allows bash commands to be executed by recipes. IMG_REGISTRY ?= docker.io
# Options are set to exit when a recipe line exits non-zero or a piped command fails. IMG_REPOSITORY ?= netbirdio/kubernetes-operator
SHELL = /usr/bin/env bash -o pipefail IMG_TAG ?= dev
.SHELLFLAGS = -ec IMG := $(IMG_REGISTRY)/$(IMG_REPOSITORY):$(IMG_TAG)
.PHONY: all
all: build
##@ Development
## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
.PHONY: manifests
manifests:
go tool controller-gen crd paths="./..." output:crd:artifacts:config=helm/kubernetes-operator/crds
go tool crd-ref-docs --log-level error --output-path docs/api-reference.md --renderer markdown --source-path api/v1alpha1 --config docs/.crd-ref-docs.yaml
## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
.PHONY: generate .PHONY: generate
generate: generate: api/v1/zz_generated.deepcopy.go api/v1alpha1/zz_generated.deepcopy.go pkg/applyconfigurations helm/kubernetes-operator/crds docs/api-reference.md
go tool controller-gen applyconfiguration:headerFile="hack/boilerplate.go.txt" object:headerFile="hack/boilerplate.go.txt" paths="./..."
api/v1/zz_generated.deepcopy.go api/v1alpha1/zz_generated.deepcopy.go: $(shell find api -not -name 'zz_generated*') hack/boilerplate.go.txt
@go tool controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
pkg/applyconfigurations: $(shell find api -not -name 'zz_generated*') hack/boilerplate.go.txt
@go tool controller-gen applyconfiguration:headerFile="hack/boilerplate.go.txt" object:headerFile="hack/boilerplate.go.txt" paths="./..."
@touch pkg/applyconfigurations
helm/kubernetes-operator/crds: $(shell find api)
@go tool controller-gen crd paths="./..." output:crd:artifacts:config=helm/kubernetes-operator/crds
@touch helm/kubernetes-operator/crds
docs/api-reference.md: $(shell find api) docs/.crd-ref-docs.yaml
@go tool crd-ref-docs --log-level error --output-path docs/api-reference.md --renderer markdown --source-path api/v1alpha1 --config docs/.crd-ref-docs.yaml
.PHONY: lint
lint:
@golangci-lint run ./...
.PHONY: test .PHONY: test
test: manifests setup-envtest test: setup-envtest
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v $$(go list ./... | grep -v /e2e) -coverprofile cover.out KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v $$(go list ./... | grep -v /e2e) -coverprofile cover.out
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: manifests test-e2e: generate
@command -v kind >/dev/null 2>&1 || { \ @command -v kind >/dev/null 2>&1 || { \
echo "Kind is not installed. Please install Kind manually."; \ echo "Kind is not installed. Please install Kind manually."; \
exit 1; \ exit 1; \
@@ -45,27 +52,25 @@ test-e2e: manifests
} }
go test ./test/e2e/ -v -ginkgo.v go test ./test/e2e/ -v -ginkgo.v
.PHONY: lint
lint:
@golangci-lint run ./...
##@ Build
.PHONY: build .PHONY: build
build: manifests build: generate bin/linux-$(GOARCH)/netbird-operator
go build -o bin/manager cmd/main.go
.PHONY: run bin/linux-%/netbird-operator: $(shell find api cmd internal pkg) go.mod go.sum
run: manifests @CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -ldflags="-w -s" -trimpath -o $@ cmd/main.go
go run ./cmd/main.go
.PHONY: docker-build .PHONY: build-image
docker-build: build-image: build
docker build -t ${IMG} . @docker buildx build -t ${IMG} .
@echo ${IMG}
.PHONY: build-image-multiarch
build-image-multiarch: generate bin/linux-amd64/netbird-operator bin/linux-arm64/netbird-operator
@docker buildx build --platform linux/amd64,linux/arm64 -t ${IMG} .
@echo ${IMG}
## Generate a consolidated YAML with CRDs and deployment. ## Generate a consolidated YAML with CRDs and deployment.
.PHONY: build-installer .PHONY: build-installer
build-installer: manifests build-installer: generate
mkdir -p manifests mkdir -p manifests
helm template --include-crds kubernetes-operator helm/kubernetes-operator > manifests/install.yaml helm template --include-crds kubernetes-operator helm/kubernetes-operator > manifests/install.yaml
@@ -77,17 +82,17 @@ endif
## Install CRDs into the K8s cluster specified in ~/.kube/config. ## Install CRDs into the K8s cluster specified in ~/.kube/config.
.PHONY: install .PHONY: install
install: manifests install: generate
kubectl apply --server-side -f helm/kubernetes-operator/crds kubectl apply --server-side -f helm/kubernetes-operator/crds
## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
.PHONY: uninstall .PHONY: uninstall
uninstall: manifests uninstall: generate
kubectl delete -f helm/kubernetes-operator/crds kubectl delete -f helm/kubernetes-operator/crds
## Deploy controller to the K8s cluster specified in ~/.kube/config. ## Deploy controller to the K8s cluster specified in ~/.kube/config.
.PHONY: deploy .PHONY: deploy
deploy: manifests deploy: genereate
helm install -n netbird --create-namespace kubernetes-operator --set operator.image.tag=$(word 2,$(subst :, ,${IMG})) helm/kubernetes-operator helm install -n netbird --create-namespace kubernetes-operator --set operator.image.tag=$(word 2,$(subst :, ,${IMG})) helm/kubernetes-operator
## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
+1 -1
View File
@@ -4,7 +4,7 @@ This example walks you through how to setup a Netbird Gateway API and expose Ngi
Build image locally and load it into Kind. Build image locally and load it into Kind.
```shell ```shell
make docker-build IMG=docker.io/netbirdio/kubernetes-operator:dev make build-image
kind load docker-image docker.io/netbirdio/kubernetes-operator:dev kind load docker-image docker.io/netbirdio/kubernetes-operator:dev
``` ```