<p>We want to use <a href="https://paketo.io" rel="nofollow noreferrer">Paketo.io</a> / <a href="https://buildpacks.io" rel="nofollow noreferrer">CloudNativeBuildpacks (CNB)</a> <a href="https://docs.gitlab.com/ee/ci/" rel="nofollow noreferrer">GitLab CI</a> in the most simple way. Our GitLab setup uses an AWS EKS cluster with unprivileged GitLab CI Runners leveraging <a href="https://docs.gitlab.com/runner/executors/kubernetes.html" rel="nofollow noreferrer">the Kubernetes executor</a>. We also <strong>don't want to introduce security risks</strong> by <a href="https://docs.gitlab.com/runner/executors/kubernetes.html#using-docker-in-your-builds" rel="nofollow noreferrer">using Docker in our builds</a>. So we don't have our host’s <code>/var/run/docker.sock</code> exposed nor want to use <code>docker:dind</code>.</p>
<p>We found some guides on how to use Paketo with GitLab CI like this <a href="https://tanzu.vmware.com/developer/guides/gitlab-ci-cd-cnb/" rel="nofollow noreferrer">https://tanzu.vmware.com/developer/guides/gitlab-ci-cd-cnb/</a> . But as described beneath the headline <code>Use Cloud Native Buildpacks with GitLab in GitLab Build Job WITHOUT Using the GitLab Build Template</code>, the approach relies on Docker and pack CLI. We tried to resemble this in our <code>.gitlab-ci.yml</code> which looks like this:</p>
<pre><code>image: docker:20.10.9

stages:
  - build

before_script:
  - |
    echo &quot;install pack CLI (see https://buildpacks.io/docs/tools/pack/)&quot;
    apk add --no-cache curl
    (curl -sSL &quot;https://github.com/buildpacks/pack/releases/download/v0.21.1/pack-v0.21.1-linux.tgz&quot; | tar -C /usr/local/bin/ --no-same-owner -xzv pack)

build-image:
  stage: build
  script:
    - pack --version
    - &gt;
      pack build $REGISTRY_GROUP_PROJECT/$CI_PROJECT_NAME:latest
      --builder paketobuildpacks/builder:base
      --path . 
</code></pre>
<p>But as outlined our setup does not support docker and we end up with the following error inside our logs:</p>
<pre><code>...
$ echo &quot;install pack CLI (see https://buildpacks.io/docs/tools/pack/)&quot; # collapsed multi-line command
install pack CLI (see https://buildpacks.io/docs/tools/pack/)
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz
(1/4) Installing brotli-libs (1.0.9-r5)
(2/4) Installing nghttp2-libs (1.43.0-r0)
(3/4) Installing libcurl (7.79.1-r0)
(4/4) Installing curl (7.79.1-r0)
Executing busybox-1.33.1-r3.trigger
OK: 12 MiB in 26 packages
pack
$ pack --version
0.21.1+git-e09e397.build-2823
$ pack build $REGISTRY_GROUP_PROJECT/$CI_PROJECT_NAME:latest --builder paketobuildpacks/builder:base --path .
ERROR: failed to build: failed to fetch builder image 'index.docker.io/paketobuildpacks/builder:base': Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Cleaning up project directory and file based variables 00:01
ERROR: Job failed: command terminated with exit code 1
</code></pre>
<p>Any idea on how to use Paketo Buildpacks with GitLab CI without having Docker present inside our GitLab Kubernetes runners (which seems to be kind of a best practice)? We also don't want our setup to become to complex - e.g. by adding <a href="https://buildpacks.io/docs/tools/kpack/" rel="nofollow noreferrer">kpack</a>.</p>

<h2>TLDR;</h2>
<p>Use the Buildpack's lifecycle directly inside your <code>.gitlab-ci.yml</code> <a href="https://gitlab.com/jonashackt/microservice-api-spring-boot/-/blob/main/.gitlab-ci.yml" rel="noreferrer">here's a fully working example</a>):</p>
<pre><code>image: paketobuildpacks/builder

stages:
  - build

# We somehow need to access GitLab Container Registry with the Paketo lifecycle
# So we simply create ~/.docker/config.json as stated in https://stackoverflow.com/a/41710291/4964553
before_script:
  - mkdir ~/.docker
  - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_JOB_TOKEN\&quot;}}}&quot; &gt;&gt; ~/.docker/config.json

build-image:
  stage: build
  script:
    - /cnb/lifecycle/creator -app=. $CI_REGISTRY_IMAGE:latest
</code></pre>
<hr />
<h2>The details: &quot;using the lifecycle directly&quot;</h2>
<p>There are ongoing discussions about this topic. Especially have a look into <a href="https://github.com/buildpacks/pack/issues/564" rel="noreferrer">https://github.com/buildpacks/pack/issues/564</a> and <a href="https://github.com/buildpacks/pack/issues/413#issuecomment-565165832" rel="noreferrer">https://github.com/buildpacks/pack/issues/413#issuecomment-565165832</a>. As stated there:</p>
<blockquote>
<p>If you're looking to build images in CI (not locally), I'd encourage
you to use the lifecycle directly for that, so that you don't need
Docker. Here's an example:</p>
</blockquote>
<p>The link to the example is broken, but it refers to the <a href="https://tekton.dev/" rel="noreferrer">Tekton</a> implementation on <a href="https://github.com/tektoncd/catalog/blob/main/task/buildpacks/0.3/buildpacks.yaml" rel="noreferrer">how to use buildpacks in a Kubernetes environment</a>. Here we can get a first glue about what Stephen Levine referred to as <code>&quot;to use the lifecycle directly&quot;</code>. Inside it the crucial point is the usage of <code>command: [&quot;/cnb/lifecycle/creator&quot;]</code>. <strong>So this is the lifecycle everyone is talking about!</strong> And there's good documentaion about this command that could be found <a href="https://github.com/buildpacks/rfcs/blob/main/text/0026-lifecycle-all.md#usage" rel="noreferrer">in this CNB RFC</a>.</p>
<h2>Choosing a good image: paketobuildpacks/builder:base</h2>
<p>So how to develop a working <code>.gitlab-ci.yml</code>? Let's start simple. Digging <a href="https://github.com/tektoncd/catalog/blob/main/task/buildpacks/0.3/buildpacks.yaml" rel="noreferrer">into the Tekton implementation</a> you'll see that the lifecycle command is executed inside an environment defined in <code>BUILDER_IMAGE</code>, which itself is documented as <code>The image on which builds will run (must include lifecycle and compatible buildpacks).</code> That sound's familiar! Can't we simply pick the builder image <code>paketobuildpacks/builder:base</code> from our pack CLI command? Let's try this locally on our workstation before commiting to much noise into our GitLab. Choose a project you want to build (I created a example Spring Boot app if you'd like at <a href="https://gitlab.com/jonashackt/microservice-api-spring-boot" rel="noreferrer">gitlab.com/jonashackt/microservice-api-spring-boot</a> you can clone) and run:</p>
<pre><code>docker run --rm -it -v &quot;$PWD&quot;:/usr/src/app -w /usr/src/app paketobuildpacks/builder bash
</code></pre>
<p>Now inside the <code>paketobuildpacks/builder</code> image powered container try to run the Paketo lifecycle directly with:</p>
<pre><code>/cnb/lifecycle/creator -app=. microservice-api-spring-boot:latest
</code></pre>
<p>I only used the <code>-app</code> parameter of <a href="https://github.com/buildpacks/rfcs/blob/main/text/0026-lifecycle-all.md#usage" rel="noreferrer">the many possible parameters for the <code>creator</code> command</a>, since most of them have quite good defaults. But as the default app directory path is not the default <code>/workspace</code> - but the current directory, I configured it. Also we need to define an <code>&lt;image-name&gt;</code> at the end, which will simply be used as the resulting container image name.</p>
<h2>The first .gitlab-ci.yml</h2>
<p>Both commands did work at my local workstation, so let's finally create a <code>.gitlab-ci.yml</code> using this approach (<a href="https://gitlab.com/jonashackt/microservice-api-spring-boot/-/blob/add-gitlab-ci/.gitlab-ci.yml" rel="noreferrer">here's a fully working example <code>.gitlab-ci.yml</code></a>):</p>
<pre><code>image: paketobuildpacks/builder

stages:
  - build

build-image:
  stage: build
  script:
    - /cnb/lifecycle/creator -app=. $CI_REGISTRY_IMAGE:latest
</code></pre>
<h2>docker login without docker</h2>
<p>As we don't have <code>docker</code> available inside our Kubernetes Runners, we can't <a href="https://docs.gitlab.com/ee/user/packages/container_registry/#authenticate-by-using-gitlab-cicd" rel="noreferrer">login into GitLab Container Registry as described in the docs</a>. So the following error occured to me using this first approach:</p>
<pre><code>===&gt; ANALYZING
ERROR: failed to get previous image: connect to repo store &quot;gitlab.yourcompanyhere.cloud:4567/yourgroup/microservice-api-spring-boot:latest&quot;: GET https://gitlab.yourcompanyhere.cloud/jwt/auth?scope=repository%3Ayourgroup%2Fmicroservice-api-spring-boot%3Apull&amp;service=container_registry: DENIED: access forbidden
Cleaning up project directory and file based variables 00:01
ERROR: Job failed: command terminated with exit code 1
</code></pre>
<p>Using the approach <a href="https://stackoverflow.com/a/46422186/4964553">described in this so answer</a> fixed the problem. We need to create a <code>~/.docker/config.json</code> containing the GitLab Container Registry login information - and then <a href="https://github.com/buildpacks/spec/blob/main/platform.md#registry-authentication" rel="noreferrer">the Paketo build will pick them up, as stated in the docs</a>:</p>
<blockquote>
<p>If <code>CNB_REGISTRY_AUTH</code> is unset and a docker config.json file is
present, the lifecycle SHOULD use the contents of this file to
authenticate with any matching registry.</p>
</blockquote>
<p>Inside our <code>.gitlab-ci.yml</code> this could look like:</p>
<pre><code># We somehow need to access GitLab Container Registry with the Paketo lifecycle
# So we simply create ~/.docker/config.json as stated in https://stackoverflow.com/a/41710291/4964553
before_script:
  - mkdir ~/.docker
  - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_JOB_TOKEN\&quot;}}}&quot; &gt;&gt; ~/.docker/config.json
</code></pre>
<h2>Our final .gitlab-ci.yml</h2>
<p>As we're using the <code>image: paketobuildpacks/builder</code> at the top of our <code>.gitlab-ci.yml</code>, we can now leverage the lifecycle directly. Which is what we wanted to do in the first place. Only remember to use the correct GitLab CI variables to describe your <code>&lt;image-name&gt;</code> like this:</p>
<pre><code>/cnb/lifecycle/creator -app=. $CI_REGISTRY_IMAGE:latest
</code></pre>
<p>Otherwise the Buildpack process analyser step will break and it finally won't get pushed to the GitLab Container Registry. So finally our <code>.gitlab-ci.yml</code> looks like this (<a href="https://gitlab.com/jonashackt/microservice-api-spring-boot/-/blob/main/.gitlab-ci.yml" rel="noreferrer">here's the fully working example</a>):</p>
<pre><code>image: paketobuildpacks/builder

stages:
  - build

# We somehow need to access GitLab Container Registry with the Paketo lifecycle
# So we simply create ~/.docker/config.json as stated in https://stackoverflow.com/a/41710291/4964553
before_script:
  - mkdir ~/.docker
  - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_JOB_TOKEN\&quot;}}}&quot; &gt;&gt; ~/.docker/config.json

build-image:
  stage: build
  script:
    - /cnb/lifecycle/creator -app=. $CI_REGISTRY_IMAGE:latest
</code></pre>
<p>Our builds should now run successfully using Paketo/Buildpacks without pack CLI and Docker:</p>
<p><a href="https://i.stack.imgur.com/cuG16.png" rel="noreferrer"><img src="https://i.stack.imgur.com/cuG16.png" alt="enter image description here" /></a></p>
<p>See <a href="https://gitlab.com/jonashackt/microservice-api-spring-boot/-/jobs/1679982122" rel="noreferrer">the full log of the example project here</a>.</p>
