Skip to content

CI/CD

The Docker image is built and pushed automatically by a GitHub Actions workflow on every qualifying push to main.

Workflow File

The workflow is defined in .github/workflows/build.yml.

Triggers

Push to main

The workflow runs on every push to the main branch, except when the push only modifies files in the ignored paths:

paths-ignore:
  - 'workflows/**'
  - 'backend/**'
  - 'frontend/**'
  - 'catalogs/**'
  - 'chrome-extension/**'
  - 'version.json'
  - '*.md'
  - 'docs/**'
  - 'mkdocs.yml'

Safe to push without triggering a build:

Path Content
backend/** Python backend code
frontend/** HTML/JS/CSS frontend
catalogs/** Model, LoRA, and LLM catalogs
workflows/** Workflow definitions and manifests
chrome-extension/** Chrome browser extension
version.json Version metadata
*.md Markdown files (README, etc.)
docs/** Documentation site
mkdocs.yml MkDocs configuration

These paths are excluded because their content is delivered via the git-based update mechanism at runtime, not baked into the Docker image.

Will trigger a build:

Path Content
docker/* Dockerfile, start.sh, bootstrap.py, nodes.txt, install_nodes.sh, configure.sh
.github/workflows/* CI/CD workflow definitions

Manual Dispatch

The workflow also supports workflow_dispatch, allowing manual triggers from the GitHub Actions UI. This is useful for rebuilding the image after upstream dependency changes (e.g., a new PyTorch release) without modifying any files.

Image Registry

Images are pushed to the GitHub Container Registry (GHCR):

ghcr.io/diego-devita/comfyui-studio

Tags

Every build produces two tags:

Tag Format Example Purpose
latest Fixed ghcr.io/diego-devita/comfyui-studio:latest Always points to the most recent build from main
sha-<commit> Per-commit ghcr.io/diego-devita/comfyui-studio:sha-a1b2c3d Immutable reference to a specific build

The latest tag is only applied when building from the default branch (main). The SHA tag uses the short commit hash.

Tags and labels are generated by the docker/metadata-action@v5 action:

tags: |
  type=raw,value=latest,enable={{is_default_branch}}
  type=sha,prefix=sha-,format=short

Build Configuration

Build Arguments

The workflow passes build arguments to the Dockerfile, sourced from GitHub Actions repository variables with fallback defaults:

build-args: |
  CUDA_VERSION=${{ vars.CUDA_VERSION || '12.8.1' }}
  PYTORCH_INDEX=${{ vars.PYTORCH_INDEX || 'cu128' }}
  PYTHON_VERSION=${{ vars.PYTHON_VERSION || '3.12' }}
  ENABLE_SAGE_ATTENTION=${{ vars.ENABLE_SAGE_ATTENTION || 'true' }}
  ENABLE_FLASH_ATTENTION=${{ vars.ENABLE_FLASH_ATTENTION || 'false' }}
Variable Default Notes
CUDA_VERSION 12.8.1 Override via GitHub repo variable vars.CUDA_VERSION
PYTORCH_INDEX cu128 Override via GitHub repo variable vars.PYTORCH_INDEX
PYTHON_VERSION 3.12 Override via GitHub repo variable vars.PYTHON_VERSION
ENABLE_SAGE_ATTENTION true Override via GitHub repo variable vars.ENABLE_SAGE_ATTENTION
ENABLE_FLASH_ATTENTION false Defaults to false in CI

FlashAttention defaults to false in CI

The Dockerfile default for ENABLE_FLASH_ATTENTION is true, but the CI workflow overrides it to false. FlashAttention builds from source and requires a GPU during compilation for best results. GitHub Actions runners do not have GPUs, so the source build would either fail or produce suboptimal binaries. Users who need FlashAttention should either build locally or set the ENABLE_FLASH_ATTENTION repository variable to true.

ENABLE_LLM and LLAMA_CPP_VERSION

The ENABLE_LLM and LLAMA_CPP_VERSION build arguments are not explicitly set in the workflow, so they use the Dockerfile defaults (true and b8505 respectively). llama-server is compiled in every CI build.

Configuring via Repository Variables

To change the build configuration without modifying the workflow file:

  1. Go to the GitHub repository Settings
  2. Navigate to Secrets and variables > Actions > Variables
  3. Create or update a repository variable (e.g., CUDA_VERSION, PYTORCH_INDEX)

The workflow reads these at build time via ${{ vars.VARIABLE_NAME }}. If the variable is not set, the fallback default is used.

Cache Strategy

Registry Cache

The workflow uses Docker Buildx with registry-based caching:

cache-from: type=registry,ref=ghcr.io/diego-devita/comfyui-studio:cache
cache-to: type=registry,ref=ghcr.io/diego-devita/comfyui-studio:cache,mode=max

A dedicated :cache tag on GHCR stores the layer cache. This is used instead of GitHub Actions cache (GHA cache) for a specific reason:

Why registry cache instead of GHA cache

GitHub Actions cache has a 10 GB limit per repository. The ComfyUI Studio image has multiple large layers -- PyTorch wheels, custom node installations, and especially the llama-server compilation (~60 min). With GHA cache, the 10 GB limit was frequently exceeded, causing the llama-server layer to be evicted. Every subsequent build would recompile llama-server from scratch, adding ~60 minutes to every CI run.

Registry cache stores layers in the container registry (GHCR) which has no practical size limit. The :cache tag holds all layers with mode=max (cache every layer, not just the final image layers). This ensures the expensive llama-server compilation is cached and reused across builds.

Layer Ordering for Cache Efficiency

The Dockerfile is structured to maximize cache hits:

1. Base image + system packages        (changes rarely)
2. Python venv + PyTorch               (changes on CUDA/PyTorch version bump)
3. xformers                            (changes rarely)
4. SageAttention + Triton              (changes rarely)
5. FlashAttention                      (changes rarely)
6. FastAPI + backend dependencies      (changes rarely)
7. llama-server compilation            (changes on LLAMA_CPP_VERSION bump)
8. COPY nodes.txt + install_nodes.sh   (INVALIDATES all below on node change)
9. Custom node installation            (rebuilds when nodes.txt changes)
10. COPY bootstrap.py + start.sh       (changes rarely)

The key insight: COPY docker/production/nodes.txt at step 8 invalidates all subsequent layers. This is why llama-server (step 7) and all pip installs (steps 2-6) are placed above it. Adding a custom node to nodes.txt only rebuilds steps 8-10 (~15 minutes), not the full llama-server compilation (~60 minutes).

Workflow Steps

The complete workflow runs these steps:

  1. Checkout repository -- actions/checkout@v4
  2. Log in to GHCR -- authenticates with GITHUB_TOKEN (automatic, no secrets to configure)
  3. Extract metadata -- generates image tags and OCI labels
  4. Set up Docker Buildx -- enables BuildKit features (multi-platform, cache)
  5. Build and push -- builds the image, pushes to GHCR with both tags, updates cache

Permissions

The workflow requires:

permissions:
  contents: read    # Read repository files
  packages: write   # Push to GHCR

The GITHUB_TOKEN secret is automatically provided by GitHub Actions and requires no manual configuration.

Build Times

Typical CI build times on ubuntu-latest runners:

Scenario Time Notes
Full cold build ~85-115 min No cache, compiles everything including llama-server
Incremental (node change only) ~15-25 min llama-server and PyTorch cached
Incremental (no layer changes) ~2-5 min Everything cached, just tag update
llama-server recompile +~60 min Triggered by LLAMA_CPP_VERSION change
FlashAttention recompile +~20-30 min Triggered by enabling ENABLE_FLASH_ATTENTION

Pulling the Image

# Latest build
docker pull ghcr.io/diego-devita/comfyui-studio:latest

# Specific commit
docker pull ghcr.io/diego-devita/comfyui-studio:sha-a1b2c3d

The image is public. No authentication is required to pull.