SEANK.H.LIAO

melange and apko

building containers halfway declaratively

apko and melange

apko and melange are 2 interesting tools coming from chainguard.dev aiming for declarative builds of alpine packages and containers based on the apk ecosystem.

melange

melange is for building apk packages, generating, and signing repository indexes. It consists of: package metadata, packages to be installed in the build environment, and a linear pipeline of steps, using either reusable stages declared in yaml on disk, or inline scripts. A config for building a Go application might look like:

 1# package metadata
 2package:
 3  name: my-package
 4  description: some description
 5  version: 0.0.1
 6  epoch: 0
 7  target-architecture:
 8    - all
 9  copyright:
10    - paths:
11        - "*"
12      license: MIT
13      # attestation
14  # dependencies:
15  #   runtime:
16  #   provides:
17  # scriptlets:
18  #   pre-install:
19  #   post-install:
20  #   ...
21
22# build environment
23environment:
24  contents:
25    repositories:
26      - https://dl-cdn.alpinelinux.org/alpine/edge/main
27      - https://dl-cdn.alpinelinux.org/alpine/edge/community
28    packages:
29      # always necessary
30      - alpine-baselayout-data
31      # i think things break without this
32      - busybox
33      # connect to the outside world, eg GOPROXY
34      - ca-certificates-bundle
35      # buildvcs stamping
36      - git
37      # build tool
38      - go
39
40pipeline:
41  # melange comes with some basic declarative pipelines
42  # (someone else wrote the script in yaml for you)
43  # but you can always just include a shell script
44  - runs: |
45      mkdir -p "${{targets.destdir}}"
46      go build -trimpath -ldflags='-s -w' -o "${{targets.destdir}}/usr/bin/" .      

melange relies on some alpine tools, on other systems docker can be used to run it with something like:

1$ docker run --privileged -v $(PWD):/work distroless.dev/melange:latest \
2  build \
3  --arch x86_64 \
4  --signing-key build/melange.rsa \
5  --out-dir /work/build/packages \
6  melange.yaml

apko

apko composes packages into container images. It appears that any complex config will need to be done by building a package and including that.

 1contents:
 2  # repositories to pull packages from
 3  # NOTE: these need to be signed
 4  repositories:
 5    - https://dl-cdn.alpinelinux.org/alpine/edge/main
 6    - https://dl-cdn.alpinelinux.org/alpine/edge/community
 7    # syntax for local repo
 8    - "@local /work/build/packages"
 9  packages:
10    - alpine-baselayout
11    - weechat
12    # pull from local repo
13    - example-package@local
14
15# dockerfile ENTRYPOINT
16entrypoint:
17  command: "fly-weechat"
18
19# run as non root
20accounts:
21  run-as: 10000
22  users:
23    - username: user
24      uid: 10000
25  groups:
26    - groupname: user
27      gid: 10000
28
29# setup a home for our user
30environment:
31  HOME: /home/user
32
33paths:
34  - path: /home/user
35    type: directory
36    uid: 10000
37    gid: 10000
38    permissions: 0o755

This also has a dependency on alpine tools, so here comes docker again. Alternatively, use publish to push directly to a repository.

1$ docker run -v $(PWD):/work distroless.dev/apko:latest \
2  build \
3  --keyring-append build/melange.rsa.pub \
4  apko.yaml \
5  ${IMAGE_NAME}:${IMAGE_REF} \
6  build/image.tar

makefile

While they are declarative, unlike ko which inspired it, it's not quite there in terms of no runtime config (flags) needed. So we're back to writing a Makefile to remember all the commands.

 1IMAGE_NAME := "my.registry.example/img"
 2IMAGE_REF := "latest"
 3IMAGE_OUT := "image.tar"
 4
 5SRC := $(shell find . -name '*.go')
 6
 7$(SRC) go.mod go.sum melange.yaml apko.yaml:
 8
 9gen-keys: build/melange.rsa
10build/melange.rsa:
11	mkdir -p build/
12	docker run --privileged -v $(PWD):/work distroless.dev/melange:latest \
13		keygen build/melange.rsa
14
15build-apk: build/packages/x86_64/APKINDEX.tar.gz
16build/packages/x86_64/APKINDEX.tar.gz: gen-keys $(SRC) go.mod go.sum melange.yaml
17	docker run --privileged -v $(PWD):/work distroless.dev/melange:latest \
18		build \
19		--arch x86_64 \
20		--signing-key build/melange.rsa \
21		--out-dir /work/build/packages \
22		melange.yaml
23
24build-img: build/image.tar
25build/image.tar: build-apk apko.yaml
26	docker run -v $(PWD):/work distroless.dev/apko:latest \
27		build \
28		--keyring-append build/melange.rsa.pub \
29		apko.yaml \
30		${IMAGE_NAME}:${IMAGE_REF} \
31		build/image.tar
32
33load-img: build-img
34	docker load -i build/image.tar
35
36.PHONY: run-img
37run-img: load-img
38	docker run --rm -it ${IMAGE_NAME}:${IMAGE_REF}