Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into multiarch
Browse files Browse the repository at this point in the history
# Conflicts:
#	bin/build.sh
#	bin/publish-to-registries.sh
  • Loading branch information
boboldehampsink committed Mar 4, 2024
2 parents aa0613f + ef0452b commit 3121926
Show file tree
Hide file tree
Showing 26 changed files with 2,586 additions and 115 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,12 @@ jobs:
STACK_VERSION: "${{ matrix.stack-version }}"
DOCKER_HUB_TOKEN: "${{ secrets.DOCKER_HUB_TOKEN }}"
DOCKER_HUB_USERNAME: "${{ secrets.DOCKER_HUB_USERNAME }}"
ID_SERVICE_PASSWORD: "${{ secrets.ID_SERVICE_PASSWORD }}"
ID_SERVICE_TOKEN_ENDPOINT: "${{ secrets.ID_SERVICE_TOKEN_ENDPOINT }}"
ID_SERVICE_USERNAME: "${{ secrets.ID_SERVICE_USERNAME }}"
INTERNAL_REGISTRY_HOST: "${{ secrets.INTERNAL_REGISTRY_HOST }}"
INTERNAL_REGISTRY_USERNAME: "${{ secrets.INTERNAL_REGISTRY_USERNAME }}"
MANIFEST_APP_TOKEN: "${{ secrets.MANIFEST_APP_TOKEN }}"
MANIFEST_APP_URL: "${{ secrets.MANIFEST_APP_URL }}"
strategy:
fail-fast: false
matrix:
stack-version: ["20", "22"]
stack-version: ["20", "22", "24"]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -55,8 +50,13 @@ jobs:
exit 1
fi
- name: Publish to image registries
run: bin/publish-to-registries.sh
run: |-
docker buildx create --use
bin/publish-to-registries.sh
if: success() && (github.ref_name == 'main' || github.ref_type == 'tag')
- name: Unpublish temp tags from this run
run: bin/unpublish-tags.sh
if: always()
- name: Convert docker image and release to Heroku staging
run: bin/convert-and-publish-to-heroku.sh
if: success() && github.ref_type == 'tag'
32 changes: 15 additions & 17 deletions BUILD.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Building Stack Images Locally
# Building Heroku Base Images Locally

## Prepare your local environment

The build scripts in this repository require bash 4+. To update to newer bash on OS X, see:
https://johndjameson.com/blog/updating-your-shell-with-homebrew/

## Adding packages to the stack image
## Adding packages to the base image

Add the package you want to the appropriate `setup.sh` for example `heroku-22/setup.sh`:

Expand All @@ -21,7 +21,7 @@ The `*cnb*` variants inherit the installed packages from the non-`*cnb*` variant

## Build

To build the stack images locally, run this from the repo root:
To build the base images locally, run this from the repo root:

bin/build.sh STACK_VERSION

Expand All @@ -36,31 +36,29 @@ If you're building on a machine with an architecture other than amd64, set `DOCK
The supported stacks are: `20` and `22`. This script will build a family
of 4 images:

* `heroku/heroku:{STACK_VERSION}` - The runtime stack image for the Heroku platform
* `heroku/heroku:{STACK_VERSION}-build` - The build-time stack image for the Heroku platform
* `heroku/heroku:{STACK_VERSION}-cnb` - The runtime stack image for Cloud Native Buildpacks
* `heroku/heroku:{STACK_VERSION}-cnb-build` - The build-time stack image for Cloud Native Buildpacks
* `heroku/heroku:{STACK_VERSION}` - The base run image for the Heroku platform
* `heroku/heroku:{STACK_VERSION}-build` - The base build image for the Heroku platform
* `heroku/heroku:{STACK_VERSION}-cnb` - The base run image for Cloud Native Buildpacks
* `heroku/heroku:{STACK_VERSION}-cnb-build` - The base build image for Cloud Native Buildpacks

# Releasing Stack Images
# Releasing Heroku Base Images

We use GitHub Actions to build and release Stack Images:
We use GitHub Actions to build and release Heroku Base Images:

* Any push to `main` will build the images and push the nightly Docker tag variants (such as `heroku/heroku:22-build.nightly`).
* Any new Git tag will build the image and push the latest Docker tag (such as `heroku/heroku:22-build`), as well as a versioned tag (such as `heroku/heroku:22-build.v89`).

# Releasing Heroku Stack Images Locally (Prime)
# Releasing Heroku Base Images Locally (Prime)

When building Heroku Stack Images for release locally, youll need a number of additional steps.
When building Heroku Base Images for release locally, you'll need a number of additional steps.

NOTE: These steps do *not* apply to `*cnb*` images.

export DOCKER_DEFAULT_PLATFORM=linux/amd64
# Build the stack image(s) as you would above
# Build the base image(s) as you would above
# …
cd stack-images/tools
# build the stack-image-tooling
docker build . -t heroku/stack-image-tools
docker build ./tools -t heroku/image-tools
# SET MANIFEST_APP_URL and MANIFEST_APP_TOKEN values, this is the app that controls the bucket for images and metadata about the images (Cheverny)
docker run -it --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock -e "MANIFEST_APP_URL=$MANIFEST_APP_URL" -e "MANIFEST_APP_TOKEN=$MANIFEST_APP_TOKEN" heroku/stack-image-tools STACK
# this will use your local docker image and convert it to a heroku stack image
docker run -it --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock -e "MANIFEST_APP_URL=$MANIFEST_APP_URL" -e "MANIFEST_APP_TOKEN=$MANIFEST_APP_TOKEN" heroku/image-tools STACK
# this will use your local docker image and convert it to a heroku base image
# it will then upload this image and the staging manifest via the MANIFEST_APP
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
## Heroku Stack Images
## Heroku Base Images

[![CI](https://github.com/heroku/stack-images/actions/workflows/ci.yml/badge.svg)](https://github.com/heroku/stack-images/actions/workflows/ci.yml)
[![CI](https://github.com/heroku/base-images/actions/workflows/ci.yml/badge.svg)](https://github.com/heroku/base-images/actions/workflows/ci.yml)

This repository holds recipes for building [Heroku stack images](https://devcenter.heroku.com/articles/stack).
This repository holds recipes for building the base images for [Heroku stacks](https://devcenter.heroku.com/articles/stack).
The recipes are also rendered into Docker images that are available on Docker Hub:

| Image | Base | Type | Status |
|-------------------------------------------|---------------------------------------|----------------------------|-------------|
| [heroku/heroku:18][heroku-tags] | [ubuntu:18.04][ubuntu-tags] | Heroku Runtime Stack Image | End-of-life |
| [heroku/heroku:18-build][heroku-tags] | [heroku/heroku:18][heroku-tags] | Heroku Build Stack Image | End-of-life |
| [heroku/heroku:18-cnb][heroku-tags] | [heroku/heroku:18][heroku-tags] | CNB Runtime Stack Image | End-of-life |
| [heroku/heroku:18-cnb-build][heroku-tags] | [heroku/heroku:18-build][heroku-tags] | CNB Build Stack Image | End-of-life |
| [heroku/heroku:20][heroku-tags] | [ubuntu:20.04][ubuntu-tags] | Heroku Runtime Stack Image | Available |
| [heroku/heroku:20-build][heroku-tags] | [heroku/heroku:20][heroku-tags] | Heroku Build Stack Image | Available |
| [heroku/heroku:20-cnb][heroku-tags] | [heroku/heroku:20][heroku-tags] | CNB Runtime Stack Image | Available |
| [heroku/heroku:20-cnb-build][heroku-tags] | [heroku/heroku:20-build][heroku-tags] | CNB Build Stack Image | Available |
| [heroku/heroku:22][heroku-tags] | [ubuntu:22.04][ubuntu-tags] | Heroku Runtime Stack Image | Recommended |
| [heroku/heroku:22-build][heroku-tags] | [heroku/heroku:22][heroku-tags] | Heroku Build Stack Image | Recommended |
| [heroku/heroku:22-cnb][heroku-tags] | [heroku/heroku:22][heroku-tags] | CNB Runtime Stack Image | Recommended |
| [heroku/heroku:22-cnb-build][heroku-tags] | [heroku/heroku:22-build][heroku-tags] | CNB Build Stack Image | Recommended |
| Image | Base | Type | Status |
|-------------------------------------------|---------------------------------------|-------------------------|-------------|
| [heroku/heroku:18][heroku-tags] | [ubuntu:18.04][ubuntu-tags] | Heroku Base Run Image | End-of-life |
| [heroku/heroku:18-build][heroku-tags] | [heroku/heroku:18][heroku-tags] | Heroku Base Build Image | End-of-life |
| [heroku/heroku:18-cnb][heroku-tags] | [heroku/heroku:18][heroku-tags] | CNB Base Run Image | End-of-life |
| [heroku/heroku:18-cnb-build][heroku-tags] | [heroku/heroku:18-build][heroku-tags] | CNB Base Build Image | End-of-life |
| [heroku/heroku:20][heroku-tags] | [ubuntu:20.04][ubuntu-tags] | Heroku Base Run Image | Available |
| [heroku/heroku:20-build][heroku-tags] | [heroku/heroku:20][heroku-tags] | Heroku Base Build Image | Available |
| [heroku/heroku:20-cnb][heroku-tags] | [heroku/heroku:20][heroku-tags] | CNB Base Run Image | Available |
| [heroku/heroku:20-cnb-build][heroku-tags] | [heroku/heroku:20-build][heroku-tags] | CNB Base Build Image | Available |
| [heroku/heroku:22][heroku-tags] | [ubuntu:22.04][ubuntu-tags] | Heroku Base Run Image | Recommended |
| [heroku/heroku:22-build][heroku-tags] | [heroku/heroku:22][heroku-tags] | Heroku Base Build Image | Recommended |
| [heroku/heroku:22-cnb][heroku-tags] | [heroku/heroku:22][heroku-tags] | CNB Base Run Image | Recommended |
| [heroku/heroku:22-cnb-build][heroku-tags] | [heroku/heroku:22-build][heroku-tags] | CNB Base Build Image | Recommended |

### Learn more

* [Lists of packages installed on current stacks](https://devcenter.heroku.com/articles/stack-packages)
* [Lists of packages installed on current Heroku stacks](https://devcenter.heroku.com/articles/stack-packages)
* [Stack update policy](https://devcenter.heroku.com/articles/stack-update-policy)

See [BUILD.md](BUILD.md) for instructions on how to build the images yourself.
Expand Down
172 changes: 141 additions & 31 deletions bin/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,159 @@ set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
. bin/stack-helpers.sh

[ $# -eq 1 ] || abort "usage: $(basename "${BASH_SOURCE[0]}") STACK_VERSION"
REPO="heroku/heroku"
STACK_VERSION=${1:-"NAN"}
PUBLISH_SUFFIX=${2:-}
BASE_NAME=$(basename "${BASH_SOURCE[0]}")

STACK_VERSION=$1
print_usage(){
>&2 echo "usage: ${BASE_NAME} STACK_VERSION [PUBLISH_SUFFIX]"
>&2 cat <<-EOF
write_package_list() {
local image_tag="$1"
local output_file="${2}/installed-packages.txt"
echo '# List of packages present in the final image. Regenerate using bin/build.sh' > "$output_file"
docker run --rm "$image_tag" dpkg-query --show --showformat='${Package}\n' >> "$output_file"
This script builds heroku base images and writes package lists. It builds
multi-arch images for heroku-24 and newer, and amd64 images for heroku-22 and
older. It works in the following scenarios:
Local builds with Docker Desktop and the 'containerd' snapshotter. In this
mode, all resulting images will be loaded into the local container store.
The 'default' and 'docker-container' drivers both work in this mode.
Note that the 'containerd' snapshotter is not compatible with 'pack'.
For CI tests and package list generation with Docker and the 'default'
Docker driver. In this mode, resulting images will be loaded into the
local container store, the package lists generated, but only for amd64.
The 'default' Docker driver is not able to store/retreive multi-arch images
locally with the default snapshotter, and the 'containerd' snapshotter is
only available with Docker Desktop. The 'docker-container' driver will not
work in this mode (it can't load any images from the default local store).
Publishing images in CI with Docker and the 'docker-container'
driver. Pass in a REPO and PUBLISH_SUFFIX argument to publish images
directly during the build. Since Docker is unable to store/reference
multi-arch images locally, the publish process involves building+pushing
an image to a disposable tag, then retagging it. The 'default' Docker
driver will not work in this mode (it can't build cross-architecture).
EOF
}

# Create buildx driver
docker buildx create --use

RUN_IMAGE_TAG="heroku/heroku:${STACK_VERSION}"
RUN_DOCKERFILE_DIR="heroku-${STACK_VERSION}"
[[ -d "${RUN_DOCKERFILE_DIR}" ]] || abort "fatal: directory ${RUN_DOCKERFILE_DIR} not found"
display "Building ${RUN_DOCKERFILE_DIR} / ${RUN_IMAGE_TAG} Heroku runtime image"
docker buildx build --pull --platform=linux/arm64,linux/amd64 --tag "${RUN_IMAGE_TAG}" --push "${RUN_DOCKERFILE_DIR}" | indent
write_package_list "${RUN_IMAGE_TAG}" "${RUN_DOCKERFILE_DIR}"
[[ $STACK_VERSION =~ ^[0-9]+$ ]] || (>&2 print_usage && abort "fatal: invalid STACK_VERSION")

have_docker_container_driver=
if (docker buildx inspect; true) | grep -q 'Driver:\s*docker-container$'; then
have_docker_container_driver=1
fi

have_containerd_snapshotter=
if docker info -f "{{ .DriverStatus }}" | grep -qF "io.containerd.snapshotter."; then have_containerd_snapshotter=1; fi


if (( STACK_VERSION <= 22 )); then
# heroku/heroku:22 and prior images need separate *cnb* variants that
# add compatibility for Cloud Native Buildpacks.
VARIANTS=("-build:" "-cnb:" "-cnb-build:-build")
else
# heroku/heroku:24 and beyond images include CNB specific
# modifications, so separate *cnb* variants are not created.
VARIANTS=("-build:")
fi

# The --pull option is not used for variants to ensure they are based on the
# runtime stack image built above, rather than the one last published to DockerHub.
# Due to weak feature support parity between Docker on Linux and Docker
# Desktop building and publishing across platforms has caveats (see the
# top of this file).
if [[ $have_containerd_snapshotter ]] || { [[ $PUBLISH_SUFFIX ]] && [[ $have_docker_container_driver ]]; }; then
DOCKER_ARGS=("buildx" "build" "--platform=linux/amd64,linux/arm64")
elif [[ ! $PUBLISH_SUFFIX ]] && [[ ! $have_docker_container_driver ]]; then
DOCKER_ARGS=("buildx" "build" "--platform=linux/amd64")
>&2 echo "WARNING: heroku-24 and newer images are multi-arch images," \
"but this script is building single architecture images" \
"due to limitations of the current platform." \
"To build a multi-arch image, enable the 'containerd'" \
"snapshotter in Docker Desktop and/or use a 'docker-container'" \
"Docker BuildKit driver."
else
>&2 echo "ERROR: Can't build images with this configuration. Enable" \
"the 'containerd' snapshotter in Docker Desktop, enable" \
"the 'docker-container' driver in Docker, or use this script" \
"in build-only mode (don't provide PUBLISH_SUFFIX argument)."
abort 1
fi

BUILD_IMAGE_TAG="${RUN_IMAGE_TAG}-build"
BUILD_DOCKERFILE_DIR="${RUN_DOCKERFILE_DIR}-build"
display "Building ${BUILD_DOCKERFILE_DIR} / ${BUILD_IMAGE_TAG} Heroku build-time image"
docker buildx build --platform=linux/arm64,linux/amd64 --tag "$BUILD_IMAGE_TAG" --push "$BUILD_DOCKERFILE_DIR" | indent
write_package_list "$BUILD_IMAGE_TAG" "$BUILD_DOCKERFILE_DIR"
if [[ $PUBLISH_SUFFIX ]]; then
# If there is a tag suffix, this script is pushing to a remote registry.
DOCKER_ARGS+=("--push")
else
# Otherwise, load the image into the local image store.
DOCKER_ARGS+=("--load")
fi

write_package_list() {
local image_tag="$1"
local dockerfile_dir="$2"

# Extract the stack version from the dockerfile_dir variable (e.g., heroku-24)
local stack_version
stack_version=$(echo "$dockerfile_dir" | sed -n 's/^heroku-\([0-9]*\).*$/\1/p')

local archs=("amd64")
# heroku-24 and newer are multiarch. If containerd is available,
# the package list for each architecture can be generated.
if (( stack_version >= 24 )); then
if [[ $have_containerd_snapshotter ]]; then
archs+=(arm64)
else
>&2 echo "WARNING: Generating package list for single architecture." \
"Use the 'containerd' snapshotter to generate package lists" \
"for all architectures."
fi
fi
local output_file=""
for arch in "${archs[@]}"; do
if (( stack_version >= 24 )); then
output_file="${dockerfile_dir}/installed-packages-${arch}.txt"
else
output_file="${dockerfile_dir}/installed-packages.txt"
fi
display "Generating package list: ${output_file}"
echo "# List of packages present in the final image. Regenerate using bin/build.sh" > "$output_file"
docker run --rm --platform="linux/${arch}" "$image_tag" dpkg-query --show --showformat='${Package}\n' >> "$output_file"
done
}

RUN_IMAGE_TAG="${REPO}:${STACK_VERSION}${PUBLISH_SUFFIX}"
RUN_DOCKERFILE_DIR="heroku-${STACK_VERSION}"

[[ -d "${RUN_DOCKERFILE_DIR}" ]] || abort "fatal: directory ${RUN_DOCKERFILE_DIR} not found"
display "Building ${RUN_DOCKERFILE_DIR} / ${RUN_IMAGE_TAG} image"
# The --pull option is used for the run image, so that the latest updates
# from upstream ubuntu images are included.
docker "${DOCKER_ARGS[@]}" --pull \
--tag "${RUN_IMAGE_TAG}" "${RUN_DOCKERFILE_DIR}" | indent
write_package_list "${RUN_IMAGE_TAG}" "${RUN_DOCKERFILE_DIR}"

# write_package_list is not needed for *cnb* variants, as they don't install
# any additional packages over their non-*cnb* counterparts.
for VARIANT in "${VARIANTS[@]}"; do
VARIANT_NAME=$(echo "$VARIANT" | cut -d ":" -f 1)
DEPENDENCY_NAME=$(echo "$VARIANT" | cut -d ":" -f 2)
VARIANT_IMAGE_TAG="${REPO}:${STACK_VERSION}${VARIANT_NAME}${PUBLISH_SUFFIX}"
VARIANT_DOCKERFILE_DIR="heroku-${STACK_VERSION}${VARIANT_NAME}"
DEPENDENCY_IMAGE_TAG="${REPO}:${STACK_VERSION}${DEPENDENCY_NAME}${PUBLISH_SUFFIX}"

CNB_RUN_IMAGE_TAG="${RUN_IMAGE_TAG}-cnb"
CNB_RUN_DOCKERFILE_DIR="${RUN_DOCKERFILE_DIR}-cnb"
display "Building ${CNB_RUN_DOCKERFILE_DIR} / ${CNB_RUN_IMAGE_TAG} CNB runtime image"
docker buildx build --platform=linux/arm64,linux/amd64 --tag "$CNB_RUN_IMAGE_TAG" --push "$CNB_RUN_DOCKERFILE_DIR" | indent
[[ -d "${VARIANT_DOCKERFILE_DIR}" ]] || abort "fatal: directory ${VARIANT_DOCKERFILE_DIR} not found"
display "Building ${VARIANT_DOCKERFILE_DIR} / ${VARIANT_IMAGE_TAG} image"
# The --pull option is not used for variants since they depend on images
# built earlier in this script.
docker "${DOCKER_ARGS[@]}" --build-arg "BASE_IMAGE=${DEPENDENCY_IMAGE_TAG}" \
--tag "${VARIANT_IMAGE_TAG}" "${VARIANT_DOCKERFILE_DIR}" | indent

CNB_BUILD_IMAGE_TAG="${RUN_IMAGE_TAG}-cnb-build"
CNB_BUILD_DOCKERFILE_DIR="${RUN_DOCKERFILE_DIR}-cnb-build"
display "Building ${CNB_BUILD_DOCKERFILE_DIR} / ${CNB_BUILD_IMAGE_TAG} CNB build-time image"
docker buildx build --platform=linux/arm64,linux/amd64 --tag "$CNB_BUILD_IMAGE_TAG" --push "$CNB_BUILD_DOCKERFILE_DIR" | indent
# generate the package list for non-cnb variants. cnb variants don't
# influence the list of installed packages.
if [[ ! "$VARIANT_NAME" = -cnb* ]]; then
write_package_list "$VARIANT_IMAGE_TAG" "$VARIANT_DOCKERFILE_DIR"
fi
done

display "Size breakdown..."
docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}" \
| grep -E "(ubuntu|heroku)" | sed '1!G;h;$!d' | indent
| grep -E "(ubuntu|heroku)" | sed '1!G;h;$!d' | indent
Loading

0 comments on commit 3121926

Please sign in to comment.