Last Updated: February 22, 2026 - Added SBOM generation (syft), GitHub attestation, package changelog & build history
This guide covers the automated workflows and actions used for container management in our production-ready CI/CD system.
- 8 Workflows: All active and properly integrated
- 7 Actions: All used across workflows
- Core Scripts: Fully integrated with automation
- Make Script: All functions utilized by workflows
- Recent: SBOM generation (syft) + GitHub attestation, package changelog & build history, recreate-manifests workflow
Our GitHub Actions system uses a hybrid approach combining direct workflow calls for upstream changes with GitHub's native path filtering for code changes:
graph TD
A[upstream-monitor.yaml<br/>📅 Schedule: 6AM UTC daily] -->|version changes| B[Matrix Strategy<br/>🔄 One call per container]
B -->|workflow_call| C[auto-build.yaml<br/>🏗️ Build & Push]
C -->|workflow_call| D[update-dashboard.yaml<br/>📊 GitHub Pages]
E[Code Changes<br/>📝 Push/PR] -->|path filtering| C
F[validate-version-scripts.yaml<br/>🧪 Quality Assurance] --> G[Standalone Testing]
A --> H[check-upstream-versions<br/>📋 Version Detection]
A --> I[update-version<br/>📝 File Updates]
A --> J[close-duplicate-prs<br/>🔄 PR Management]
C --> K[detect-containers<br/>🔍 Change Detection]
C --> L[build-container<br/>🏗️ Build & Push]
C --> M[setup-github-cli<br/>⚙️ CLI Config]
F --> H
style A fill:#e1f5fe
style C fill:#f3e5f5
style D fill:#e8f5e8
style F fill:#fff3e0
Key Principles:
- 🔄 Single Source of Truth: upstream-monitor is the only scheduled workflow
- ⚡ Optimized Flow: Removed redundant version checking in build-container
- 🎯 Hybrid Triggers: workflow_call for upstream changes, path filtering for code changes
- 📋 Matrix Strategy: upstream-monitor calls auto-build once per updated container
- 🛡️ Clean Separation: Each workflow has distinct, focused responsibilities
- 🚀 Force Rebuild Trust: build-container trusts upstream-monitor decisions
- Force Rebuild: Upstream changes use force_rebuild to ensure containers are updated
The core scheduler that monitors upstream sources and initiates the entire automation pipeline.
Status: ✅ Recently Fixed - JSON parsing errors resolved, matrix strategy optimized
Triggers:
- 🕕 Schedule: 6 AM UTC daily (ONLY workflow with schedule triggers)
- ⚙️ Manual:
gh workflow run upstream-monitor.yaml
Responsibilities:
- 🔍 Monitor upstream software versions using
check-upstream-versionsaction - 📝 Create PRs for version updates using
update-versionaction - 🔄 Manage duplicate PR cleanup using
close-duplicate-prsaction - 🚀 Trigger auto-build directly using optimized matrix strategy (one call per container)
- 🔀 Auto-merge minor updates using
gh pr checks --watchto poll CI status before merging with standardGITHUB_TOKEN(no elevated permissions needed —enforce_admins: falseon master)
Key Inputs:
# Check specific container with debug
gh workflow run upstream-monitor.yaml \
--field container=wordpress \
--field debug=trueWorkflow Chain:
graph LR
A[Schedule/Manual] --> B[check-upstream-versions]
B --> C[Matrix: create-update-prs]
C --> D[update-version + close-duplicate-prs]
D --> E[Create PRs + Auto-merge minor]
E --> F[Matrix: trigger-auto-build]
F --> G[auto-build.yaml]
- Closes duplicate PRs using
close-duplicate-prsaction - Triggers auto-build using matrix strategy (one call per container)
Builds and pushes containers when triggered by upstream monitor or code changes.
Triggers:
- workflow_call with matrix: From upstream-monitor (one call per updated container)
- Push/PR Events: GitHub's native path filtering for container file changes
- Manual:
gh workflow run auto-build.yaml
Pipeline stages:
detect-containers— Smart change detection via git diff or force inputcache-base-images— Cache Docker Hub base images to GHCR (avoids rate limits)build-extensions— Build PostgreSQL extension images (if postgres detected)build-and-push— Multi-platform builds (native amd64 + arm64 runners) + SBOM generation + GitHub attestationcreate-manifest— Multi-arch manifest lists (GHCR primary, Docker Hub via cross-registry from GHCR sources)cache-lineage— Cache.build-lineage/JSON artifacts + process SBOMs (changelog, build history)update-dashboard— Trigger dashboard regeneration
Features:
- Native multi-architecture builds (separate amd64/arm64 runners, no QEMU)
- Hybrid detection: Matrix for upstream changes, path filtering for code changes
- Registry push automation via
build-containeraction - Docker Hub manifests created using GHCR images as cross-registry sources
- Build lineage tracking (base image digest, build args, timestamps)
- SBOM generation via syft (SPDX JSON format, amd64 only — packages identical across arches)
- GitHub attestation via
actions/attest-sbom(Sigstore-signed supply chain compliance) - Package changelog — inter-version package diffs (added/removed/updated)
- Build history — last 10 builds per variant with package counts
Scoped Build Inputs (optional):
scope_versions: Comma-separated major versions (e.g.,"18"). Empty = all.scope_flavors: Comma-separated flavors (e.g.,"distributed,full"). Empty = all.scope_extensions: Comma-separated extensions (e.g.,"citus"). Empty = all.
When set, only matching builds are included in the matrix. Used automatically by upstream-monitor to avoid unnecessary rebuilds.
Usage:
# Build all containers manually
gh workflow run auto-build.yaml
# Force rebuild specific container
gh workflow run auto-build.yaml \
--field container=wordpress \
--field force_rebuild=true
# Rebuild containers without recompiling extensions (faster)
gh workflow run auto-build.yaml \
--field container=postgres \
--field force_rebuild=true \
--field skip_extensions=true
# Scoped build: only PG 18 vector+full variants
gh workflow run auto-build.yaml \
--field container=postgres \
--field force_rebuild=true \
--field scope_versions=18 \
--field scope_flavors=vector,full
# Scoped build: rebuild only citus-affected flavors
gh workflow run auto-build.yaml \
--field container=postgres \
--field force_rebuild=true \
--field scope_flavors=distributed,full \
--field scope_extensions=citusRecreates multi-arch manifest lists without rebuilding containers. Use when manifests need to be regenerated (e.g., after a manifest fix, or to sync Docker Hub with GHCR).
Triggers:
- Manual only:
gh workflow run recreate-manifests.yaml
Inputs:
container(optional): Specific container, or all if emptyregistry:both(default),ghcr, ordockerhub
Usage:
# Recreate Docker Hub manifests for all containers
gh workflow run recreate-manifests.yaml -f registry=dockerhub
# Recreate all manifests for postgres only
gh workflow run recreate-manifests.yaml -f container=postgres
# Recreate both GHCR + Docker Hub for all
gh workflow run recreate-manifests.yamlUpdates the repository dashboard and GitHub Pages after successful builds.
Triggers:
- workflow_call: From
auto-build.yaml(primary trigger) - Manual: For dashboard-only updates
Responsibilities:
- Generate container status dashboard
- Deploy updates to GitHub Pages
- Maintain build history
Validates all version.sh scripts for functionality and standards compliance.
Triggers:
- Push/PR: Changes to version.sh files
- Manual: For testing and validation
Local Testing:
./validate-version-scripts.shOutputs:
- Container update summary
- Pull requests for version updates
- Automatic build triggers
Builds and pushes containers when changes are detected.
Triggers:
- Push to main/master (affecting container files)
- Pull requests
- workflow_call (from upstream-monitor)
- Manual dispatch
Features:
- Multi-architecture builds (amd64, arm64)
- Smart change detection
- Registry push automation
- Build retry logic
Usage:
# Build all containers
gh workflow run auto-build.yaml
# Force rebuild specific container
gh workflow run auto-build.yaml \
--field container=wordpress \
--field force_rebuild=trueValidates all version.sh scripts for functionality and standards compliance.
Triggers:
- Changes to version.sh files
- Manual dispatch
Local Testing:
./validate-version-scripts.sh| Workflow | Actions Used | Usage Count | Trigger Type |
|---|---|---|---|
upstream-monitor.yaml |
check-upstream-versionscheck-dependency-versionsupdate-versionclose-duplicate-prs |
4 actions | Schedule (6 AM UTC daily) Manual dispatch |
auto-build.yaml |
detect-containersbuild-containerdocker-login |
3 actions | workflow_call (from upstream-monitor) Push/PR events Manual dispatch |
recreate-manifests.yaml |
detect-containersdocker-login |
2 actions | Manual dispatch only |
update-dashboard.yaml |
None (uses scripts directly) | 0 actions | workflow_call (from auto-build) Manual dispatch |
validate-version-scripts.yaml |
check-upstream-versions |
1 action | Push/PR events Manual dispatch |
| Source Workflow | Triggers | Target Workflow | Method |
|---|---|---|---|
upstream-monitor.yaml |
→ | auto-build.yaml |
workflow_call |
auto-build.yaml |
→ | update-dashboard.yaml |
workflow_call |
| Action | Used By | Total Usage | Purpose |
|---|---|---|---|
check-upstream-versions |
upstream-monitor.yamlvalidate-version-scripts.yaml |
2x | Version detection and validation |
build-container |
auto-build.yaml |
1x | Container building and pushing |
detect-containers |
auto-build.yaml |
1x | Smart container change detection |
check-dependency-versions |
upstream-monitor.yaml |
1x | 3rd party dependency version monitoring |
close-duplicate-prs |
upstream-monitor.yaml |
1x | PR management and cleanup |
update-version |
upstream-monitor.yaml |
1x | Version file updates |
setup-github-cli |
auto-build.yaml |
1x | GitHub CLI configuration |
Status: ✅ All 7 actions are actively used across workflows
Used by: upstream-monitor.yaml, validate-version-scripts.yaml
Checks for upstream version updates across containers using individual version.sh scripts.
Inputs:
container(optional): Specific container to check
Outputs:
containers_with_updates: JSON array of containers needing updatesupdate_count: Number of containers with updatesversion_info: Detailed version information
Key Features:
- Supports both single container and bulk checking
- Integrates with shared helper functions
- Handles version script failures gracefully
Used by: upstream-monitor.yaml
Checks 3rd party dependency versions against upstream releases (GitHub Releases API, PyPI).
Inputs:
container(optional): Specific container to check
Outputs:
containers_with_dep_updates: JSON array of container names with updatesdep_update_count: Total number of dependency updates founddep_version_info: Full JSON output from orchestrator
Key Features:
- Config-driven: reads
dependency_sourcessection from each container's config.yaml - Supports GitHub releases, GitHub tags, and PyPI as sources
- Postgres special case: delegates to
version-extension.sh - Pre-flight validation warns about unregistered build_args (INV-05)
- Writes GITHUB_STEP_SUMMARY with update table
Used by: auto-build.yaml
Builds a specific container using the universal make script with enhanced error handling and security scanning.
Inputs:
container: Container name to buildforce_rebuild: Force rebuild even if up-to-datedockerhub_username,dockerhub_token,github_token: Registry credentialsscan_vulnerabilities: Enable Trivy security scanning (default:true)vulnerability_severity: Minimum severity to fail build -CRITICAL,HIGH,MEDIUM,LOW(default:CRITICAL)
Key Features:
- Delegates to universal
makescript with--baremode - Multi-registry support (Docker Hub + GHCR)
- Security scanning with Trivy (CVE detection)
- SARIF reports uploaded to GitHub Security tab
- Proper exit code handling
- Build status reporting
Security Scanning:
- Scans for OS and library vulnerabilities after build
- Blocks push on CRITICAL vulnerabilities by default
- Generates SARIF reports for GitHub Security integration
- PR builds show results without blocking
Build Caching (Registry-based):
- Uses GHCR as persistent cache backend (
ghcr.io/<owner>/<container>:buildcache) - Cache is read during build step (faster rebuilds)
- Cache is written during GHCR push (updated after each build)
- Docker Hub push uses read-only cache from GHCR
- Significantly reduces build times for unchanged layers
Used by: auto-build.yaml
Intelligently detects which containers need building based on file changes.
Outputs:
containers: JSON array of containers to buildcount: Number of containers detected
Logic:
- Analyzes git changes for container-specific files
- Supports forced rebuilds via input parameters
- Excludes non-container directories
Used by: upstream-monitor.yaml
Manages PR lifecycle by closing outdated or duplicate version update PRs.
Features:
- Prevents PR spam from multiple automation runs
- Maintains clean PR history
- Respects manual PRs and other automation
Used by: upstream-monitor.yaml
Updates container version files and creates commit for PR creation.
Features:
- Updates version.sh or metadata files
- Creates descriptive commit messages
- Integrates with PR creation workflow
Used by: auto-build.yaml
Configures GitHub CLI for workflow operations.
Features:
- Installs and configures
ghCLI - Sets up authentication tokens
- Provides consistent CLI environment
Checks for upstream version updates across containers.
Inputs:
container(optional): Specific container to check
Outputs:
containers_with_updates: JSON array of containers needing updatesupdate_count: Number of containers with updatesversion_info: Detailed version information
Checks 3rd party dependency versions against upstream releases.
Inputs:
container(optional): Specific container to check
Outputs:
containers_with_dep_updates: JSON array of container names with updatesdep_update_count: Total dependency updates founddep_version_info: Full JSON output
Builds a specific container with optimizations and error handling.
Inputs:
container: Container name to buildforce_rebuild: Force rebuild even if up-to-datedockerhub_username,dockerhub_token,github_token: Registry credentials
Features:
- Multi-architecture support
- Build caching
- Registry push automation
- Retry logic on failures
Intelligently detects which containers need building based on changes.
Outputs:
containers: JSON array of containers to buildcount: Number of containers detected
# PRIMARY: Monitor all containers for updates (triggers full pipeline)
gh workflow run upstream-monitor.yaml
# Check specific container with debug output
gh workflow run upstream-monitor.yaml \
--field container=ansible \
--field debug=true \
--field create_pr=false
# SECONDARY: Force rebuild all containers (manual builds only)
gh workflow run auto-build.yaml \
--field force_rebuild=true
# Build specific container manually
gh workflow run auto-build.yaml \
--field container=wordpress \
--field force_rebuild=true
# MANIFEST-ONLY: Recreate Docker Hub manifests without rebuilding
gh workflow run recreate-manifests.yaml -f registry=dockerhub
# Recreate manifests for specific container
gh workflow run recreate-manifests.yaml -f container=postgres
# UTILITY: Update dashboard only
gh workflow run update-dashboard.yaml
# TESTING: Validate version scripts
gh workflow run validate-version-scripts.yamlOur optimized architecture follows this pattern for reliable automation:
# upstream-monitor.yaml (Primary Entry Point)
name: Upstream Monitor
on:
schedule:
- cron: '0 6 * * *' # Only workflow with schedule (6 AM UTC daily)
workflow_dispatch:
inputs:
container: { type: string }
debug: { type: boolean, default: false }
create_pr: { type: boolean, default: true }
jobs:
# ... version checking jobs ...
trigger-builds:
if: success() && steps.check.outputs.update_count > 0
uses: ./.github/workflows/auto-build.yaml # workflow_call trigger
with:
container: ${{ inputs.container }}
secrets: inherit
---
# auto-build.yaml (Secondary - Triggered by Primary)
name: Auto Build
on:
workflow_call: # PRIMARY trigger from upstream-monitor
inputs:
container: { type: string }
push: # SECONDARY trigger for code changes
paths: ['*/Dockerfile', '*/version.sh']
workflow_dispatch: # MANUAL trigger
jobs:
# ... build jobs ...
update-dashboard:
if: success()
uses: ./.github/workflows/update-dashboard.yaml # workflow_call trigger
secrets: inheritname: Custom Container Workflow
on: workflow_dispatch
jobs:
check-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for updates
id: check
uses: ./.github/actions/check-upstream-versions
with:
container: wordpress
- name: Build if updated
if: steps.check.outputs.update_count > 0
uses: ./.github/actions/build-container
with:
container: wordpress
force_rebuild: false
github_token: ${{ secrets.GITHUB_TOKEN }}Workflows require specific GitHub token permissions based on their responsibilities:
permissions:
contents: write # Update version files and create commits
pull-requests: write # Create and manage version update PRs
actions: write # Trigger auto-build workflow via workflow_callpermissions:
contents: read # Read repository files for building
packages: write # Push to GHCR (ghcr.io registry)
actions: write # Trigger update-dashboard workflow via workflow_call
attestations: write # GitHub SBOM attestation via Sigstore
id-token: write # OIDC token for Sigstore signingpermissions:
contents: write # Update dashboard files and commit to gh-pages
pages: write # Deploy to GitHub Pages# Via GitHub Secrets (for Docker Hub):
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
# GitHub Container Registry uses GITHUB_TOKEN automaticallyenv:
# Build Optimization
DOCKER_BUILDKIT: 1 # Enable BuildKit for faster builds
BUILDX_NO_DEFAULT_ATTESTATIONS: 1 # Disable attestations for speed
# Automation Limits
MAX_OPEN_PRS_PER_CONTAINER: 2 # Limit concurrent PRs per container
PR_AUTO_CLOSE_DAYS: 7 # Auto-close stale PRs after 7 days
# Registry Configuration
REGISTRY_URL: ghcr.io # Primary registry (GHCR)
DOCKERHUB_REGISTRY: docker.io # Secondary registry (Docker Hub)# Set in Dockerfiles with flexible version handling
ARG VERSION=latest # Default to latest, override in builds
ENV CONTAINER_VERSION=${VERSION} # Runtime version visibilityScheduling Conflicts (RESOLVED):
- ✅ Fixed: Only
upstream-monitor.yamlhas schedule triggers - ✅ Fixed:
auto-build.yamlusesworkflow_callinstead of schedule - ✅ Fixed: Clean trigger chain eliminates race conditions
Workflow Not Triggering:
- Check if
upstream-monitor.yamlis the entry point (it should be) - Verify
workflow_calltriggers are properly configured - Ensure branch protection rules allow workflow_call
- Validate GitHub token permissions for cross-workflow triggers
Local Testing:
# Test version script directly
cd container-name
./version.sh # Check current version
./version.sh latest # Check upstream version
# Test with debug output
DEBUG=1 ./version.sh latestCommon Issues:
- API rate limits (use authenticated requests where possible)
- Network timeouts (check API response times)
- JSON parsing errors (validate with
jqlocally) - Invalid version formats (check regex patterns)
Debug Build Process:
# Test build locally using make script
./make build container-name
# Enable verbose output
DEBUG=1 ./make build container-name
# Test specific architecture
./make build container-name --platform linux/amd64Common Issues:
- Docker daemon connectivity
- Registry authentication (check DOCKERHUB_TOKEN)
- Build context size (optimize .dockerignore)
- Multi-architecture compatibility
Action Path Resolution:
- All actions use relative paths:
uses: ./.github/actions/action-name - Verify action directories exist and contain
action.yml - Check action inputs/outputs match workflow usage
Workflow Call Issues:
# Correct workflow_call syntax:
uses: ./.github/workflows/target-workflow.yaml
with:
input_name: value
secrets: inherit # Required for cross-workflow secretsBefore Optimization:
- Multiple workflows with overlapping schedules
- Race conditions between concurrent runs
- Redundant container detection logic
After Optimization:
- Single scheduled entry point (
upstream-monitor) - Sequential workflow execution via
workflow_call - Shared container detection and build logic
- Reduced GitHub Actions minutes usage
Multi-Architecture Strategy:
# Optimized build matrix
strategy:
matrix:
platform:
- linux/amd64
- linux/arm64
fail-fast: false # Continue building other architectures on failureBuild Caching:
# Leverage BuildKit cache mounts
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y package- Single Source of Truth: One workflow triggers the automation chain
- Proper Separation: Each workflow has distinct responsibilities
- Fail-Safe Operation: Workflows continue on non-critical errors
- Comprehensive Logging: All steps include descriptive names and summaries
- Reusability: Actions should work across multiple workflows
- Error Handling: Graceful failure with descriptive error messages
- Input Validation: Validate all inputs before processing
- Output Consistency: Standardized output formats (JSON for complex data)
- Minimal Permissions: Use least-privilege principle for workflow permissions
- Secret Management: Store credentials in GitHub Secrets, never in code
- Version Pinning: Use specific action versions (e.g.,
@v4) not@latest - Regular Audits: Review workflow logs and update patterns quarterly