Search code examples
dockerdocker-registrydocker-multi-stage-builddocker-buildkitdocker-push

Build all containers before pushing to registry using Buildkit


I've the following Dockerfile:

FROM php:7.4 as base

RUN apt-get install -y libzip-dev && \
    docker-php-ext-install zip

# install some other things...

FROM base as intermediate

COPY upgrade.sh /usr/local/bin

FROM base as final

COPY start-app.sh /usr/local/bin

As you can see, I've 3 stages:

  • base
  • intermedia
  • final

At first, I'm building the base container and then both "derived" containers. My problem is that I need to push the intermediate and the final container to my (gitlab) registry. The containers are built using the following gitlab-ci.yml:

.build_container: &build_container
  stage: build
  image:
    name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/moby/buildkit:rootless
    entrypoint: ["sh", "-c"]
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  script:
    - mkdir ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)\"}}}" > ~/.docker/config.json
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=${CI_PROJECT_DIR} \
        --local dockerfile=${CI_PROJECT_DIR} \
        --opt filename=./${DOCKERFILE} \
        --import-cache type=registry,ref=${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG} \
        --export-cache type=inline \
        --output type=image,name=${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG},push=true

Build:Container:
  <<: *build_container
  variables:
    DOCKERFILE: "Dockerfile"

Ok, I can simply add another "buildctl-daemonless.sh"-command and use a separate file, but I want to make sure that both containers (intermediate and final) are build successfully before pushing them. So I'm looking for a solution that builds the intermediate and the final containers at first and then pushing both to the registry, e.g. something like this:

      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=${CI_PROJECT_DIR} \
        --local dockerfile=${CI_PROJECT_DIR} \
        --opt filename=./${DOCKERFILE} \
        --import-cache type=registry,ref=${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG} \
        --export-cache type=inline

      buildctl-daemonless.sh push intermediate final

Unfortunately there's no "push" command in buildctl-daemonless.sh as far as I know. So es anyone having an idea how I can to this?


Solution

  • Directly from buildkit, I don't think there's a separate push command. The output of a multi-platform image is usually directly to a registry, but could also be an OCI Layout tar file. To write to that tar file, you would add the following option:

    --output type=oci,dest=path/to/output.tar
    

    With that tar file, there are various standalone tools that can help. Pretty sure RedHat's skopeo and Google's go-containerregistry/crane both have options. My own tool is regclient which includes the following regctl command:

    regctl image import ${image_name_tag} path/to/output.tar
    

    There's a regclient/regctl:alpine image that's made to be embedded in CI pipelines like GitLab, and it will read registry auths from the same ~/.docker/config.json.