Building a Go Package with Melange and a Docker Image with Apko
Tags: go, docker, cybersecurity
Published: 2023-12-21 12:52:04

> In this tutorial, we will learn how to create a Go package with Melange, build a Docker with Apko image, and showcase examples using GitLab CI.

## Prerequisites

Before we begin, make sure you have the following tools installed on your system:

- [Docker](https://docs.docker.com/get-docker/)
- [Golang](https://go.dev/doc/install)
- [Melange](https://github.com/chainguard-dev/melange)
- [Apko](https://github.com/chainguard-dev/apko)

## Keyfile Generation

To use Melange and Apko, we need to create a private and public key pair to sign our artifacts:

```bash
melange keygen
```

You should now see a `melange.rsa` and `melange.rsa.pub` file in your directory. These files should not be committed and added to the `.gitignore` file just like the `/packages`.
This directory will be created ones we run melange to build our Go Application.

Additionally create a sbom directory with a `.gitkeep` file and add `sbom/sbom-*.*` and `image.tar` to the `.gitignore` file as they will become relevant once we create the Docker image.

## Creating a Go Package with Melange

Initialize a Go module:

```bash
go mod init gitlab.com/your-username/golang-apko-example
```

Please replace `your-username` with your actual GitLab username in the `go mod init` command.

Create your Go source code files and write your package logic.

Create a `melange.yml` file in the root of your package directory. This file will define the build configuration for your package:

```yaml
package:
  name: golang-apko-example
  version: 0.1.0
  epoch: 0
  description: Build a golang application with melange
  copyright:
    - license: MIT
  target-architecture:
    - x86_64

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

pipeline:
  - uses: go/build
    with:
      modroot: .
      packages: .
      output: golang-apko-example
  - uses: strip
```

Customize the `name`, `version` and `description` fields according to your package's requirements. Also, make sure you check the name of the output.

Build your package locally with Melange:

```bash
melange build --signing-key melange.rsa --runner docker melange.yml
```

This will generate an `APKINDEX.json`, `APKINDEX.tar.gz` and `.apk` file in your packages directory.

## Building a Docker Image with apko.yml

Create an `apko.yml` file in the root of your package directory. This file will define the Docker image build configuration:

```yaml
contents:
  keyring:
    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub
  repositories:
    - https://packages.wolfi.dev/os
  packages:
    - wolfi-base
    - ca-certificates-bundle
    - golang-apko-example@local

accounts:
  groups:
    - groupname: nonroot
      gid: 65532
  users:
    - username: nonroot
      uid: 65532
      gid: 65532
  run-as: 65532

archs:
  - amd64

cmd: /usr/bin/golang-apko-example
```

The important line is the `golang-apko-example@local` package signaling a local package should be used instead of the wolfi packages.

Build the Image with your local repository appended:

```bash
apko build --debug --sbom-path ./sbom/ --repository-append $(pwd)/packages --keyring-append=melange.rsa.pub apko.yml golang-apko-example:latest image.tar
```

This will build a Docker image as an image.tar with our local package and create SBOM files for our image.

To actually use the image locally we have to load and execute it:

```bash
docker load --input image.tar
docker run -it golang-apko-example:latest-amd64
```

## Using Gitlab CI

### Environment Variables

With Gitlab CI we can automate this process and make it even better.
However, before we can begin we have to save the `melange.rsa` and `melange.rsa.pub` as Gitlab CI Environment Files so we can use them. To do this simply go to `Settings` --> `CI/CD` --> `Variables` and `Add variable` of the Type `File` our two files:

- MELANGE_RSA: File content of `melange.rsa`
- MELANGE_PUB: File content of `melange.rsa.pub`

You might want to store your certificates in a safer location then Gitlab Environments but lets just stick with this for now.

### Gitlab CI Preparation

Now we can create a `.gitlab-ci.yml` file in the root of your GitLab repository.

At first we should define some general structure:

```yaml
stages:
  - build
  - containerize

variables:
  APKO_FILE: "apko.yml"
  MELANGE_FILE: "melange.yml"
  FULL_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest
```

Just like in our CLI we have 2 Steps to complete:

- Build the Golang Application
- Build the Docker image

Currently we simply create a Docker image without any versioning: The name of our image will be the name of the repository name including the path and using latest as the tag.

### Building the Application with Melange

We can use the same command to build the application in Gitlab CI as we did locally. We just have to make sure that our melange.rsa key exists:

```yaml
build_package:
  stage: build
  image:
    name: cgr.dev/chainguard/melange:latest
    entrypoint: [""]
  before_script:
    - melange version
    - cat ${MELANGE_RSA} > melange.rsa
  script:
    - melange build --signing-key melange.rsa "${MELANGE_FILE}"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != "schedule"
  artifacts:
    paths:
      - ./packages
```

Since the default entrypoint of melange is melange itself we have to overwrite to entrypoint so that we can use it in Gitlab CI.
After that we make sure that our key exists and can simply build our application.
The only thing left to do is store the `packages` directory as an artifact which can use in the next step.

### Publishing the Docker Image with Apko

We now need to build and publish the Docker Image to the Gitlab Registry. In this step we will need the public key instead of the private key. In addition we can use the predefined Registry Variables to log into the Gitlab Registry and publish the image:

```yaml
containerize_package:
  stage: containerize
  image: registry.gitlab.com/stammkneipe.dev/apko-ci:latest
  variables:
    CI_BUILDS_DIR: $CI_BUILDS_DIR
  before_script:
    - apko version
    - apko login -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
    - cat ${MELANGE_PUB} > melange.rsa.pub
  script:
    - apko publish --debug --sbom-path $CI_PROJECT_DIR/sbom/ --repository-append $(pwd)/packages --keyring-append melange.rsa.pub "${APKO_FILE}" "${FULL_IMAGE_NAME}"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != "schedule"
  artifacts:
    paths:
      - "sbom/*"
```

This will result in a Docker Image directly instead of a tar file. You should be able to see your docker image in the Gitlab Registry (/container_registry/) with information on how to use it.
In addition we can see the SBOM files in the artifacts of the pipeline.

## Conclusion

See the full example [here](https://gitlab.com/stammkneipe.dev/golang-apko-example).