docbrown/scripts/build_containers.sh
Josh Klar e4d33b316c
devxp: Base the application container images off new Ruby base image. (#19633)
This "rebases" our application images off of the new Ruby base image
added in #19632, and fixes numerous problems and quirks with how the
images were built along the way. Notably:

- Issues where layers attempted to delete files in prior layers have
  been resolved (this caused build failures on some Docker filesystem
  drivers, notably overlay2).

- Bundler is no longer allowed to deviate from or modify the lockfile
  (`BUNDLE_FROZEN` is now `true`).

- `git(1)` is no longer required to live inside the container and `.git`
  is no longer required to be copied into the Docker build context, as
  these were only used to calculate `FOREM_BUILD_SHA`, which is now
  passed in as a Build Argument to the container build context.

- The entire source tree is no longer `chmod` in one giant swing, which
  ran so long on my system (as just one example) that I gave up after
  15-20 minutes and issued it a `SIGTERM`. Instead, `COPY --chown` is
  used more heavily and ensures the `APP_USER` will have access to the
  requisite files.

This new container image appears to build successfully for
`linux/arm64`, which refs (but does not complete) #19626. Currently,
such builds aren't automated , and must be built on a developer
workstation. For example:

```sh
docker buildx build --platform linux/amd64,linux/arm64 -f Containerfile . -t ghcr.io/forem/forem:klardotsh-test --push --build-arg VCS_REF=$(git rev-parse --short HEAD)
```

In the meantime, the existing `linux/amd64`-only BuildKite scripts have
been updated to allow this PR to merge as a separate unit, and CI
refactors to enable the multiarch builds of `linux/arm64,linux/amd64`
can come later when more time is available.

This is one of several blockers on the path to getting #19603 merged.
The next step in that chronology will be rebasing that work on top of
this work, which *should* be, on the containerization side, as
straightforward as bumping `Containerfile.base` to reference the new
upstream image, rebuilding the base container, and then bumping the
reference in `Containerfile`.
2023-06-29 10:50:26 -07:00

228 lines
9.3 KiB
Bash
Executable file

#!/bin/bash
set -euo pipefail
: ${CONTAINER_REPO:="quay.io/forem"}
: ${CONTAINER_APP:=forem}
export DOCKER_BUILDKIT=1
function create_pr_containers {
PULL_REQUEST=$1
# Pull images if available for caching
echo "Pulling pull request #${PULL_REQUEST} containers from registry..."
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}" ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":pr-"${PULL_REQUEST}" ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-"${PULL_REQUEST}" ||:
# Build the builder image
echo "Building builder-${PULL_REQUEST} container..."
docker build --target builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}" \
--label quay.expires-after=8w \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}" .
# Build the pull request image
echo "Building pr-${PULL_REQUEST} container..."
docker build --target production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}" \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":pr-"${PULL_REQUEST}" \
--label quay.expires-after=8w \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":pr-"${PULL_REQUEST}" .
# Build the testing image
echo "Building testing-$"${PULL_REQUEST}" container..."
docker build --target testing \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}" \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":pr-"${PULL_REQUEST}" \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-"${PULL_REQUEST}" \
--label quay.expires-after=8w \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-"${PULL_REQUEST}" .
# Push images to Quay
echo "Pushing pull request #${PULL_REQUEST} containers to registry..."
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder-"${PULL_REQUEST}"
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":pr-"${PULL_REQUEST}"
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-"${PULL_REQUEST}"
}
function create_production_containers {
# Pull images if available for caching
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":production ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":development ||:
# Build the builder image
docker build --target builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder .
# Build the production image
docker build --target production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":$(date +%Y%m%d) \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":latest .
docker build --target production \
--label quay.expires-after=8w \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_COMMIT:0:7} .
# Build the testing image
docker build --target testing \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing .
# Build the development image
docker build --target development \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":development \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":development .
# Push images to Quay
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":production
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":development
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":$(date +%Y%m%d)
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_COMMIT:0:7}
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":latest
}
function create_release_containers {
BRANCH=$1
# Pull images if available for caching
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":production ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing ||:
docker pull "${CONTAINER_REPO}"/"${CONTAINER_APP}":development ||:
# Build the builder image
docker build --target builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":builder .
# Build the production image
docker build --target production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_COMMIT:0:7} \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BRANCH} .
# Build the testing image
docker build --target testing \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-${BRANCH} .
# Build the development image
docker build --target development \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":testing \
--build-arg "VCS_REF=${BUILDKITE_COMMIT}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":development-${BRANCH} .
# If the env var for the git tag doesn't exist or is an empty string, then we
# won't build a container image for a cut release.
if [ -v BUILDKITE_TAG ] && [ ! -z "${BUILDKITE_TAG}" ]; then
echo "Buildkite Tag: ${BUILDKITE_TAG}"
docker build --target production \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":builder \
--cache-from="${CONTAINER_REPO}"/"${CONTAINER_APP}":production \
--build-arg "VCS_REF=${BUILDKITE_TAG}" \
--tag "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BUILDKITE_TAG} .
fi
# Push images to Quay
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":${BRANCH}
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":development-${BRANCH}
docker push "${CONTAINER_REPO}"/"${CONTAINER_APP}":testing-${BRANCH}
}
function prune_containers {
docker image prune -f
}
trap prune_containers ERR INT EXIT
if [ -v BUILDKITE ]
then
echo "Branch: $BUILDKITE_BRANCH"
echo "PR : $BUILDKITE_PULL_REQUEST"
echo "Commit: $BUILDKITE_COMMIT"
echo "Tag : $BUILDKITE_TAG"
fi
if [ ! -v BUILDKITE_BRANCH ]; then
echo "Not running in Buildkite. Building Production Containers..."
BUILDKITE_COMMIT=$(git rev-parse --short HEAD)
create_production_containers
elif [ -z "$BUILDKITE_BRANCH" ]; then
echo "BUILDKITE_BRANCH is set to an empty string! Exiting..."
exit 1
elif [[ "${BUILDKITE_BRANCH}" = "master" || "${BUILDKITE_BRANCH}" = "main" ]]; then
echo "Building Production Containers..."
create_production_containers
elif [[ ${BUILDKITE_BRANCH} = stable* ]]; then
echo "Building Production Containers for ${BUILDKITE_BRANCH}..."
create_release_containers "${BUILDKITE_BRANCH}"
else
if [ ! -v BUILDKITE_PULL_REQUEST ]; then
echo "BUILDKITE_PULL_REQUEST is unset! Exiting..."
exit 1
elif [ -z "$BUILDKITE_PULL_REQUEST" ]; then
echo "BUILDKITE_PULL_REQUEST is set to an empty string! Exiting..."
exit 1
else
echo "Building containers for pull request #${BUILDKITE_PULL_REQUEST}..."
create_pr_containers "${BUILDKITE_PULL_REQUEST}"
fi
fi