Skip to content

Docker Publish (dev - latest build) #16

Docker Publish (dev - latest build)

Docker Publish (dev - latest build) #16

name: Publish to Docker Hub
run-name: Docker Publish (${{ inputs.docker_tag || 'auto' }} - ${{ inputs.run_id || 'latest build' }})
on:
workflow_dispatch:
inputs:
docker_tag:
description: "Docker tag to publish"
required: true
type: choice
options:
- dev
- preview
- latest
default: "dev"
run_id:
description: "Build workflow run ID (leave empty to use latest)"
required: false
type: string
use_local_storage:
description: "Use local artifact storage"
required: false
type: boolean
default: true
use_self_hosted_runners:
description: "Use self-hosted runners"
required: false
type: boolean
default: true
workflow_call:
inputs:
docker_tag:
description: "Docker tag to publish (leave empty to derive from the build metadata)"
required: false
type: string
default: ""
run_id:
description: "Build workflow run ID (leave empty to use latest)"
required: false
type: string
use_local_storage:
description: "Use local artifact storage"
required: false
type: boolean
default: true
use_self_hosted_runners:
description: "Use self-hosted runners"
required: false
type: boolean
default: true
permissions:
actions: read
contents: read
jobs:
publish:
name: Publish to Docker Hub
runs-on: ${{ inputs.use_self_hosted_runners && fromJson('["self-hosted","Linux"]') || fromJson('["ubuntu-latest"]') }}
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Validate local storage configuration
uses: ./.github/actions/validate-local-storage
with:
use_local_storage: ${{ inputs.use_local_storage }}
artifact_host: ${{ vars.ARTIFACT_HOST }}
artifact_base_path: ${{ vars.ARTIFACT_BASE_PATH }}
- name: Get Latest Build Run ID
id: get-run-id
uses: ./.github/actions/get-latest-build-run-id
with:
run_id: ${{ inputs.run_id }}
github_token: ${{ github.token }}
- name: Download Server Artifact
uses: ./.github/actions/download-artifact
with:
name: Server
path: ./server-publish
github-token: ${{ github.token }}
artifact-host: ${{ inputs.use_local_storage && vars.ARTIFACT_HOST || '' }}
artifact-base-path: ${{ inputs.use_local_storage && vars.ARTIFACT_BASE_PATH || '' }}
run-id: ${{ steps.get-run-id.outputs.run_id }}
- name: Verify Server Payload
uses: ./.github/actions/verify-server-payload
with:
path: ./server-publish
- name: Get Build Metadata
id: metadata
uses: ./.github/actions/get-build-metadata
with:
run-id: ${{ steps.get-run-id.outputs.run_id }}
github-token: ${{ github.token }}
artifact-host: ${{ inputs.use_local_storage && vars.ARTIFACT_HOST || '' }}
artifact-base-path: ${{ inputs.use_local_storage && vars.ARTIFACT_BASE_PATH || '' }}
# Each Linux runtime the build produced becomes one architecture in the image. The
# build metadata decides which runtimes matter; a prerelease build can only go out
# on the 'dev' tag, so a stable image can never be clobbered by a dev build.
- name: Prepare Docker Context
id: payload
shell: pwsh
env:
DOCKER_TAG_INPUT: ${{ inputs.docker_tag }}
PRERELEASE: ${{ steps.metadata.outputs.prerelease }}
RELEASE_VERSION: ${{ steps.metadata.outputs.release_version }}
SERVER_RIDS: ${{ steps.metadata.outputs.server_rids }}
run: |
$ArchByRid = [ordered]@{
'linux-x64' = 'amd64'
'linux-arm64' = 'arm64'
}
# An explicit tag wins; otherwise the build metadata decides, so a prerelease
# build can never be published over the stable 'latest' image.
$DockerTag = $env:DOCKER_TAG_INPUT
if (-not $DockerTag) {
$DockerTag = if ($env:PRERELEASE -eq 'true') { 'dev' } else { 'latest' }
}
if ($env:PRERELEASE -eq 'true' -and $DockerTag -ne 'dev') {
throw "This build was flagged prerelease, so it must publish to the 'dev' tag, not '$DockerTag'. Re-run with docker_tag=dev or rebuild without the prerelease flag."
}
New-Item -Path ./payload -ItemType Directory -Force | Out-Null
$BuildRids = $env:SERVER_RIDS.Split(',')
$Platforms = [System.Collections.Generic.List[string]]::new()
foreach ($Rid in $ArchByRid.Keys) {
if ($BuildRids -notcontains $Rid) {
continue
}
$Arch = $ArchByRid[$Rid]
$Source = "./server-publish/$Rid"
if (-not (Test-Path -PathType Container $Source)) {
throw "'$Source' is listed in the build metadata but is missing from the Server artifact."
}
Copy-Item -Path $Source -Destination "./payload/$Arch" -Recurse -Force
$Platforms.Add("linux/$Arch")
Write-Host "linux/$Arch <- $Rid"
}
if ($Platforms.Count -eq 0) {
throw "This build produced no Linux runtime ($env:SERVER_RIDS), so there is nothing to put in a container image."
}
Add-Content -Path $env:GITHUB_OUTPUT -Value "version=$env:RELEASE_VERSION"
Add-Content -Path $env:GITHUB_OUTPUT -Value "platforms=$($Platforms -join ',')"
Add-Content -Path $env:GITHUB_OUTPUT -Value "docker_tag=$DockerTag"
Write-Host "Version: $env:RELEASE_VERSION"
Write-Host "Platforms: $($Platforms -join ',')"
Write-Host "Docker tag: $DockerTag"
- name: Create Dockerfile
shell: pwsh
run: |
@'
FROM mcr.microsoft.com/dotnet/aspnet:10.0
RUN apt update
RUN apt -y install curl
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# Each runtime is unpacked under its Docker architecture name.
ARG TARGETARCH
COPY ${TARGETARCH}/ /app
ENTRYPOINT ["dotnet", "ControlR.Web.Server.dll"]
HEALTHCHECK \
CMD curl -f http://localhost:8080/health || exit 1
'@ | Set-Content -Path ./payload/Dockerfile -Encoding utf8
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
# Harmless when the runner's own architecture is the only target.
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PAT }}
- name: Build and Push Image
uses: docker/build-push-action@v7
with:
context: ./payload
file: ./payload/Dockerfile
push: true
platforms: ${{ steps.payload.outputs.platforms }}
tags: |
bitbound/controlr:${{ steps.payload.outputs.docker_tag }}
bitbound/controlr:${{ steps.payload.outputs.version }}
- name: Image Published
shell: pwsh
env:
VERSION: ${{ steps.payload.outputs.version }}
PLATFORMS: ${{ steps.payload.outputs.platforms }}
DOCKER_TAG: ${{ steps.payload.outputs.docker_tag }}
run: |
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "## Publish Summary"
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "**Version:** $env:VERSION"
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "**Platforms:** $env:PLATFORMS"
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "**Tags:**"
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- bitbound/controlr:$env:DOCKER_TAG"
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "- bitbound/controlr:$env:VERSION"