Skip to content

Instantly share code, notes, and snippets.

@nouse
Last active December 24, 2025 10:58
Show Gist options
  • Select an option

  • Save nouse/cb1ed4a8b58c65a05e3435a026003ee7 to your computer and use it in GitHub Desktop.

Select an option

Save nouse/cb1ed4a8b58c65a05e3435a026003ee7 to your computer and use it in GitHub Desktop.
Building go app with ko and deploy to kind cluster

Build go service with ko and push to kind

Prerequisties

  • Install ko
  • Install kind
  • Have a Kubernetes cluster running (e.g., kind)

MacOS can build a k8s cluster with below after Homebrew is installed.

brew install ko kind
kind create cluster

Then build and push image to kind cluster, then rollout pods.

echo "make sure main.go is under hello/"
mkdir hello
mv main.go hello/
echo "specify linux/arm64 platform on Mac M1 machines"
env KO_DOCKER_REPO=kind.local ko build --platform linux/arm64 -B ./hello/
kubectl apply -f hello.yaml
kubectl get pods
kubectl wait --for=condition=Available deployments/hello

After update the code, rebuild and push image to kind cluster, then rollout pods.

env KO_DOCKER_REPO=kind.local ko build --platform linux/arm64 -B ./hello/
kubectl rollout restart deployment/hello
kubectl wait --for=condition=Available deployments/hello

After pods are ready, forward port to the service.

kubectl port-forward service/hello 8080:8080

Then in other terminal window run curl localhost:8080 to test, use kubectl logs -l app=hello to check logs.

module kind-ko-example
go 1.25.5
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 3
selector:
matchLabels: # Tell which pods this deployment manages
app: hello
template:
metadata:
labels: # Apply labels for pods
app: hello
spec:
containers:
- name: hello
image: kind.local/hello
imagePullPolicy: Never # https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
ports:
- port: 8080
targetPort: 8080
package main
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
rootCtx := context.Background()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// - SIGINT: Keyboard interrupt, triggered by CTRL-C.
// - SIGTERM: The default signal to ask the process to terminate gracefully.
ctx, stop := signal.NotifyContext(rootCtx, syscall.SIGINT, syscall.SIGTERM)
// Add a route to http.DefaultServeMux
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
logger.InfoContext(ctx, "request received", "method", r.Method, "url", r.URL)
w.Write([]byte("Hello, World!"))
})
go func() {
logger.InfoContext(ctx, "listening on", "port", 8080)
if err := http.ListenAndServe(":8080", nil); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
// If the port is already in use, will print this error
logger.ErrorContext(ctx, "server closed unexpectedly", "error", err)
stop()
}
}
}()
// Wait for stop() by go routine, or SIGINT and SIGTERM signals
<-ctx.Done()
logger.InfoContext(rootCtx, "server exited")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment