Skip to content

Commit id 502f1ab94339e659016e66883e60734243e51432: CI-CD build and deploy docker images based on the commit id in the main branch #5222

Commit id 502f1ab94339e659016e66883e60734243e51432: CI-CD build and deploy docker images based on the commit id in the main branch

Commit id 502f1ab94339e659016e66883e60734243e51432: CI-CD build and deploy docker images based on the commit id in the main branch #5222

name: CI-CD build and deploy docker images based on the last commit in the target branch
run-name: "Commit id ${{ github.sha }}: CI-CD build and deploy docker images based on the commit id in the ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }} branch"
env:
APPLICATION: "erigon"
APP_REPO: "erigontech/erigon"
DOCKERHUB_REPOSITORY: "erigontech/erigon"
BUILDER_IMAGE: "golang:1.26-trixie"
LABEL_DESCRIPTION: "[docker image built on the last commit id from the main branch] Erigon is an implementation of Ethereum (execution layer with embeddable consensus layer), on the efficiency frontier. Archive Node by default."
on:
push:
branches:
- 'main'
- 'docker_pectra'
paths-ignore:
- '.github/**'
workflow_dispatch:
inputs:
checkout_ref:
required: false
type: string
default: ''
description: 'The branch to checkout and build artifacts from (in case of a manual run). Important: the Docker image tag is generated automatically from the branch name by removing everything before the last slash. For example, for the branch "feature/user/my-cool-change", the tag will be "my-cool-change". Default is "".'
permissions:
contents: read
jobs:
Build:
name: Build and publish docker image
# runs-on: ubuntu-latest
runs-on: [devops-01-self-hosted]
environment: dockerhub-publish
timeout-minutes: 45
outputs:
docker_build_tag: ${{ steps.built_tag_export.outputs.docker_build_tag }}
steps:
- name: Cleanup workspace
run: |
rm -drf $(pwd)/*
- name: Fast checkout git repository, git ref ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }}
uses: actions/checkout@v7
with:
repository: ${{ env.APP_REPO }}
fetch-depth: 1
ref: ${{ inputs.checkout_ref == '' && github.ref || inputs.checkout_ref }}
path: 'erigon'
persist-credentials: false
- name: Define variables
id: def_docker_vars
## Idea is:
## latest image: erigontech/erigon:${tag_name}${latest_suffix}
## commit id image: erigontech/erigon:${tag_name}-${short_commit_id}
env:
BRANCH_REF: ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }}
run: |
branch_name="$BRANCH_REF"
case "$branch_name" in
"main" )
export tag_name='main';
export keep_images=100;
export latest_suffix='-latest'
export binaries="erigon integration rpcdaemon"
;;
"docker_pectra" )
export tag_name='docker_pectra';
export keep_images=5;
export latest_suffix='';
export binaries="erigon caplin downloader evm hack integration rpcdaemon rpctest sentinel sentry state txpool"
;;
* )
# use last string after last slash '/' by default if branch contains slash:
export tag_name=$(echo "$branch_name" | sed -e 's/.*\///g');
export keep_images=0;
export latest_suffix=''
export binaries="erigon"
;;
esac
echo "tag_name=${tag_name}" >> $GITHUB_OUTPUT
echo "keep_images=${keep_images}" >> $GITHUB_OUTPUT
echo "latest_suffix=${latest_suffix}" >> $GITHUB_OUTPUT
echo "binaries=${binaries}" >> $GITHUB_OUTPUT
echo "Debug ${tag_name} ${keep_images} ${latest_suffix} ${binaries}"
- name: Get commit id
id: getCommitId
run: |
cd erigon
echo "id=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "short_commit_id=$(git rev-parse --short=7 HEAD)" >> $GITHUB_OUTPUT
cd ..
- name: Login to Docker Hub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 ## v4.0.0
with:
username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
password: ${{ secrets.DOCKERHUB_PUSH_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4.2.0
- name: Set up Docker Buildx
id: docker-buildx
uses: docker/setup-buildx-action@v4
- name: Build and push multi-platform docker image based on the commit id ${{ steps.getCommitId.outputs.short_commit_id }} in the ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }} branch
id: built_tag_export
env:
BUILD_VERSION: "${{ steps.def_docker_vars.outputs.tag_name }}-${{ steps.getCommitId.outputs.short_commit_id }}"
BUILD_VERSION_LATEST: "${{ steps.def_docker_vars.outputs.tag_name }}${{ steps.def_docker_vars.outputs.latest_suffix }}"
DOCKER_PUBLISH_CONDITION: ${{ steps.def_docker_vars.outputs.keep_images > 0 && format('--tag {0}:{1}-{2} ', env.DOCKERHUB_REPOSITORY, steps.def_docker_vars.outputs.tag_name, steps.getCommitId.outputs.short_commit_id) || '' }}
DOCKER_URL: ${{ env.DOCKERHUB_REPOSITORY }}
DOCKERFILE_PATH: Dockerfile
BINARIES: ${{ steps.def_docker_vars.outputs.binaries }}
IMAGE_REF: ${{ inputs.checkout_ref == '' && github.ref || inputs.checkout_ref }}
COMMIT_ID: ${{ steps.getCommitId.outputs.id }}
SHORT_COMMIT_ID: ${{ steps.getCommitId.outputs.short_commit_id }}
REPO_OWNER: ${{ github.repository_owner }}
run: |
echo "docker_build_tag=$BUILD_VERSION" >> $GITHUB_OUTPUT
cd erigon
docker buildx build \
--file "$DOCKERFILE_PATH" \
--build-arg BINARIES="$BINARIES" \
--build-arg BUILDER_IMAGE="$BUILDER_IMAGE" \
--attest type=provenance,mode=max \
--no-cache \
--sbom=true \
${DOCKER_PUBLISH_CONDITION} \
--tag "$DOCKER_URL:$BUILD_VERSION_LATEST" \
--label org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--label org.opencontainers.image.authors="https://github.com/erigontech/erigon/graphs/contributors" \
--label org.opencontainers.image.url="https://github.com/erigontech/erigon/blob/$IMAGE_REF/Dockerfile" \
--label org.opencontainers.image.documentation="https://docs.erigon.tech/" \
--label org.opencontainers.image.source="https://github.com/erigontech/erigon" \
--label org.opencontainers.image.version="$COMMIT_ID" \
--label org.opencontainers.image.revision="$COMMIT_ID" \
--label org.opencontainers.image.vcs-ref-short="$SHORT_COMMIT_ID" \
--label org.opencontainers.image.vendor="$REPO_OWNER" \
--label org.opencontainers.image.description="$LABEL_DESCRIPTION" \
--push \
--platform linux/amd64,linux/arm64 .
echo "Docker build and push done"
- name: export and print docker build tag, cleanup old docker images
env:
BUILD_VERSION: "${{ steps.def_docker_vars.outputs.tag_name }}-${{ steps.getCommitId.outputs.short_commit_id }}"
BUILD_VERSION_LATEST: "${{ steps.def_docker_vars.outputs.tag_name }}${{ steps.def_docker_vars.outputs.latest_suffix }}"
BUILD_VERSION_CONDITION: ${{ steps.def_docker_vars.outputs.keep_images > 0 && format('{0}:{1}-{2} ', env.DOCKERHUB_REPOSITORY, steps.def_docker_vars.outputs.tag_name, steps.getCommitId.outputs.short_commit_id) || '' }}
DOCKER_URL: ${{ env.DOCKERHUB_REPOSITORY }}
TAG_KEY: ${{ steps.def_docker_vars.outputs.tag_name }}
KEEP_IMAGES: ${{ steps.def_docker_vars.outputs.keep_images }}
DH_USERNAME: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
DH_TOKEN: ${{ secrets.DOCKERHUB_PUSH_TOKEN }}
run: |
HTTP_RESP=$(curl -s -o ./resp1 -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{
"identifier": "'"$DH_USERNAME"'",
"secret": "'"$DH_TOKEN"'"
}' \
https://hub.docker.com/v2/auth/token/)
if [ "$HTTP_RESP" -ne "200" ]; then
echo ERROR: HTTP response $HTTP_RESP from https://hub.docker.com/v2/auth/token/
fi
DOCKER_JWT=$(jq -r .access_token ./resp1)
rm -f ./resp1
# Registry delete-scoped token: the Hub JWT untags an image, but reclaiming
# storage requires deleting the manifests by digest via the registry API,
# which needs a token issued with 'delete' scope (PAT must allow deletion).
REG_TOKEN=$(curl -s -u "$DH_USERNAME:$DH_TOKEN" \
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_REPOSITORY}:pull,delete" \
| jq -r .token)
echo The following docker images have been published:
echo "$DOCKERHUB_REPOSITORY:$BUILD_VERSION_LATEST"
echo "$BUILD_VERSION_CONDITION (empty, if keep_images is 0)"
echo
echo "Cleanup old docker images matching pattern tag ~= $TAG_KEY-XXXXXXX (where XXXXXXX is a short commit id)"
echo "Only last $KEEP_IMAGES images will be kept."
curl_cmd="curl -s -H \"Authorization: Bearer ${DOCKER_JWT}\" "
dockerhub_url='https://hub.docker.com/v2/namespaces/erigontech/repositories/erigon'
## getting all pages in a loop from dockerhub and grepping required tag from the list of tags:
my_list () {
# First page:
next_page="$dockerhub_url/tags?page=1&page_size=100"
while [ "$next_page" != "null" ]
do
# Print tags and push dates for tags matching "$TAG_KEY-":
$curl_cmd $next_page | jq -r '.results|.[]|.name + " " + .tag_last_pushed' | grep "$TAG_KEY-" || true
next_page=`$curl_cmd $next_page | jq '.next' | sed -e 's/^\"//' -e 's/\"$//'`
done
}
# A tag is just a pointer; a manifest still consumes storage until deleted by
# digest. A 403 means either the manifest is still referenced by another kept
# tag/image, or the token lacks the 'delete' scope.
delete_manifest () {
digest="$1"
if [ -z "$digest" ] || [ "$digest" = "null" ]; then
return 0
fi
code=$(curl --write-out %{http_code} --output curl-output.log \
-s -X DELETE \
-H "Authorization: Bearer ${REG_TOKEN}" \
https://registry-1.docker.io/v2/${DOCKERHUB_REPOSITORY}/manifests/${digest})
case "$code" in
200|202) echo -n "manifest ${digest} deleted. " ;;
403) echo -n "manifest ${digest} not deleted (403: still referenced or missing delete permission). " ;;
*) echo "ERROR: failed to delete manifest ${digest} (HTTP $code): $(cat curl-output.log)." ;;
esac
}
echo "DEBUG: full list of images:"
my_list
echo "DEBUG: end of the list."
# Sort newest-first by push time (ISO-8601 sorts chronologically) so tail keeps
# the newest KEEP_IMAGES; the Hub tags API order is not contractually guaranteed.
my_list | sort -k2,2 -r | tail -n +"$((KEEP_IMAGES + 1))" | while read line; do
echo -n "Removing docker image/published - $line "
current_image=$(echo $line | sed -e "s/^\(${TAG_KEY}-.\{7\}\) .*/\1/")
# Collect the manifest digests behind this tag before untagging it.
meta=$(curl -s -H "Authorization: Bearer ${DOCKER_JWT}" "$dockerhub_url/tags/${current_image}")
index_digest=$(echo "$meta" | jq -r '.digest // empty')
image_digests=$(echo "$meta" | jq -r '.images[]?.digest // empty')
output_code=$(curl --write-out %{http_code} --output curl-output.log \
-s -X DELETE -H "Accept: application/json" \
-H "Authorization: Bearer ${DOCKER_JWT}" \
https://hub.docker.com/v2/repositories/erigontech/erigon/tags/${current_image} )
if [ $output_code -ne 204 ]; then
echo "ERROR: failed to remove docker image erigon:${current_image}"
echo "ERROR: API response: $(cat curl-output.log)."
continue
fi
echo -n " - tag removed. "
# Delete the index first so its per-platform manifests become unreferenced
# and deletable, then delete those to actually reclaim storage.
delete_manifest "$index_digest"
for d in $image_digests; do
delete_manifest "$d"
done
echo "Done."
done
- name: Notify Discord on failure
# Alert only on the automated main-branch builds; manual dispatch runs
# are watched by whoever triggered them. No-op when the webhook is unset.
if: ${{ failure() && github.event_name == 'push' }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
BRANCH: ${{ github.ref_name }}
run: |
if [ -z "${DISCORD_WEBHOOK:-}" ]; then
echo "DISCORD_WEBHOOK not set — skipping Discord notification."
exit 0
fi
msg=":x: **CI-CD docker image build failed** on branch \`${BRANCH}\`\nRun: ${RUN_URL}"
payload=$(jq -nc --arg c "$msg" '{content: $c}')
curl -sS -f -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK" \
&& echo "Discord notified." \
|| echo "::warning::Discord notification failed (webhook unreachable or invalid)."