ko: fast Kubernetes microservice development in Go

Using ko for fast microservices development in Kubernetes.

I originally wrote ko to help Knative developers. I was prompted to write this introductory post by the positive feedback from the community, including an IBM booth talk on ko during recent Kubecon Seattle 2018. I hope you enjoy using ko as much as we do, and I look forward to your feedback on .

Over the past few years, there has been a lot of hype about containers. Docker, Kubernetes and related technology have taken the public cloud by storm (pun intended). At the same time, it seems, as software projects grow increasingly more complex, so too does the development process.

What starts as:

Quickly becomes:

Tools such as skaffold can wrap this process for arbitrary languages and Dockerfiles to make it easier to manage (and faster), but you still need to write artisanal hand-crafted Dockerfiles , and typically need to write more yaml (or other) to tell the tooling how to orchestrate this (e.g. what gets pushed where?):

ko takes a different approach that leans into Go idioms to eliminate configuration.

One such Go idiom is that binaries are referenced by “import paths”; a typical way of installing a Go binary would be:

# e.g. installing ko itself
go get

Getting started with ko does not take any additional configuration files, you simply replace references to container images with import paths:

# This example is based on:
apiVersion: v1
kind: Pod
name: kodata
- name: test
# ko builds and publishes this Go binary, and replaces this
# with an image name.
restartPolicy: Never

That’s it.

How do I consume this with ko ?

ko also needs to know where the user wants to publish their images. This is defined outside of the yaml manifest as generally each developer on your team will use their own.

For example, developing on Knative, I use this in my .bashrc file:


NOTE: for DockerHub users (and possibly others), this should be: as DockerHub does not support multi-level repository names.

After that, the command-line interface is modeled after kubectl:

ko apply -f directory/ -f file.yaml

This will have the same net effect as kubectl apply, but it will also build, containerize, and publish the Go microservices referenced from the yamls as well, with significantly less configuration:

You only write Kubernetes yamls and code. No Dockerfiles, no Makefiles. You run one command and your latest code is running.

Following the above example (trimmed for width):

$ ko apply -f cmd/ko/test/test.yaml
Using base .. for
mounted blob: sha256:deadbeef
mounted blob: sha256:baadf00d
pushed blob sha256:deadf00d
pushed blob sha256:baadbeef
pushed blob sha256:beeff00d digest: ... size: 915
pod/kodata created
~/go/src/$ kubectl get pods
kodata 0/1 Completed 0 1

Just the image

The simplest trick that ko supports is to simply containerize and publish an image. One neat thing about this is that it works with most Go binaries without any knowledge of ko.

For example (trimmed for width):

$ ko publish ./cmd/goimports/
Using base .. for
mounted blob: sha256:deadbeef
mounted blob: sha256:baadf00d
mounted blob: sha256:deadf00d
pushed blob sha256:baadbeef
pushed blob sha256:beeff00d digest: ... size: 914

ko is for releases too!

You can also use ko to publish things for redistribution via:

# This does everything `apply` does except it pipes to
# stdout instead of kubectl
ko resolve -f config/ > release.yaml
# Later...
kubectl apply -f release.yaml

For example, we use this to release all of the Knative components.

Try it out, and tell us what you think.

This just scratches the surface of what you can do with ko, and what ko does for you. For more information check out the If you have questions: #ko on, or reach me on Twitter @mattomata.

Some Common Pitfalls

A couple of things to be careful of with ko because of how heavily it relies on convention:

  1. You need to be on ${GOPATH} or it will not know what package you are in.
  2. Typos are the worst. Because ko is insensitive to schemas, it will ignore any string that is not the import path of a “main” package, so if you have a simple typo in your import path then it will be left as is and you will likely see your Pod ErrImagePull.