Composable CI/CD pipeline system for GitHub Actions
gh-ci-assembler is a CLI tool that assembles modular GitHub Actions workflows from reusable packages. Instead of maintaining monolithic workflow files, you compose pipelines from multiple packages, each contributing jobs to predefined stages.
- Package-based composition — Combine multiple technology packages (Drupal, Redis, etc.) without conflicts
- Stage-based topology — Define pipeline stages once; packages contribute jobs to any stage
- Project customizations — Extend, replace, or disable package jobs without forking
- Native GitHub Actions syntax — Everything below the job level is standard GitHub Actions (GHA) YAML
- Automatic dependency management — Jobs within stages run in parallel; stages run sequentially
- Collision-free by design — Automatic job prefixing eliminates ID conflicts
go install github.com/sparkfabrik/github-ci-assembler/cmd/gh-ci-assembler@latest# Generate workflow from packages
gh-ci-assembler generate \
--conf configuration.yml \
--pkg pkg_base.yml \
--pkg pkg_drupal.yml \
--output .github/workflows/gh-ci-assembler.yml
# With project customizations
gh-ci-assembler generate \
--conf configuration.yml \
--pkg pkg_base.yml \
--pkg pkg_drupal.yml \
--project project.yml \
--output .github/workflows/gh-ci-assembler.yml
# Dry run (print to stdout)
gh-ci-assembler generate \
--conf configuration.yml \
--pkg pkg_base.yml \
--dry-runDefines workflow root keys and stage topology:
version: "1"
name: GitHub CI Assembler
on:
push: {}
pull_request: {}
defaults:
run:
shell: bash
env:
CI_ORCHESTRATOR: gh-ci-assembler
GLOBAL_TIMEOUT: "600"
permissions:
actions: read
contents: read
stages:
- build
- test
- deployEach package contributes jobs and can declare file-scoped env defaults merged into each package job (job.env wins on conflicts):
id: drupal
env:
PHP_VERSION: "8.2"
hooks:
build:
docker-php:
name: Build PHP container
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: docker build -t app:latest .
test:
phpunit:
name: PHPUnit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: vendor/bin/phpunitKey points:
idis required and must be unique across all packageshooksmaps stages to job definitions (native GHA syntax)- Root
name,on, anddefaultsare not allowed in packages - Source
needsvalues are preserved and merged with automatic dependencies
Customize package jobs per-project, with optional file-scoped env defaults:
env:
PROJECT_NAME: "acme"
permissions:
statuses: write
hooks:
build:
# Extend a package job (deep merge)
docker-php:
extend:
provided_by: drupal
env:
CUSTOM_VAR: "value"
needs: [custom-lint] # must reference output job IDs
# Add new project-specific job
custom-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Custom lint
run: npm run lint
test:
# Replace a package job entirely
phpunit:
replace:
provided_by: drupal
runs-on: ubuntu-latest
steps:
- name: Custom test
run: npm test
# Disable a package job
deploy-staging:
disable:
provided_by: drupalname, on, defaults, and root env are only allowed in configuration.yml.
Root permissions can be declared in configuration.yml, pkg_*.yml, and project.yml (deep-merged in assembly order).
env in pkg_*.yml and project.yml is file-scoped: it merges into jobs declared in that same file and does not contribute to root workflow env.
- Load configuration — Read workflow root keys (
name,on,defaults,env,permissions) and stage topology fromconfiguration.yml - Load packages — Parse each
--pkgfile in order - Load project — Parse
project.ymlif provided - Validate — Check IDs, stage references, forbidden root keys, directive targets
- Merge file-level env — Merge package/project root
envinto each job in that same file - Merge jobs — Apply extend/replace/disable operations
- Compute dependencies — Generate
needschains for sequential stages and merge explicit sourceneeds - Generate names — Create display names:
[stage] pkg-id · job-name - Render YAML — Write GitHub Actions workflow file with ordered root keys (
name,on,defaults,env,permissions,jobs)
Package jobs are automatically prefixed to prevent collisions:
Original: docker-php (in stage build, package drupal)
Generated ID: build--drupal--docker-php
Display name: [build] drupal · Build PHP container
Project jobs (no directive) are not prefixed:
Original: lighthouse (in stage test)
Generated ID: lighthouse
Display name: [test] Lighthouse audit
Generate a GitHub Actions workflow from configuration files.
gh-ci-assembler generate [flags]Flags:
--conf <file>— Configuration file (required)--pkg <file>— Package file (repeatable, order matters)--project <file>— Project customization file (optional)--output <file>— Output workflow file (required unless --dry-run)--dry-run— Print to stdout instead of writing file
Exit codes:
0— Success1— Validation or assembly error
See testdata/full-example/ for complete working examples:
configuration.yml— Workflow root keys + 4-stage pipelinepkg_base.yml— Base package placeholder jobpkg_drupal.yml— Drupal build/test/notify jobspkg_redis.yml— Redis build/test/notify/deploy jobsproject.yml— All customization operations (extend/replace/disable/new)golden/expected.yml— Generated workflow output
Full specification: specs/gh-ci-assembler.md (version 2.1.2-draft)
Key design principles:
- Native GHA below job level — No custom DSL; job properties are passed through as-is
- Explicit package identity —
idfield defines package contract, not filename - Linear stage topology — Jobs in stage N depend on all jobs in stage N-1
- Deep merge semantics — Kubernetes strategic merge patch rules for
extendoperations
Build:
go build ./...Test:
go test ./...Update golden files:
UPDATE_GOLDEN=1 go test ./...Releases are automated via release-please:
- Merge PRs to
mainusing conventional commit titles (enforced by CI) - release-please creates/updates a Release PR with version bump and changelog
- Merging the Release PR creates a GitHub Release with a git tag
- The release triggers GoReleaser, which builds cross-platform binaries and attaches them to the release
See CONTRIBUTING.md for commit conventions and details.
- Go 1.25.6 or later
- Dependencies managed via
go.mod
Copyright SparkFabrik. All rights reserved.
- Full specification:
specs/gh-ci-assembler.md - JSON schemas:
schemas/gh-ci-assembler-schemas.json - AI context:
AGENTS.md