Self-hosted service
ko-build/ko avatar
ko-build/ko

ko: building Go container images without a Docker daemon

Build and deploy Go applications

8,555 stars448 forksGoApache-2.0

At a glance

What is it?
ko compiles a Go application and assembles a container image from the resulting binary, with no Dockerfile and no local Docker daemon. It fits single-binary Go services and Kubernetes workflows, and it is the wrong tool as soon as your image needs an OS package.
Who is it for?
Adopt ko if you ship Go services that are a single binary with no cgo and no OS packages, and you want image builds inside CI without a Docker daemon. Do not adopt it if your image needs apt packages, a JVM, a Python runtime or any base-image layering; ko replaces the Dockerfile, it does not extend it.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem ko removes: a Dockerfile for a Go binary

A Go service usually compiles to one static binary. The Dockerfile that wraps it is boilerplate: a build stage with a Go toolchain, a copy of go.mod and go.sum, a dependency download, the source copy, the build, then a runtime stage that copies the binary into a minimal base. Every one of those steps duplicates what go build already does on the developer's machine, and the two can drift.

ko starts from the other end. It runs go build locally and treats the resulting binary as the image content. The README describes it as "a simple, fast container image builder for Go applications" and says it "builds images by effectively executing go build on your local machine, and as such doesn't require docker to be installed." That last clause is the design decision that everything else follows from.

The intended audience is narrow and stated: the README calls it "ideal for use cases where your image contains a single Go application without any/many dependencies on the OS base image (e.g., no cgo, no OS package dependencies)." If your image is a Go binary plus a shell, a CA bundle you install yourself, or a native library, you are outside the target case. The README also points at lightweight CI/CD as the setting where the missing Docker daemon matters most.

How ko turns a main package into an image

The unit ko works on is a Go import path, not a directory of build context. You point it at a main package, it resolves that package's dependency graph, compiles it, and writes the binary into an image layer. The base image is chosen by ko rather than by you, which is why the OS-dependency caveat above is structural rather than a configuration gap.

The repository layout reflects this split. cmd/ holds the CLI entry points, pkg/ holds the build and publish machinery, internal/ holds code not meant for import. The module is github.com/google/ko, and go.mod shows the pieces doing the heavy lifting: github.com/google/go-containerregistry for registry interaction, github.com/opencontainers/image-spec for the image format, github.com/sigstore/cosign/v3 for signing, and k8s.io/apimachinery for the Kubernetes-side work. Credential handling is not hand-rolled either: the dependency list includes github.com/awslabs/amazon-ecr-credential-helper/ecr-login and github.com/chrismellard/docker-credential-acr-env, so ECR and ACR logins route through the same helpers other tooling uses.

Beyond the binary itself, the README names three capabilities: multi-platform builds, SBOMs produced by default, and "simple YAML templating which makes it a powerful tool for Kubernetes applications." The templating is what lets a manifest refer to an image that does not exist yet, which is the mechanism behind the ko apply and ko resolve commands. The build and the deployment are meant to be one step, not two.

Installing ko and running a first build

The README does not inline installation steps. It links to https://ko.build/install/ for installation and https://ko.build/get-started/ for a first run, and the homepage for the project is https://ko.build. Treat those two pages as the source of truth for the current install commands rather than copying a snippet from a blog post.

What the README does show is the shape of the tool. The repository ships a .ko.yaml at its top level, which is where project-wide build configuration lives, and the CLI is built from cmd/ with cobra, so subcommands are the interface. The README's own example of the workflow is the ko build, ko apply and ko resolve command family, and the demo image at docs/images/demo.png shows a build session.

The Kubernetes path is where the YAML templating earns its place. According to the README, ko supports YAML templating specifically for Kubernetes applications. A manifest carries an image placeholder, and ko substitutes the freshly built reference before applying. The practical effect is that you never edit a manifest to bump an image tag: the tag is resolved at apply time from the code that was just built. ko resolve is the companion command for the case where you want the rendered manifests without applying them.

For anything beyond a single binary, .ko.yaml is the file to read first. It is present in the repository root, so the configuration surface is real and documented in the project's own docs rather than inferred.

The base image is not yours, and that is the limitation

The README's own qualifier is the honest summary: single Go application, "without any/many dependencies on the OS base image (e.g., no cgo, no OS package dependencies)." ko picks the base. You do not get a Dockerfile, so you do not get a RUN apt-get install line, and you do not get to layer a second stage on top of a distro image you chose.

This rules out more real workloads than the phrasing suggests. Anything linking cgo, anything that shells out to a system binary, anything that needs a specific glibc or musl, and anything that ships a non-Go runtime alongside the Go binary. The common workaround in the broader ecosystem is to build the Go binary with ko and then wrap it in your own image, but at that point you have reintroduced the Dockerfile you were avoiding, and you are maintaining two build paths.

A second boundary is that ko is a Go tool for Go programs. It resolves Go import paths. It has no opinion about a Node frontend, a Rust sidecar, or a data file that needs to be baked into the image. Those are not gaps in ko's implementation; they are outside the problem it was built to solve, and the README says so up front rather than burying it.

ko against a hand-written multi-stage Dockerfile

The obvious alternative is the multi-stage Dockerfile, and the difference is not speed, it is where the build definition lives. With a Dockerfile, the image is described in a text file that the Docker daemon interprets, and the build context is a directory. The Go toolchain runs inside a build stage, in a container, using whatever Go version that stage pins.

With ko, the build definition is your Go module. The toolchain is the one on the machine running ko, the dependency graph is the one go.mod already declares, and the image is a byproduct of go build. There is no build context to keep small, because there is no context. There is no .dockerignore, because there is nothing to ignore. The trade is that you lose the ability to express anything the Dockerfile format can express, which is exactly the OS-package case above.

The second difference is the daemon. A Dockerfile build requires a Docker daemon reachable from wherever you run it, which in CI means either a privileged runner or a remote builder. ko's README states plainly that it does not require docker to be installed. For a CI runner that only needs to compile Go and push to a registry, that removes a moving part. It does not remove the registry: ko still publishes images, and the go.mod dependency list shows it carries ECR and ACR credential helpers rather than inventing its own auth path.

Maintenance, licensing and upgrade cost

The repository is not archived. The last push was on 2026-09-09, which is recent enough that the project is being worked on. The release cadence visible in the release history is v0.18.1 on 2025-12-13, then v0.19.0 on 2026-06-26 and v0.19.1 on 2026-06-29. The jump from 0.18 to 0.19 took roughly six months, and the patch followed three days later. That is a project that ships in batches rather than continuously, so pinning a version and reading the release notes before moving is the reasonable posture.

The version numbers stay below 1.0, which is worth noting when you decide how much of your deployment pipeline to hand over. The README also states that Google has applied for ko to join the Cloud Native Computing Foundation as a Sandbox project. An application is not acceptance, and the README does not say the outcome.

The licence is Apache-2.0, and LICENSE is at the repository root. Apache-2.0 is a permissive licence with an explicit patent grant and a notice requirement, which is the usual reason teams pick it for infrastructure tooling. This is a description of the licence identifier, not legal advice; if your organisation has a policy on permissive licences or on the notice file, that policy is the thing to check.

Upgrade cost is concentrated in two places. The CLI surface comes from cobra subcommands, and the configuration surface is .ko.yaml plus the Kubernetes templating. Both are the kind of thing that changes between minor versions. The go.mod requires Go 1.26.3, so the toolchain floor moves with the project. If your CI image pins an older Go, a ko upgrade can force a toolchain upgrade in the same change.

Editorial conclusion

Adopt ko if you ship Go services that are a single binary with no cgo and no OS packages, and you want image builds inside CI without a Docker daemon. Do not adopt it if your image needs apt packages, a JVM, a Python runtime or any base-image layering; ko replaces the Dockerfile, it does not extend it. Before committing, verify three things in your own repository: that your main package builds with CGO_ENABLED=0, that your Kubernetes manifests can be rewritten to reference ko's image placeholders, and that your registry credentials are available to ko through the standard Docker config or the cloud-specific credential helpers its module graph already pulls in.

Frequently asked questions

What does ko build actually do?

ko compiles a Go main package and assembles a container image from the resulting binary. The README describes it as building images by effectively executing go build on your local machine, and states that it does not require docker to be installed.

Does ko need Docker installed to build images?

No. The README states that ko builds images by effectively executing go build on your local machine and as such doesn't require docker to be installed. It still publishes to a registry, which is a separate requirement from having a local daemon.

What kind of Go application is ko not suitable for?

The README says ko is ideal where the image contains a single Go application without any or many dependencies on the OS base image, giving no cgo and no OS package dependencies as examples. If your image needs OS packages or a non-Go runtime, ko is outside its intended case.

How do you use ko with Kubernetes manifests?

The README says ko includes support for simple YAML templating which makes it a tool for Kubernetes applications. In practice a manifest references an image placeholder and ko substitutes the built image reference, which is what the ko apply and ko resolve commands do.

What is ko's licence?

The project is licensed under Apache-2.0, and the LICENSE file is at the repository root.

Official sources

  1. ko-build/ko on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ko-build-ko.svg)](https://hysenlabs.com/projects/ko-build-ko)