# Chainguard Images in Practice

> Source: https://www.ymotongpoo.com/books/chainguard-image-toolchain/60-chainguard-images-practice/


The chapters so far have run melange and apko individually. This chapter checks how the two tools come together in Chainguard's own image distribution.

## The build is really just a Makefile and CLI calls

If you actually look at the [wolfi-dev/os](https://github.com/wolfi-dev/os) repository that manages Wolfi's package definitions, what drives the build is a `Makefile` sitting right at the repository root. The build proceeds by calling the `melange` and `apko` command-line tools directly — there's no Bazel `BUILD` file or `WORKSPACE` file anywhere. There's no GitHub Actions workflow definition under `.github/` either — just trust configuration that lets Chainguard's internal automation tooling write to this repository over OIDC. In other words, this doesn't depend on Bazel, and it doesn't depend on a public GitHub Actions workflow either.

In other words, the procedure we've confirmed throughout this book — write a `melange.yaml` and run `melange build`, write an `apko.yaml` and run `apko build` (or `apko publish`) — isn't a simplified stand-in. It's essentially what Chainguard actually does. Scale aside, the skeleton of the pipeline is the same one you exercised by hand in Chapters 5 and 7.

## Applying this to your own Go application

So far we've used GNU Hello, an existing C program, as our subject. Let's consider applying this to your own application instead. A typical Go application's Dockerfile is usually written as a multi-stage build like this:

```dockerfile
FROM golang:1.23 AS builder
WORKDIR /src
COPY . .
RUN go build -o /out/app .

FROM scratch
COPY --from=builder /out/app /app
ENTRYPOINT ["/app"]
```

Replacing this with melange and apko, melange's `go/build` action takes over the build stage, and apko takes over everything after `FROM scratch`. Building on melange's [examples/go-build.yaml](https://github.com/chainguard-dev/melange/blob/main/examples/go-build.yaml), `melange.yaml` looks like this:

```yaml
package:
  name: hello-go
  version: 0.0.1
  epoch: 0
  description: "A project that will greet the world infinitely"

environment:
  contents:
    keyring:
      - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
    repositories:
      - https://packages.wolfi.dev/os

pipeline:
  - uses: git-checkout
    with:
      repository: https://github.com/puerco/hello.git
      expected-commit: a73c4feb284dc6ed1e5758740f717f99dcd4c9d7
      tag: v${{package.version}}

  - uses: go/build
    with:
      packages: .
      output: hello-go
```

`git-checkout` fetches the source, and you just hand `go/build` the package to build (`packages: .`) and the binary name to output (`output: hello-go`). Everything a Dockerfile's `RUN go build` would do is concentrated into this single `go/build` action. The build command is the same as in Chapter 5:

```shell
docker run --privileged --rm -v "${PWD}":/work \
  cgr.dev/chainguard/melange build hello-go.yaml \
  --arch x86_64 --signing-key melange.rsa
```

Assemble the resulting `hello-go` APK package into an image with apko. `apko.yaml` looks almost identical to the Chapter 7 example:

```yaml
contents:
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
    - melange.rsa.pub
  repositories:
    - https://packages.wolfi.dev/os
    - "@local ./packages"
  packages:
    - wolfi-baselayout
    - hello-go@local

entrypoint:
  command: /usr/bin/hello-go

accounts:
  run-as: 65532

archs:
  - x86_64
```

The Dockerfile's second half, which loaded only a minimal set of files starting from `FROM scratch`, is replaced by a `contents.packages` that lists only `wolfi-baselayout` and your own binary. Go binaries are usually statically linked, so there's almost never a need to specify individual runtime libraries. From here, the same `apko build` (or `apko publish`) as Chapter 7 finishes the image, without writing a single line of Dockerfile.

## The mechanism behind rebuilding from source every day

Chainguard Images (the set of images published under the [chainguard-images](https://github.com/chainguard-images) organization) are operated so that they're [rebuilt from source every day](https://www.chainguard.dev/containers), starting from Wolfi's package definitions and apko configuration files. As we confirmed in Chapter 3, every package in Wolfi is built with melange, so when a vulnerability fix lands upstream, updating Wolfi's package definition and rebuilding with melange is all it takes to get an APK package that reflects the fix. apko just recomposes the image from that set of APK packages, so every build keeps distributing an image with fewer CVEs, reflecting the latest package state.

This works because it's the flip side of the problem we confirmed in Chapter 2. In a Dockerfile-based approach, it was hard to mechanically tell from outside how a base image's contents were put together, and accurately tracking vulnerabilities meant relying on scanning. With melange and apko together, an image's contents are declared as a list of APK packages, and the SBOM is also generated from information internal to the build process, so you can always accurately tell exactly which version of which package is included.

## Options for using this in a Bazel monorepo

As we've seen, Bazel doesn't appear anywhere in Chainguard's own build pipeline. That said, for teams that manage their own applications in a Bazel monorepo and want to pull in Chainguard Images as a base image, Chainguard publishes a set of Bazel rules called [rules_apko](https://github.com/chainguard-dev/rules_apko). This is a wrapper that integrates running apko into Bazel's build graph — an integration layer built for external users of Bazel, separate from Chainguard's own internal build. For readers who don't manage their applications in a Bazel monorepo, this falls outside the scope of this book.

## Surveying the whole ecosystem

To recap the relationship among the pieces covered in this book:

| Piece | Role |
|---|---|
| Wolfi | The Linux distribution that supplies APK packages |
| melange | The tool that builds APK packages from source code. Every one of Wolfi's packages is built with it |
| apko | The tool that assembles an OCI image purely from APK packages |

These are developed as separate GitHub repositories, but they function as a single, coherent pipeline through the shared APK package format and shared verification mechanisms — SBOMs and checksums. The next chapter looks back over the whole book and considers where to start if you were to adopt this in your own project.

