├── .apko.yaml ├── .github └── workflows │ └── push.yaml ├── .melange.yaml └── README.md /.apko.yaml: -------------------------------------------------------------------------------- 1 | contents: 2 | repositories: 3 | - https://dl-cdn.alpinelinux.org/alpine/edge/main 4 | - '@local /github/workspace/packages' 5 | packages: 6 | - alpine-baselayout-data 7 | - nginx@local 8 | 9 | entrypoint: 10 | type: service-bundle 11 | services: 12 | nginx: /usr/sbin/nginx -c /etc/nginx/nginx.conf -g "daemon off;" 13 | 14 | accounts: 15 | groups: 16 | - groupname: nginx 17 | gid: 10000 18 | users: 19 | - username: nginx 20 | uid: 10000 21 | -------------------------------------------------------------------------------- /.github/workflows/push.yaml: -------------------------------------------------------------------------------- 1 | name: Build action 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '15 3 * * *' # every day at 03:15 (just wanted to avoid midnight) 7 | 8 | jobs: 9 | build: 10 | name: Build nginx image 11 | runs-on: ubuntu-latest 12 | 13 | # https://docs.github.com/en/actions/reference/authentication-in-a-workflow 14 | permissions: 15 | id-token: write 16 | packages: write 17 | contents: read 18 | 19 | steps: 20 | - name: Generate snapshot date 21 | id: snapshot-date 22 | run: | 23 | echo ::set-output name=date::$(date -u +%Y%m%d) 24 | echo ::set-output name=epoch::$(date -u +%s) 25 | shell: bash 26 | 27 | - uses: actions/checkout@main 28 | - uses: sigstore/cosign-installer@main 29 | - uses: chainguard-dev/actions/melange-build@main 30 | with: 31 | sign-with-temporary-key: true 32 | empty-workspace: true 33 | 34 | - uses: chainguard-dev/actions/apko-build@main 35 | id: apko 36 | with: 37 | archs: x86_64 38 | keyring-append: /github/workspace/melange.rsa.pub 39 | tag: cgr.dev/${{ github.repository }}:latest 40 | source-date-epoch: ${{ steps.snapshot-date.outputs.epoch }} 41 | 42 | - uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b # v1.13.0 43 | with: 44 | registry: cgr.dev 45 | username: ${{ github.repository_owner }} 46 | password: ${{ github.token }} 47 | 48 | - shell: bash 49 | run: | 50 | # TODO: Add attributes based on things like the commit. 51 | COSIGN_EXPERIMENTAL=true cosign sign ${{ steps.apko.outputs.digest }} 52 | -------------------------------------------------------------------------------- /.melange.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: nginx 3 | version: 1.20.2 4 | epoch: 0 5 | description: "the nginx webserver" 6 | target-architecture: 7 | - all 8 | copyright: 9 | - paths: 10 | - "*" 11 | attestation: TODO 12 | license: BSD-2-Clause 13 | dependencies: 14 | runtime: 15 | 16 | environment: 17 | contents: 18 | repositories: 19 | - https://dl-cdn.alpinelinux.org/alpine/edge/main 20 | packages: 21 | - alpine-baselayout-data 22 | - busybox 23 | - build-base 24 | - scanelf 25 | - ssl_client 26 | - ca-certificates-bundle 27 | - brotli-dev 28 | - gd-dev 29 | - geoip-dev 30 | - libmaxminddb-dev 31 | - libxml2-dev 32 | - libxslt-dev 33 | - linux-headers 34 | - openssl3-dev 35 | - pcre-dev 36 | - pkgconf 37 | - zeromq-dev 38 | - zlib-dev 39 | 40 | accounts: 41 | groups: 42 | - groupname: nginx 43 | gid: 10000 44 | users: 45 | - username: nginx 46 | uid: 10000 47 | 48 | pipeline: 49 | - uses: fetch 50 | with: 51 | uri: https://nginx.org/download/${{package.name}}-${{package.version}}.tar.gz 52 | expected-sha256: 958876757782190a1653e14dc26dfc7ba263de310e04c113e11e97d1bef45a42 53 | - name: 'Configure nginx' 54 | runs: | 55 | ./configure \ 56 | --prefix=/var/lib/${{package.name}} \ 57 | --modules-path=/usr/lib/${{package.name}}/modules \ 58 | --sbin-path=/usr/sbin/${{package.name}} \ 59 | --conf-path=/etc/${{package.name}}/${{package.name}}.conf \ 60 | --pid-path=/run/${{package.name}}/${{package.name}}.pid \ 61 | --lock-path=/run/${{package.name}}/${{package.name}}.lock \ 62 | --http-client-body-temp-path=/var/lib/${{package.name}}/tmp/client_body \ 63 | --http-proxy-temp-path=/var/lib/${{package.name}}/tmp/proxy \ 64 | --http-fastcgi-temp-path=/var/lib/${{package.name}}/tmp/fastcgi \ 65 | --http-uwsgi-temp-path=/var/lib/${{package.name}}/tmp/uwsgi \ 66 | --http-scgi-temp-path=/var/lib/${{package.name}}/tmp/scgi \ 67 | --user=${{package.name}} \ 68 | --group=${{package.name}} \ 69 | --with-threads \ 70 | --with-file-aio \ 71 | --with-http_ssl_module \ 72 | --with-http_v2_module \ 73 | --with-http_realip_module \ 74 | --with-http_addition_module \ 75 | --with-http_xslt_module=dynamic \ 76 | --with-http_image_filter_module=dynamic \ 77 | --with-http_geoip_module=dynamic \ 78 | --with-http_sub_module \ 79 | --with-http_dav_module \ 80 | --with-http_flv_module \ 81 | --with-http_mp4_module \ 82 | --with-http_gunzip_module \ 83 | --with-http_gzip_static_module \ 84 | --with-http_auth_request_module \ 85 | --with-http_random_index_module \ 86 | --with-http_secure_link_module \ 87 | --with-http_degradation_module \ 88 | --with-http_slice_module \ 89 | --with-http_stub_status_module \ 90 | --with-stream=dynamic \ 91 | --with-stream_ssl_module \ 92 | --with-stream_realip_module \ 93 | --with-stream_geoip_module=dynamic \ 94 | --with-stream_ssl_preread_module 95 | - uses: autoconf/make 96 | - uses: autoconf/make-install 97 | - name: 'Create tmp dir' 98 | runs: mkdir ${{targets.destdir}}/var/lib/${{package.name}}/tmp 99 | - uses: strip 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nginx-image-demo 2 | 3 | This is a demonstration of how [cosign][cs], [apko][apko] and [Melange][mel] 4 | can be combined to build an image with a custom application payload. It uses 5 | a set of GitHub Actions published in the Chainguard [actions][actions] repository. 6 | 7 | [cs]: https://github.com/sigstore/cosign 8 | [apko]: https://github.com/chainguard-dev/apko 9 | [mel]: https://github.com/chainguard-dev/melange 10 | [actions]: https://github.com/chainguard-dev/actions 11 | 12 | ## The lifecycle of an application build 13 | 14 | ### Packaging 15 | 16 | First, the relevant packages to support an application are built with Melange! 17 | In this case, we are using nginx as an example (a stripped down version of the 18 | nginx ingress packaging, which builds a few dozen dependencies). 19 | 20 | You can see an example of this in the `.melange.yaml` file. 21 | 22 | ### Image composition and publish 23 | 24 | Next, the image is composed and published with `apko`. Melange stores its packages 25 | in a local repository, which is consumed by `apko`. These packages are combined 26 | with dependencies from the upstream Alpine Linux distribution, composed into an 27 | OCI image, and published. An SBOM is generated and published along side the 28 | image if `apko` 0.3 or newer is used. 29 | 30 | Apko configures an s6 service bundle and arranges for it to be launched when a 31 | container is started. This is similar to using `s6-overlay` with Docker, and is 32 | considered a best practice so that zombie processes get reaped. 33 | 34 | ### Image signing 35 | 36 | Finally, the image is signed using Cosign. You can see the `.github/workflows/push.yaml` 37 | file for the details on how this works. 38 | 39 | All of this is done in a declarative (and reproducible) way. Since it is declarative 40 | and reproducible, refreshing the image can be automated. See the [distroless][dl] 41 | GitHub project for some examples of how this can be done. 42 | 43 | [dl]: https://github.com/distroless 44 | --------------------------------------------------------------------------------