From 127b6d061a79bd789e6cadbf9c25d0f3f9f86eff Mon Sep 17 00:00:00 2001 From: Keepers Date: Thu, 1 Sep 2022 09:06:57 -0600 Subject: [PATCH] introduce docker container production (#660) Introduces the production of docker containers as a CI step. Currently only provides a rolling-release version that builds on every push to main. Images are deployed to ghcr.io. The PR includes two variations on building the images. We'll likely only want to stick with one or the other. --- .dockerignore | 3 + .github/workflows/container.yml | 114 ++++++++++++++++++++++++++++++++ README.md | 11 ++- build/Dockerfile | 15 +++++ build/build-container.sh | 46 ++++++++++--- build/build.sh | 54 +++++++++------ docker/Dockerfile | 26 ++++++-- docker/docker-bake.hcl | 12 ++++ 8 files changed, 245 insertions(+), 36 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/container.yml create mode 100644 build/Dockerfile create mode 100644 docker/docker-bake.hcl diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3cf565794 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.gitignore +.dockerignore \ No newline at end of file diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 000000000..0f32886c2 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,114 @@ +name: Publish Docker Container Images +on: + push: + branches: [main] + +env: + REGISTRY: ghcr.io + REPO_NAME: ${{ github.repository }} + +permissions: + contents: read + packages: write + +jobs: + + # ------------------------------------------------------------------------------------------ + # To be decided: Script-Deploy or Dockerfile-Deploy: + # Script: + # + Separates the golang build from the corso build. + # - Haven't figured out multiplatform builds yet. + # - Doesn't cache, always takes 10-15 minutes per build in the matrix. + # Dockerfile: + # + Once cached, takes <1m to deploy. + # + Multiplatform. + # + Extended features (such as tagging) can be handled by more github actions. + # - When not cached, can take >2 hours to build (at least initially). + # - Currently includes the complete golang:1.18 image. + # ------------------------------------------------------------------------------------------ + + Script-Deploy: + runs-on: ubuntu-latest + defaults: + run: + working-directory: build + strategy: + matrix: + BUILD_ARCH: [amd64, arm64] + BUILD_OS: [linux] + env: + IMAGE_PREFIX: ghcr.io + VERSION_SUFFIX: rolling + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Run build script + run: > + ./build-container.sh + --arch ${{ matrix.BUILD_ARCH }} + --prefix ${{ env.IMAGE_PREFIX }} + --suffix ${{ env.VERSION_SUFFIX }} + + # login step boilerplate from: + # https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio + - name: Log in to registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + + - name: Push image + env: + IMAGE_ID: ${{ env.IMAGE_PREFIX }}/alcionai/corso + VERSION: ${{ matrix.BUILD_OS }}-${{ matrix.BUILD_ARCH }}-${{ env.VERSION_SUFFIX }} + run: | + docker images -a + docker push ${{ env.IMAGE_ID }}:${{ env.VERSION }} + + Dockerfile-Deploy: + runs-on: ubuntu-latest + env: + TARGETOS: linux + TARGETARCH: arm64 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # apparently everyone uses this step + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + # setup Docker buld action + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + # In case we want to switch to dockerhub + # - name: Login to DockerHub + # uses: docker/login-action@v2 + # with: + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + + # retrieve credentials for ghcr.io + - name: Login to Github Packages + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # build the image + - name: Build image and push to Docker Hub and GitHub Container Registry + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/alcionai/corso:rolling + # use the github cache + cache-from: type=gha + cache-to: type=gha,mode=max + + # check the image digest + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/README.md b/README.md index ba3500072..e0d8f805a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,16 @@ TODO - Link to the appropriate page in the published docs. ./build/build-container.sh ``` -## Contribution Guidelines +# Containers + +Corso images are hosted on [ghrc.io](https://github.com/alcionai/corso/pkgs/container/corso). + +Rolling release +```sh +docker pull ghcr.io/alcionai/corso:{SHA} --platform linux/arm64 +``` + +# Contribution Guidelines TODO diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 000000000..4bc2a9060 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 + +# This dockerfile is configured to be run by the /corso/build/build-container.sh +# script. Using docker to build this file directly will fail. + +FROM gcr.io/distroless/base-debian10 +# FROM gcr.io/distroless/base:debug + +WORKDIR / + +COPY ./bin/corso ./ + +USER nonroot:nonroot + +ENTRYPOINT ["/corso"] diff --git a/build/build-container.sh b/build/build-container.sh index 4a6f9af3c..9bc8dab6f 100755 --- a/build/build-container.sh +++ b/build/build-container.sh @@ -10,12 +10,15 @@ usage() { echo "-----" echo "Flags" echo " -h|--help Help" - echo " |--arm Set the architecture to arm64 (default: amd64)" + echo " -a|--arch Set the architecture to the specified value (default: amd64)" + echo " -p|--prefix Prefixes the image name." + echo " -s|--suffix Suffixes the version." echo " " echo "-----" echo "Example Usage:" echo " ./build/build-container.sh" - echo " ./build/build-container.sh --arm" + echo " ./build/build-container.sh --arch arm64" + echo " ./build/build-container.sh --arch arm64 --prefix ghcr.io --suffix nightly" echo " " exit 0 } @@ -25,6 +28,8 @@ PROJECT_ROOT=$(dirname ${SCRIPT_ROOT}) OS=linux ARCH=amd64 +IMAGE_NAME_PREFIX= +IMAGE_TAG_SUFFIX= while [ "$#" -gt 0 ] do @@ -33,31 +38,52 @@ do usage exit 0 ;; - --arm) - ARCH=arm64 + -a|--arch) + ARCH=$2 + shift + ;; + -p|--prefix) + IMAGE_NAME_PREFIX=$2 + shift + ;; + -s|--suffix) + IMAGE_TAG_SUFFIX=$2 + shift ;; -*) - echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2 + echo "Invalid flag '$1'. Use -h|--help to see the valid options" >&2 return 1 ;; *) - echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2 + echo "Invalid arg '$1'. Use -h|--help to see the valid options" >&2 return 1 ;; esac shift done -IMAGE_TAG=${OS}-${ARCH}-$(git describe --tags --always --dirty) +IMAGE_TAG=${OS}-${ARCH} +if [ ! -z "${IMAGE_TAG_SUFFIX}" ]; then + IMAGE_TAG=${IMAGE_TAG}-${IMAGE_TAG_SUFFIX} +fi + IMAGE_NAME=alcionai/corso:${IMAGE_TAG} +if [ ! -z "${IMAGE_NAME_PREFIX}" ]; then + IMAGE_NAME=${IMAGE_NAME_PREFIX}/${IMAGE_NAME} +fi ${SCRIPT_ROOT}/build.sh --arch ${ARCH} -echo "building container" +echo "-----" +echo "building corso container ${IMAGE_NAME}" +echo "-----" + set -x docker buildx build --tag ${IMAGE_NAME} \ --platform ${OS}/${ARCH} \ - --file ${PROJECT_ROOT}/docker/Dockerfile \ + --file ${PROJECT_ROOT}/build/Dockerfile \ ${PROJECT_ROOT} set +x -echo "container built successfully ${IMAGE_NAME}" + +echo "-----" +echo "container built successfully" diff --git a/build/build.sh b/build/build.sh index 91e2c78b3..4b82ad542 100755 --- a/build/build.sh +++ b/build/build.sh @@ -4,12 +4,19 @@ set -e SCRIPT_ROOT=$(dirname $(readlink -f $0)) PROJECT_ROOT=$(dirname ${SCRIPT_ROOT}) -SRC_DIR=${PROJECT_ROOT} + +CORSO_BUILD_CONTAINER=/go/src/github.com/alcionai/corso +CORSO_BUILD_CONTAINER_SRC=${CORSO_BUILD_CONTAINER}/src +CORSO_BUILD_PKG_MOD=/go/pkg/mod +CORSO_BUILD_TMP=/tmp/.corsobuild +CORSO_BUILD_TMP_CACHE=${CORSO_BUILD_TMP}/cache +CORSO_BUILD_TMP_MOD=${CORSO_BUILD_TMP}/mod +CORSO_CACHE=${CORSO_BUILD_TMP_CACHE} +CORSO_MOD_CACHE=${CORSO_BUILD_PKG_MOD}/cache + CORSO_BUILD_ARGS='' -CORSO_BUILD_CONTAINER_DIR=/go/src/github.com/alcionai/corso -CORSO_BUILD_CONTAINER_SRC_DIR=${CORSO_BUILD_CONTAINER_DIR}/src - +GOVER=1.18 GOOS=linux GOARCH=amd64 @@ -25,26 +32,31 @@ do done # temporary directory for caching go build -mkdir -p /tmp/.corsobuild/cache +mkdir -p ${CORSO_BUILD_TMP_CACHE} # temporary directory for caching go modules (needed for fast cross-platform build) -mkdir -p /tmp/.corsobuild/mod +mkdir -p ${CORSO_BUILD_TMP_MOD} + +echo "-----" +echo "building corso binary for ${GOOS}-${GOARCH}" +echo "-----" -echo "building corso" set -x -docker run --rm --mount type=bind,src=${SRC_DIR},dst=${CORSO_BUILD_CONTAINER_DIR} \ - --mount type=bind,src=/tmp/.corsobuild/cache,dst=/tmp/.corsobuild/cache \ - --mount type=bind,src=/tmp/.corsobuild/mod,dst=/go/pkg/mod \ - --workdir ${CORSO_BUILD_CONTAINER_SRC_DIR} \ - --env GOCACHE=/tmp/.corsobuild/cache \ - --env GOOS=${GOOS} \ - --env GOARCH=${GOARCH} \ - --env GOCACHE=/tmp/.corsobuild/cache \ - --entrypoint /usr/local/go/bin/go \ - golang:1.18 \ - build ${CORSO_BUILD_ARGS} - -mkdir -p ${PROJECT_ROOT}/bin +docker run --rm \ + --mount type=bind,src=${PROJECT_ROOT},dst=${CORSO_BUILD_CONTAINER} \ + --mount type=bind,src=${CORSO_BUILD_TMP_CACHE},dst=${CORSO_BUILD_TMP_CACHE} \ + --mount type=bind,src=${CORSO_BUILD_TMP_MOD},dst=${CORSO_BUILD_PKG_MOD} \ + --workdir ${CORSO_BUILD_CONTAINER_SRC} \ + --env GOMODCACHE=${CORSO_MOD_CACHE} \ + --env GOCACHE=${CORSO_CACHE} \ + --env GOOS=${GOOS} \ + --env GOARCH=${GOARCH} \ + --entrypoint /usr/local/go/bin/go \ + golang:${GOVER} \ + build ${CORSO_BUILD_ARGS} set +x -echo "creating binary image in bin/corso" +mkdir -p ${PROJECT_ROOT}/bin mv ${PROJECT_ROOT}/src/corso ${PROJECT_ROOT}/bin/corso + +echo "-----" +echo "created binary image in ${PROJECT_ROOT}/bin/corso" diff --git a/docker/Dockerfile b/docker/Dockerfile index 09b9c56d2..d0ebfeb7e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,11 +1,29 @@ # syntax=docker/dockerfile:1 +# This dockerfile is able to make a quick, local image of corso. +# It is not used for deployments. + +## Build +FROM golang:1.18 AS base + +WORKDIR /src + +COPY ./src/go.mod . +COPY ./src/go.sum . +RUN go mod download + +COPY ./src . + +FROM base AS build +ARG TARGETOS +ARG TARGETARCH +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /corso . + +## Deploy FROM gcr.io/distroless/base-debian10 -WORKDIR / +COPY --from=build /corso / -COPY ./bin/corso ./ - USER nonroot:nonroot -ENTRYPOINT ["/corso"] \ No newline at end of file +ENTRYPOINT ["/corso"] diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl new file mode 100644 index 000000000..9292418b3 --- /dev/null +++ b/docker/docker-bake.hcl @@ -0,0 +1,12 @@ +// docker-bake.hcl +target "docker-metadata-action" {} + +target "build" { + inherits = ["docker-metadata-action"] + context = "./" + dockerfile = "Dockerfile" + platforms = [ + "linux/amd64", + "linux/arm64", + ] +} \ No newline at end of file