Skip to content

Merge pull request #21 from FakeOverFlow/shreyas/tags #46

Merge pull request #21 from FakeOverFlow/shreyas/tags

Merge pull request #21 from FakeOverFlow/shreyas/tags #46

name: Build and Push Backend Image
on:
push:
branches: [ main ]
paths:
- '.github/workflows/build-push-backend-image.yml'
- 'apps/fakeoverflow-api/**'
workflow_dispatch:
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}-backend
jobs:
build:
name: Build and Push (${{ matrix.platform }})
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runs_on: ubuntu-latest
- arch: arm64
platform: linux/arm64
runs_on: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata with arch suffix
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
flavor: |
latest=auto
suffix=-${{ matrix.arch }}
- name: Verify Dockerfile exists
run: |
DOCKERFILE_PATH="apps/fakeoverflow-api/FakeOverFlow/FakeoverFlow.Backend.Http.Api/Dockerfile"
if [ ! -f "$DOCKERFILE_PATH" ]; then
echo "Error: Dockerfile not found at $DOCKERFILE_PATH"
exit 1
fi
- name: Build and push per-arch image
id: build
uses: docker/build-push-action@v6
with:
context: apps/fakeoverflow-api/FakeOverFlow
file: apps/fakeoverflow-api/FakeOverFlow/FakeoverFlow.Backend.Http.Api/Dockerfile
push: true
platforms: ${{ matrix.platform }}
build-args: |
BUILD_CONFIGURATION=Release
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
provenance: false
sbom: false
manifest:
name: Create and Push Multi-Arch Manifests
runs-on: ubuntu-latest
needs: build
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract base Docker metadata (no arch suffix)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
flavor: |
latest=auto
- name: Create and push manifest list for each tag
run: |
set -euo pipefail
# Function to check if image exists
check_image_exists() {
local image="$1"
if docker buildx imagetools inspect "$image" >/dev/null 2>&1; then
echo "✓ Image exists: $image"
return 0
else
echo "✗ Image not found: $image"
return 1
fi
}
echo "Creating multi-arch manifests for the following tags:"
echo "${{ steps.meta.outputs.tags }}"
echo "---"
# Convert tags to array and process each one
IFS=$'\n' read -rd '' -a tags_array <<< "${{ steps.meta.outputs.tags }}" || true
for tag in "${tags_array[@]}"; do
if [ -n "$tag" ]; then
echo "Processing tag: $tag"
# Check if both arch-specific images exist
amd64_image="${tag}-amd64"
arm64_image="${tag}-arm64"
if check_image_exists "$amd64_image" && check_image_exists "$arm64_image"; then
echo "Creating manifest $tag from $amd64_image and $arm64_image"
docker buildx imagetools create \
--tag "$tag" \
"$amd64_image" \
"$arm64_image"
echo "✓ Successfully created manifest for $tag"
else
echo "⚠️ Skipping $tag due to missing arch-specific images"
exit 1
fi
echo "---"
fi
done
echo "All manifests created successfully!"
- name: Cleanup arch-specific tags to reduce SHA clutter in GHCR
env:
REGISTRY: ${{ env.REGISTRY }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
IMAGE_LOWER=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]')
ACCEPT="application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json"
echo "Cleaning up arch-specific tags for: ${IMAGE_LOWER} on ${REGISTRY}"
IFS=$'\n' read -rd '' -a tags_array <<< "${{ steps.meta.outputs.tags }}" || true
for tag in "${tags_array[@]}"; do
if [ -z "$tag" ]; then continue; fi
for arch in amd64 arm64; do
ref="${tag}-${arch}"
echo "Processing tag to delete: $ref"
# Get digest for the tag
digest=$(curl -fsSLI \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: ${ACCEPT}" \
"https://${REGISTRY}/v2/${IMAGE_LOWER}/manifests/${ref}" \
| awk -F': ' '/Docker-Content-Digest/ {gsub("\r",""); print $2; exit}') || true
if [ -n "${digest:-}" ]; then
echo "Deleting manifest by digest: ${digest} (for ${ref})"
curl -fsSL -X DELETE \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
"https://${REGISTRY}/v2/${IMAGE_LOWER}/manifests/${digest}" \
&& echo "✓ Deleted ${ref}" \
|| echo "⚠️ Failed to delete ${ref} (it may already be removed)"
else
echo "No digest found for ${ref}; skipping."
fi
done
done
- name: Verify multi-arch manifests
run: |
echo "Verifying created manifests support both architectures:"
IFS=$'\n' read -rd '' -a tags_array <<< "${{ steps.meta.outputs.tags }}" || true
for tag in "${tags_array[@]}"; do
if [ -n "$tag" ]; then
echo "Checking $tag:"
docker buildx imagetools inspect "$tag" --format '{{json .Manifest}}' | jq -r '.manifests[] | " - \(.platform.architecture)/\(.platform.os)"'
fi
done