# Getting started with the Go Chainguard Container

URL: https://chainguard-docs-preview-git-fix-learning-lab-jsonld.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/go.md
Last Modified: October 2, 2026
Tags: Chainguard Containers

Learn how to build more secure Go applications with Chainguard's Go container images, featuring minimal attack surface and multi-stage build patterns for optimized runtime


Chainguard's [Go container image](https://images.chainguard.dev/directory/image/go/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) provides a secure foundation for building Go applications. The distroless `latest` variant contains only the Go compiler and runtime, while the `latest-dev` variant includes additional build tools and package management capabilities for development workflows.

In this guide, we'll demonstrate how to build and execute Go applications using Chainguard Containers, using three examples from our [demos repository](https://github.com/chainguard-dev/edu-images-demos). In the first example, we'll build a CLI application using a Docker multi-stage build. In the second example, we'll build an application that's accessible by HTTP server, also using a Docker multi-stage build to obtain an optimized runtime. The third example shows how to build an image using [ko](https://ko.build/), a tool that enables you to build container images from Go programs and push them to container registries without requiring a Dockerfile.

The examples in this guide recommend executing Go binaries from one of our runtime Chainguard Containers, such as the [`glibc-dynamic`](https://images.chainguard.dev/directory/image/glibc-dynamic/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) or [`static`](https://images.chainguard.dev/directory/image/static/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) Chainguard Containers. That is possible because Go applications are compiled and the toolchain is not typically required in a runtime container image.

**What is distroless?**


Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.





**What is Wolfi?**


[Wolfi](https://github.com/wolfi-dev/) is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.




**What are multi-stage builds?**


<p><a href="https://docs.docker.com/build/building/multi-stage/">Multi-stage builds</a> are a Docker feature that allow you to use multiple <code>FROM</code> statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.</p>

<p>This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.</p>

<p>Chainguard's container images are designed with this pattern in mind. Most have a <a href="/chainguard/containers/concepts/container-variants/"><code>:latest-dev</code> development variant</a> suited for use as a builder stage, and a corresponding <code>:latest</code> (or <code>-slim</code>) standard image for the distroless runtime. For example, a Go application can be compiled in the <code>go:latest-dev</code> builder stage and its binary copied into a <code>static</code> or <code>glibc-dynamic</code> runtime image — with no Go toolchain in the final container.</p>




**Chainguard Containers**


[Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.




## Preparation

This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/). The third and optional example requires the installation of [ko](https://ko.build/), which you can install by following the instructions on the official site.

### Cloning the demos repository

Start by cloning the demos repository to your local machine:

```shell
git clone git@github.com:chainguard-dev/edu-images-demos.git
```

Access the `go` folder in the repository:

```shell
cd edu-images-demos/go
```

Here you will find three folders, each with a different demo that we'll cover in this guide.

## Example 1: CLI application in multi-stage build

The following example demonstrates a command line application with support for flags and positional arguments. The application prints a modifiable greeting message and provides usage information if the wrong number of arguments are passed by a user or the user passes an unrecognized flag.

Start by accessing the `go-greeter` folder in the demos repository:

```shell
cd go-greeter
```

For reference, here is the content of the `Dockerfile` for this demo:

```Dockerfile
FROM cgr.dev/chainguard/go AS builder
COPY . /app
RUN cd /app && go build -o go-greeter .

FROM cgr.dev/chainguard/static
COPY --from=builder /app/go-greeter /usr/bin/
ENTRYPOINT ["/usr/bin/go-greeter"]
```

This Dockerfile will:

1. Start a build stage based on the `go:latest` container image and name it `builder`;
2. Copy the application files to the `/app` directory in the image;
3. Build the application in the `/app` directory;
4. Start a new build stage based on the `static:latest` image;
5. Copy the built application from the `builder` stage to the `/usr/bin` directory in the new image;
6. Set the entrypoint to the built application.

Run the following command to build the container image, tagging it `go-greeter`:

```shell
docker build . --pull -t go-greeter
```

You can now run the container image with:

```shell
docker run go-greeter
```

You should get output similar to the following:

```
Hello, Linky 🐙!
```

You can also pass in arguments that will be parsed by the Go CLI application:

```shell
docker run go-greeter -g Greetings "Chainguard user"
```

This will produce the following output:

```
Greetings, Chainguard user!
```

The application will also share usage instructions when prompted with the `--help` flag or when invalid flags are passed.

Because we used the `static` Chainguard Container as our runtime, the final container image only requires a few megabytes on disk:

```shell
docker inspect go-greeter | jq -c 'first' | jq .Size | numfmt --to iec --format "%8.4f"
```

```
 3.3009M
```

The final size, `3.309M`, is orders of magnitude smaller than it would be running the application using a Go image. However, if your application is dynamically linked to shared objects, consider using the `glibc-dynamic` Chainguard Container for your runtime or take extra steps to build your Go binary statically. In the next example, we'll build a web application and use the `glibc-dynamic` Chainguard Container as runtime.

## Example 2: Web application

The second example demonstrates an application that's accessible by HTTP server. The application renders a simple message that changes based on the URI.

Start by accessing the `greet-server` folder in the Go demos repository:

```shell
cd greet-server
```

For reference, here is the content of the `Dockerfile` for this demo:

```Dockerfile
FROM cgr.dev/chainguard/go AS builder
COPY . /app
RUN cd /app && go build

FROM cgr.dev/chainguard/glibc-dynamic
COPY --from=builder /app/greet-server /usr/bin/

EXPOSE 8080

ENTRYPOINT ["/usr/bin/greet-server"]
```

Use the following command to build the container image, tagging it `greet-server`:

```shell
docker build . --pull -t greet-server
```

Now you can run the container image with the following command:

```shell
docker run -p 8080:8080 greet-server
```

Visit `http://0.0.0.0:8080/` using a web browser on your host machine. You should get a greeting message:

```
Hello, Linky 🐙!
```

Changes to the URI will be routed to the application. Try visiting [http://0.0.0.0:8080/Chainguard%20Customer](http://0.0.0.0:8080/Chainguard%20Customer). You should see the following output:

```
Hello, Chainguard Customer!
```

The application will also share version information at [http://0.0.0.0:8080/version](http://0.0.0.0:8080/version).

## Example 3: Minimal Go Chainguard Container built with ko

In this example, we'll build a distroless Go Chainguard Container with [ko](https://ko.build/). ko offers fast container image builds for Go applications without requiring a Dockerfile. Additionally, ko produces [SBOMs](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom) by default, supporting a holistic approach to software security.

Start by accessing the `go-digester` folder in the Go demos repository:

```shell
cd go-digester
```

The `go-digester` demo uses the `go-containerregistry` library to print out the digest of the latest build of a Chainguard Container, using `go` as the default image to pull the digest from, and with an optional parameter to specify a different container image name. If you have Go installed locally, you can run the application with:

```shell
go run main.go
```

You should obtain output similar to this:

```
The latest digest of the go Chainguard Container is sha256:86178b42db2e32763304e37f4cf3c6ec25b7bb83660dcb985ab603e3726a65a6
```

We'll now use ko to build an image that is suitable to run the application defined in `main.go`. By default, ko uses the `cgr.dev/chainguard/static` image as the base image for the build. You can override this by setting the `KO_DEFAULTBASEIMAGE` environment variable to a different base image.

Before building the container image, you'll need to set up the environment variable `KO_DOCKER_REPO`. This environment variable identifies where ko should push images that it builds. This is usually a remote registry like the GitHub Container registry or Docker Hub, but you can publish to your local machine for testing and demonstration purposes.

Run the following command to set the `KO_DOCKER_REPO` environment variable to your local machine:

```shell
export KO_DOCKER_REPO=ko.local
```

Next, ensuring that you are in the same directory as your `main.go` file, run the following command to build the container with ko:

```shell
ko build .
```

Once you run this command, you'll receive output similar to the following.

```
2024/12/06 13:03:14 Using base cgr.dev/chainguard/static:latest@sha256:5ff428f8a48241b93a4174dbbc135a4ffb2381a9e10bdbbc5b9db145645886d5 for go-digester
2024/12/06 13:03:15 git doesn't contain any tags. Tag info will not be available
2024/12/06 13:03:15 Building go-digester for linux/amd64
2024/12/06 13:03:20 Loading ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336
2024/12/06 13:03:20 Loaded ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336
2024/12/06 13:03:20 Adding tag latest
2024/12/06 13:03:20 Added tag latest
ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336
```

At this point, your container is built. Because the output of `ko build` is an image reference, you can pass it to other tools like Docker. You can learn more about [deployment with ko](https://ko.build/deployment/) and [Kubernetes integration](https://ko.build/features/k8s/) by reading the respective documentation on the official site.

We'll demonstrate running the above built container with Docker.

> **Note**: To follow along, be sure that you copy and paste the last line of output from your last command that begins `ko.local/go-digester-...`

```shell
docker run --rm ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336
```

Here, you'll expect to receive the same output as before that shows the digest of the Go container.

```
The latest digest of the go Chainguard Container is sha256:86178b42db2e32763304e37f4cf3c6ec25b7bb83660dcb985ab603e3726a65a6
```

You can also pass in an optional argument to specify which Chainguard Container to pull the latest digest from:

```shell
docker run --rm ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 mariadb
```

```
The latest digest of the mariadb Chainguard Container is sha256:6ba5d792d463b69f93e8d99541384d11b0f9b274e93efdeb91497f8f0aae03d1
```

## Advanced usage

<p>If your project requires a more specific set of packages that aren't included within the general-purpose Go Chainguard Container, you'll first need to check if the package you want is already available on the <a href="https://github.com/wolfi-dev/os" title="Wolfi on GitHub">wolfi-os repository</a>. </p>

<blockquote>
    <p><b>Note</b>: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with <code>apk add</code> you need to use the <code>USER root</code> directive.</p>
</blockquote>

<p>If the package is available, you can use the <a href="https://images.chainguard.dev/directory/image/wolfi-base/overview" title="Overview of wolfi-base image">wolfi-base</a> image in a Dockerfile and install what you need with <code>apk</code>, then use the resulting image as the base for your app.
For a worked example, see the <a href="/chainguard/containers/using-and-deploying/using-containers/#using-the-wolfi-base-container-image-within-dockerfiles">Using the wolfi-base container image within Dockerfiles</a> section of our <a href="/chainguard/containers/using-and-deploying/using-containers/" title="How to use Chainguard Containers">guide to using Chainguard Containers</a>.
</p>

<p>If the packages you need are not available, you can build your own apks using <a href="/open-source/build-tools/melange/">melange</a>. To get started, see <a href="/open-source/build-tools/melange/getting-started-with-melange/">Getting started with melange</a>.</p>


