Skip to content

Build and publish Docker images #7

Build and publish Docker images

Build and publish Docker images #7

Workflow file for this run

name: Build and publish Docker images
on:
workflow_dispatch:
inputs:
releaseTag:
description: 'Required fcli release tag for which to build and release Docker images (e.g., v3.14.0)'
required: true
type: string
doPublish:
description: 'Publish images to Docker Hub'
required: true
type: boolean
default: false
alpineBase:
description: 'Alpine base image (default: alpine:3.23.0)'
required: false
type: string
default: 'alpine:3.23.0'
ubiBase:
description: 'Red Hat UBI base image (default: redhat/ubi9:9.7)'
required: false
type: string
default: 'redhat/ubi9:9.7'
servercoreBase:
description: 'Windows Server Core base image (default: mcr.microsoft.com/windows/servercore:ltsc2022)'
required: false
type: string
default: 'mcr.microsoft.com/windows/servercore:ltsc2022'
updateBaseImages:
description: 'Update base images for already-released fcli version (advanced: republish with new base images)'
required: false
type: boolean
default: false
permissions:
contents: read
packages: write
jobs:
docker-linux:
name: Build & Publish Linux Images
runs-on: ubuntu-latest
env:
DOCKER_SRC: linux
REGISTRY: docker.io
IMAGE_NAME: fortifydocker/fcli
steps:
- name: Check-out source code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:latest
- name: Docker Login
if: ${{ inputs.doPublish }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Check for newer base images
continue-on-error: true
run: |
echo "Checking for newer base image versions..."
check_dockerhub_image() {
local repo=$1
local current=$2
local pattern=$3
local name=$4
echo "Checking $name..."
local latest=$(curl -fsSL "https://registry.hub.docker.com/v2/repositories/$repo/tags?page_size=100" | \
jq -r '.results[].name' | grep -E "$pattern" | sort -V | tail -1)
if [[ -z "$latest" ]]; then
echo "::warning::Failed to check $name updates"
return
fi
local current_tag="${current##*:}"
if [[ "$latest" != "$current_tag" ]]; then
echo "::warning::Newer $name version available: ${repo##*/}:$latest (current: $current)"
else
echo "✓ $name is up to date: $current"
fi
}
check_mcr_image() {
local image=$1
local current=$2
local pattern=$3
local name=$4
echo "Checking $name..."
# Get auth token from MCR
local token=$(curl -fsSL "https://mcr.microsoft.com/v2/token?scope=repository:${image}:pull" | jq -r '.token')
if [[ -z "$token" ]]; then
echo "::warning::Failed to authenticate with MCR for $name"
return
fi
# Get tags list
local latest=$(curl -fsSL -H "Authorization: Bearer $token" \
"https://mcr.microsoft.com/v2/${image}/tags/list" | \
jq -r '.tags[]' | grep -E "$pattern" | sort -V | tail -1)
if [[ -z "$latest" ]]; then
echo "::notice::Could not determine latest $name version (current: $current)"
return
fi
local current_tag="${current##*:}"
if [[ "$latest" != "$current_tag" ]]; then
echo "::warning::Newer $name version available: $latest (current: $current)"
else
echo "✓ $name is up to date: $current"
fi
}
# Check Linux base images (Docker Hub)
check_dockerhub_image "library/alpine" "${{ inputs.alpineBase }}" "^3\.[0-9]+\.[0-9]+$" "Alpine"
check_dockerhub_image "redhat/ubi9" "${{ inputs.ubiBase }}" "^9\.[0-9]+(\.[0-9]+)?$" "Red Hat UBI9"
# Check Windows base image (MCR)
echo ""
SERVERCORE_DEFAULT="mcr.microsoft.com/windows/servercore:ltsc2022"
check_mcr_image "windows/servercore" "$SERVERCORE_DEFAULT" "^ltsc[0-9]+$" "Windows Server Core"
- name: Extract metadata
id: meta
run: |
# Extract version from tag (remove 'v' prefix if present)
VERSION="${{ inputs.releaseTag }}"
VERSION="${VERSION#v}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
# Determine tags
TAGS="${{ env.IMAGE_NAME }}:${VERSION}"
# Add 'latest' tag only for non-dev versions and when not updating base images
if [[ ! "$VERSION" =~ ^dev_ ]] && [[ "${{ inputs.updateBaseImages }}" != "true" ]]; then
TAGS="${TAGS},${{ env.IMAGE_NAME }}:latest"
fi
# Add timestamp suffix if updating base images
if [[ "${{ inputs.updateBaseImages }}" == "true" ]]; then
DATE=$(date +%Y%m%d)
TAGS="${TAGS},${{ env.IMAGE_NAME }}:${VERSION}-${DATE}"
fi
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "Generated tags: ${TAGS}"
# Build and push fcli-scratch image (preferred/primary image)
- name: Build and push fcli-scratch
uses: docker/build-push-action@v6
with:
context: ${{ env.DOCKER_SRC }}
file: ${{ env.DOCKER_SRC }}/Dockerfile
target: fcli-scratch
platforms: linux/amd64
push: ${{ inputs.doPublish }}
tags: ${{ steps.meta.outputs.tags }}
labels: |
org.opencontainers.image.source=${{ github.repositoryUrl }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.event.repository.updated_at }}
build-args: |
FCLI_VERSION=${{ inputs.releaseTag }}
ALPINE_BASE=${{ inputs.alpineBase }}
UBI_BASE=${{ inputs.ubiBase }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
# Build and push fcli-ubi9 image (secondary shell-based image)
- name: Extract UBI9 metadata
id: meta-ubi9
run: |
VERSION="${{ inputs.releaseTag }}"
VERSION="${VERSION#v}"
TAGS="${{ env.IMAGE_NAME }}:${VERSION}-ubi9"
if [[ "${{ inputs.updateBaseImages }}" == "true" ]]; then
DATE=$(date +%Y%m%d)
TAGS="${TAGS},${{ env.IMAGE_NAME }}:${VERSION}-ubi9-${DATE}"
fi
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "Generated UBI9 tags: ${TAGS}"
- name: Build and push fcli-ubi9
uses: docker/build-push-action@v6
with:
context: ${{ env.DOCKER_SRC }}
file: ${{ env.DOCKER_SRC }}/Dockerfile
target: fcli-ubi9
platforms: linux/amd64
push: ${{ inputs.doPublish }}
tags: ${{ steps.meta-ubi9.outputs.tags }}
labels: |
org.opencontainers.image.source=${{ github.repositoryUrl }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.event.repository.updated_at }}
build-args: |
FCLI_VERSION=${{ inputs.releaseTag }}
ALPINE_BASE=${{ inputs.alpineBase }}
UBI_BASE=${{ inputs.ubiBase }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
# Build (but don't push) Alpine image for testing
- name: Build fcli-alpine (test only)
uses: docker/build-push-action@v6
with:
context: ${{ env.DOCKER_SRC }}
file: ${{ env.DOCKER_SRC }}/Dockerfile
target: fcli-alpine
platforms: linux/amd64
push: false
tags: fcli-alpine:test
build-args: |
FCLI_VERSION=${{ inputs.releaseTag }}
ALPINE_BASE=${{ inputs.alpineBase }}
UBI_BASE=${{ inputs.ubiBase }}
load: true
# Test images
- name: Test fcli-scratch image
run: |
docker pull ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} || \
docker tag $(docker images -q ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} | head -1) ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
mkdir -p ${PWD}/test-scratch
docker run --rm -u $(id -u):$(id -g) -v ${PWD}/test-scratch:/data \
${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} tool sc-client install
test -f ${PWD}/test-scratch/fortify/tools/bin/scancentral
echo "✓ fcli-scratch image test passed"
- name: Test fcli-ubi9 image
run: |
docker pull ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-ubi9 || \
docker tag $(docker images -q ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-ubi9 | head -1) ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-ubi9
mkdir -p ${PWD}/test-ubi9
docker run --rm -u $(id -u):$(id -g) -v ${PWD}/test-ubi9:/data \
${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-ubi9 fcli tool sc-client install
test -f ${PWD}/test-ubi9/fortify/tools/bin/scancentral
echo "✓ fcli-ubi9 image test passed"
- name: Test fcli-alpine image
run: |
mkdir -p ${PWD}/test-alpine
docker run --rm -u $(id -u):$(id -g) -v ${PWD}/test-alpine:/data \
fcli-alpine:test fcli tool sc-client install
test -f ${PWD}/test-alpine/fortify/tools/bin/scancentral
echo "✓ fcli-alpine image test passed"
- name: Summary
if: always()
run: |
echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**fcli Version:** ${{ inputs.releaseTag }}" >> $GITHUB_STEP_SUMMARY
echo "**Published:** ${{ inputs.doPublish }}" >> $GITHUB_STEP_SUMMARY
echo "**Alpine Base:** ${{ inputs.alpineBase }}" >> $GITHUB_STEP_SUMMARY
echo "**UBI Base:** ${{ inputs.ubiBase }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Published Images" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}\` (scratch)" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}-ubi9\` (ubi9)" >> $GITHUB_STEP_SUMMARY
if [[ "${{ inputs.updateBaseImages }}" != "true" ]] && [[ ! "${{ inputs.releaseTag }}" =~ ^dev_ ]]; then
echo "- \`${{ env.IMAGE_NAME }}:latest\` (scratch)" >> $GITHUB_STEP_SUMMARY
fi
# Windows images: build only for testing, do not publish
docker-windows:
name: Build Windows Images (Test Only)
runs-on: windows-2022
env:
DOCKER_SRC: windows
steps:
- name: Check-out source code
uses: actions/checkout@v4
- name: Build Windows image
shell: pwsh
run: |
cd $env:DOCKER_SRC
docker build . `
--target fcli-ltsc2022 `
-t fcli-windows:test `
--build-arg FCLI_VERSION=${{ inputs.releaseTag }} `
--build-arg SERVERCORE_BASE=${{ inputs.servercoreBase }}
Write-Host "✓ Windows image build completed"
- name: Test Windows image
shell: pwsh
run: |
# Basic test: check fcli version
docker run --rm fcli-windows:test fcli --version
Write-Host "✓ Windows image test passed"
Write-Host "Note: Windows images are built for testing only and are not published"