name: Publish # Builds the application image and pushes it to a container registry. # # push to main -> :main and :sha- # tag 1.2.3 -> :1.2.3, :1.2, :1 and :latest # pull request -> builds without pushing, so a broken Dockerfile is # caught before it can move a published tag # # Configure with repository variables and secrets: # # vars.REGISTRY required, e.g. registry.example.com # vars.IMAGE_NAME optional, defaults to this repository's owner/name # vars.REGISTRY_USER optional, defaults to the actor running the workflow # secrets.REGISTRY_TOKEN required to push # # Point REGISTRY at a host the runner reaches directly, without an intermediate # proxy that caps request bodies: a browser image has layers well over 100MB, # and such a proxy rejects them mid-push with `413 Payload Too Large`. # # If that host serves plain HTTP, the builder's Docker daemon also needs it in # `insecure-registries` — that is daemon configuration, not something a # workflow can set. on: push: branches: - main tags: # Glob, not regex: `[0-9]` is one digit and `*` is the rest, so these # match 1.2.3 and 1.2.3-rc1 while ignoring tags that are not versions. # A `+` here would be read as a literal plus. - '[0-9]*.[0-9]*.[0-9]*' - 'v[0-9]*.[0-9]*.[0-9]*' pull_request: paths: - 'Dockerfile' - 'package.json' - 'package-lock.json' - '.gitea/workflows/publish.yml' workflow_dispatch: jobs: build: name: Build and push runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check the runner can build images run: | if ! docker info >/dev/null 2>&1; then echo "No usable Docker daemon in the job container." >&2 exit 1 fi docker version --format 'client {{.Client.Version}} / server {{.Server.Version}} / arch {{.Server.Arch}}' # Images are built natively, so each carries the architecture of the # runner that built it. A runner of a different architecture joining the # pool would overwrite these tags with its own arch, at which point this # needs buildx and a manifest list. - name: Work out the tags id: meta env: REGISTRY: ${{ vars.REGISTRY }} IMAGE_NAME: ${{ vars.IMAGE_NAME }} run: | set -euo pipefail if [ -z "${REGISTRY}" ]; then echo "The REGISTRY repository variable is not set." >&2 echo "Set it to the registry host to publish to, e.g. registry.example.com" >&2 exit 1 fi image="${REGISTRY}/$(echo "${IMAGE_NAME:-${{ github.repository }}}" | tr '[:upper:]' '[:lower:]')" tags="" if [ "${{ github.ref_type }}" = "tag" ]; then version="${{ github.ref_name }}" version="${version#v}" tags="${version}" # Only a final release moves the rolling aliases; a prerelease is # published under its own exact version and nothing else. case "${version}" in *-*) ;; *) major="${version%%.*}" minor="${version%.*}" tags="${tags} ${minor} ${major} latest" ;; esac else tags="main sha-$(git rev-parse --short HEAD)" fi args="" for tag in ${tags}; do args="${args} --tag ${image}:${tag}"; done { echo "image=${image}" echo "tags=${tags}" echo "args=${args}" } >> "$GITHUB_OUTPUT" echo "Publishing ${image} as:${tags// /, :}" # The Actions task token is rejected by some registries, so pushing uses # a token that belongs to a real user. - name: Log in to the container registry if: github.event_name != 'pull_request' run: | if [ -z "${{ secrets.REGISTRY_TOKEN }}" ]; then echo "REGISTRY_TOKEN is not set." >&2 exit 1 fi if ! echo "${{ secrets.REGISTRY_TOKEN }}" \ | docker login "${{ vars.REGISTRY }}" -u "${{ vars.REGISTRY_USER || github.actor }}" --password-stdin then echo >&2 echo "If that failed with 'server gave HTTP response to HTTPS client', the" >&2 echo "registry is plain HTTP and the builder's Docker daemon has to be told" >&2 echo "to allow it: add ${{ vars.REGISTRY }} to insecure-registries in the" >&2 echo "daemon config on the runner. The workflow cannot configure that." >&2 exit 1 fi - name: Build run: docker build --pull ${{ steps.meta.outputs.args }} --file Dockerfile . - name: Push if: github.event_name != 'pull_request' run: | set -euo pipefail for tag in ${{ steps.meta.outputs.tags }}; do docker push "${{ steps.meta.outputs.image }}:${tag}" done echo "Published ${{ steps.meta.outputs.image }} as: ${{ steps.meta.outputs.tags }}" - name: Log out if: always() && github.event_name != 'pull_request' run: docker logout "${{ vars.REGISTRY }}" || true