Skip to content

CI/CD Now Builds Multiple Images for CUDA Versions #7

CI/CD Now Builds Multiple Images for CUDA Versions

CI/CD Now Builds Multiple Images for CUDA Versions #7

# A workflow, which builds the ComfyUI Docker images and publishes them to the GitHub Container Registry
name: Build & Publish ComfyUI
# Configures this workflow to run when a tag was pushed to the repository that matches the pattern "v[0-9]+.[0-9]+.[0-9]+", which is a semantic
# versioning pattern; this token will be created when a new release is created; the release event cannot be used, because the docker/metadata-action
# action does not support the release event
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
# This workflow has two jobs: (1) the first job extracts the versions of Comfy UI, ComfyUI Manager, PyTorch, CUDA, and cuDNN from the Dockerfile;
# these are arguments for the Dockerfile and can be used to build multiple images with different versions of the dependencies; the versions extracted
# from the Dockerfile are the default versions, which are used when building the default ComfyUI Docker image that will be tagged with "latest"; the
# PyTorch version is then used to get all tags of the official PyTorch Docker image that match the specified PyTorch version; from these tags, the
# supported combinations of CUDA and cuDNN versions are extracted, which are then used to build multiple ComfyUI Docker images with different CUDA and
# cuDNN versions; the results of the first job is a matrix of version combinations; (2) the second job uses this matrix to build multiple ComfyUI
# Docker images with different combinations of CUDA and cuDNN versions and pushes them to the GitHub Container Registry
jobs:
# The "generate-version-combinations" job extracts the versions of ComfyUI, ComfyUI Manager, PyTorch, CUDA, and cuDNN from the Dockerfile, and
# generates a matrix of version combinations that is used in the next job to build multiple ComfyUI Docker images with different CUDA and cuDNN
# versions
generate-version-combinations:
# This job will run on a GitHub runner with the latest version of Ubuntu, which is a good default choice
runs-on: ubuntu-latest
# The first job outputs a matrix of version combinations that is used in the next job to build multiple ComfyUI Docker images with different
# CUDA and cuDNN versions
outputs:
version-combinations: ${{ steps.generate-version-combinations.outputs.version-combinations }}
# This job (1) checks out the repository, (2) installs Python, which is used in the next step, and (3) generates the combinations of ComfyUI,
# ComfyUI Manager, PyTorch CUDA, and cuDNN versions
steps:
# Checks out the repository so that the workflow can access the files in the repository
- name: Checkout repository
uses: actions/checkout@v6
# Installs Python, which is used to extract the versions of ComfyUI, ComfyUI Manager, PyTorch, CUDA and cuDNN from the Dockerfile using regular
# expressions, and to call the Docker Hub API to get the supported CUDA and cuDNN versions for the PyTorch Docker image with the specified
# PyTorch version
- name: Install Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
# Extracts the versions of ComfyUI, ComfyUI Manager, PyTorch, CUDA, and cuDNN from the Dockerfile using a Python script; the PyTorch version is
# then used to get the supported CUDA and cuDNN versions for the official PyTorch Docker image from the Docker Hub API; then a matrix of version
# combinations is generated, which is used in the next job to build multiple ComfyUI Docker images with different CUDA and cuDNN versions; the
# version combination that was extracted from the Dockerfile is marked as the default combination, which will be used to build the default
# ComfyUI Docker image that will be tagged with "latest"
- name: Generate Version Combinations
id: generate-version-combinations
shell: python
run: |
import json
import os
import re
import urllib.error
import urllib.parse
import urllib.request
def get_version(version_variable_name: str) -> str:
with open('source/Dockerfile', mode='r', encoding='utf-8') as dockerfile:
match = re.search(
rf'^ARG {version_variable_name}=v?([0-9\.-a-z]+)$',
dockerfile.read(),
re.MULTILINE
)
return match.group(1) if match else 'unknown'
def get_docker_hub_image_tags(namespace: str, repository: str, tag_prefix: str) -> list[str]:
namespace = urllib.parse.quote(namespace)
repository = urllib.parse.quote(repository)
tag_prefix = urllib.parse.quote(tag_prefix)
url = f'https://hub.docker.com/v2/namespaces/{namespace}/repositories/{repository}/tags?ordering=last_updated&name={tag_prefix}'
image_tags: list[str] = []
try:
with urllib.request.urlopen(url) as response:
if response.status != 200:
print(f'Error fetching tags from Docker Hub: HTTP {response.status}')
return image_tags
data = response.read().decode(response.headers.get_content_charset("utf-8"))
json_data = json.loads(data)
for result in json_data.get('results', []):
tag_name = result.get('name', '')
if tag_name.startswith(tag_prefix):
image_tags.append(tag_name)
except urllib.error.URLError as exception:
print(f'Error fetching tags from Docker Hub: {exception}')
return image_tags
def get_supported_cuda_and_cudnn_version_combinations(pytorch_version: str) -> list[tuple[str, str]]:
pytorch_image_tags = get_docker_hub_image_tags(namespace='pytorch', repository='pytorch', tag_prefix=f'{pytorch_version}-cuda')
supported_version_combinations: list[tuple[str, str]] = []
for tag in pytorch_image_tags:
match = re.search(r'^' + re.escape(pytorch_version) + r'-cuda([0-9\.]+)-cudnn([0-9\.]+)-runtime$', tag)
if match:
cuda_version = match.group(1)
cudnn_version = match.group(2)
supported_version_combinations.append((cuda_version, cudnn_version))
return supported_version_combinations
comfyui_version = get_version('COMFYUI_VERSION')
comfyui_manager_version = get_version('COMFYUI_MANAGER_VERSION')
pytorch_version = get_version('PYTORCH_VERSION')
default_cuda_version = get_version('CUDA_VERSION')
default_cudnn_version = get_version('CUDNN_VERSION')
version_combinations: list[dict[str, str | bool]] = []
supported_cuda_and_cudnn_version_combinations: list[tuple[str, str]] = get_supported_cuda_and_cudnn_version_combinations(pytorch_version)
for cuda_version, cudnn_version in supported_cuda_and_cudnn_version_combinations:
version_combinations.append({
'comfyui_version': comfyui_version,
'comfyui_manager_version': comfyui_manager_version,
'pytorch_version': pytorch_version,
'cuda_version': cuda_version,
'cudnn_version': cudnn_version,
'is_default': (cuda_version == default_cuda_version and cudnn_version == default_cudnn_version)
})
with open(os.environ['GITHUB_OUTPUT'], mode='a', encoding='utf-8') as output_file:
output_file.write(f'version_combinations={json.dumps({'include': version_combinations})}\n')
# The "build-and-publish" job builds the Docker images, and publishes them to the GitHub Container Registry
build-and-publish:
# This job will run on an Ubuntu GitHub runner, which is a good default choice and it comes with Docker pre-installed
runs-on: ubuntu-latest
# This job requires the version combinations that were generated in the previous job
needs: generate-version-combinations
# This job is run multiple times in parallel, once for each combination of ComfyUI, ComfyUI Manager, PyTorch, CUDA, and cuDNN versions; the
# combinations are provided by the "generate-version-combinations" job in the "version-combinations" output
strategy:
matrix: ${{ fromJson(needs.generate-version-combinations.outputs.version-combinations) }}
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job; the `contents: read` permission is required to check out the
# repository, the `packages: write` permission is required to publish Docker images to the GitHub Container Registry, the `attestations: write`
# permission is required to publish attestations to the GitHub Container Registry, and the `id-token: write` permission is required to generate
# OpenID Connect tokens to authenticate to the GitHub Container Registry
permissions:
contents: read
packages: write
attestations: write
id-token: write
# This job (1) checks out the repository, (2) logs in to the GitHub Container Registry registry, (3) creates the tags and labels for the Docker
# image, (4) builds the ComfyUI Docker image with the current combination of dependency versions and pushes it to the GitHub Container Registry,
# and (5) generates an attestation for the Docker image and pushes it to the GitHub Container Registry
steps:
# Checks out the repository so that the workflow can access the files in the repository
- name: Checkout Repository
uses: actions/checkout@v6
# Logs in to the GitHub Container Registry registry using the account of the user that triggered the workflow run and the GitHub token, which is
# an automatically generated secret that is usually only used to access the repository; the permissions defined above allow the token to also
# publish Docker images to the GitHub Container Registry; once published, the packages are scoped to the specified account
# here
- name: Log in to the GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Creates the name, tags, and labels for the ComfyUI Docker image; the metadata-action extracts information from the Git repository and the
# GitHub actions workflow; it produces an image name, a version number, tags, labels, annotations, and other metadata for the Docker image; the
# image name, tags, and labels can be customized using the "images", "tags", and "labels" inputs; the flavor input can be used to tell the
# metadata-action if it should automatically create a "latest" tag or not
- name: Create Tags & Labels for the Docker Image
id: docker-image-metadata
uses: docker/metadata-action@v5
with:
# A list of new-line-separated image names, which are used as base names for the tags; here, the image name is set to the GitHub Container
# Registry followed by the repository name (which includes the owner name); this will result in "ghcr.io/lecode-official/comfyui-docker"
images: ghcr.io/${{ github.repository }}
# The flavor input is used to tell the metadata-action if it should automatically create a "latest" tag or not; here, the "latest" tag is
# only created for the default combination of dependency versions, which was extracted from the Dockerfile
flavor: |
latest=${{ matrix.is_default }}
# A list of new-line-separated tags to add to the Docker image; the type defines which metadata is used to create the tag: "sha" will use
# the commit SHA and "semver" will use the version number extracted from the Git tag that triggered the workflow; the "pattern" defines how
# the tag is constructed: "{{version}}" is replaced with the full semantic version, "{{major}}" with the major version, and "{{minor}}" with
# the minor version; the "enable" condition defines if the tag should be created or not; here, the "enable" condition is used to disable the
# creation of certain tags for non-default combinations of dependency versions (all non-default combinations of dependency versions will be
# tagged with the PyTorch, CUDA and cuDNN versions, while the default combination will additionally be tagged with simpler tags not
# including the dependency versions)
#
# The following tags are only created for the default combination:
#
# - The SHA of the commit from which the ComfyUI Docker image was built
# - The major version of ComfyUI Docker (only if the major version is not "0")
# - The major version of ComfyUI Docker and the ComfyUI version (only if the major version is not "0")
# - The major version of ComfyUI Docker, the ComfyUI version, and the ComfyUI Manager version (only if the major version is not "0")
# - The major and minor version of ComfyUI Docker
# - The major and minor version of ComfyUI Docker, and the ComfyUI version
# - The major and minor version of ComfyUI Docker, the ComfyUI version, and the ComfyUI Manager version
# - The full semantic version of ComfyUI Docker
# - The full semantic version of ComfyUI Docker and the ComfyUI version
# - The full semantic version of ComfyUI Docker, the ComfyUI version, and the ComfyUI Manager version
#
# The following tags are created for all combinations:
#
# - The major version of ComfyUI Docker, ComfyUI version, ComfyUI Manager version, PyTorch version, CUDA version, and cuDNN version (only if
# the major version is not "0")
# - The major version of ComfyUI Docker, ComfyUI version, PyTorch version, CUDA version, and cuDNN version (only if the major version is not
# "0")
# - The major and minor version of ComfyUI Docker, ComfyUI version, ComfyUI Manager version, PyTorch version, CUDA version, and cuDNN
# version
# - The major and minor version of ComfyUI Docker, ComfyUI version, PyTorch version, CUDA version, and cuDNN version
# - The full semantic version of ComfyUI Docker, ComfyUI version, ComfyUI Manager version, PyTorch version, CUDA version, and cuDNN version
# - The full semantic version of ComfyUI Docker, ComfyUI version, PyTorch version, CUDA version, and cuDNN version
tags: |
type=sha,enable=${{ matrix.is_default }}
type=semver,pattern={{major}},enable=${{ matrix.is_default && !startsWith(github.ref, 'refs/tags/v0.') }}
type=semver,pattern={{major}}-comfyui-${{ matrix.comfyui_version }},enable=${{ matrix.is_default && !startsWith(github.ref, 'refs/tags/v0.') }}
type=semver,pattern={{major}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }},enable=${{ matrix.is_default && !startsWith(github.ref, 'refs/tags/v0.') }}
type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.is_default }}
type=semver,pattern={{major}}.{{minor}}-comfyui-${{ matrix.comfyui_version }},enable=${{ matrix.is_default }}
type=semver,pattern={{major}}.{{minor}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }},enable=${{ matrix.is_default }}
type=semver,pattern={{version}},enable=${{ matrix.is_default }}
type=semver,pattern={{version}}-comfyui-${{ matrix.comfyui_version }},enable=${{ matrix.is_default }}
type=semver,pattern={{version}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }},enable=${{ matrix.is_default }}
type=semver,pattern={{major}}-comfyui-${{ matrix.comfyui_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
type=semver,pattern={{major}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
type=semver,pattern={{major}}.{{minor}}-comfyui-${{ matrix.comfyui_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
type=semver,pattern={{major}}.{{minor}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
type=semver,pattern={{version}}-comfyui-${{ matrix.comfyui_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
type=semver,pattern={{version}}-comfyui-${{ matrix.comfyui_version }}-comfyui-manager-${{ matrix.comfyui_manager_version }}-pytorch-${{ matrix.pytorch_version }}-cuda-${{ matrix.cuda_version }}-cudnn-${{ matrix.cudnn_version }}
# A list of new-line-separated labels to add to the Docker image; here, two labels are added: one for the image title and one for the image
# authors; by default, the metadata-action will add most of the labels defined in the Open Container Initiative (OCI) Image Specification,
# but the authors label is not included and the default value for the title label is the repository name
labels: |
org.opencontainers.image.title=ComfyUI Docker
org.opencontainers.image.authors=David Neumann <[email protected]>
# Builds the Docker image for ComfyUI; if the build succeeds, it is pushed to the GitHub Container Registry; the "context" parameter specifies
# the build context, which is the directory that contains the Dockerfile; the tags and labels extracted in the previous step are used to tag
# and label the image
- name: Build and Push the Docker Image
id: build-and-push-docker-image
uses: docker/build-push-action@v6
with:
context: source
push: true
tags: ${{ steps.docker-image-metadata.outputs.tags }}
labels: ${{ steps.docker-image-metadata.outputs.labels }}
# Generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built; it increases supply chain
# security for people who consume the image
- name: Generate Artifact Attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build-and-push-docker-image.outputs.digest }}
push-to-registry: true