Local CI Pipeline Emulation
The devx ci run command parses your GitHub Actions workflow files and executes them locally inside isolated Podman/Docker containers.
Instead of the painful "fix ci → push → wait 3 minutes → fail → repeat" loop, you can debug your entire CI pipeline locally in seconds.
Quick Start
# Run the default workflow interactively
devx ci run
# Run a specific workflow
devx ci run ci.yml
# Run only the test job
devx ci run ci.yml --job test
# Preview the execution plan without running
devx ci run ci.yml --dry-run
# JSON output for AI agent consumption
devx ci run ci.yml --jsonHow It Works
- Parses your
.github/workflows/*.ymlfiles natively — no third-party runners required. - Resolves
needs:job dependencies into a DAG, executing independent jobs in parallel (just like GitHub). - Expands
strategy.matrixinto concrete jobs (e.g., a 2×2 matrix produces 4 parallel containers). - Creates an isolated container per job, bind-mounting your project at
/workspace. - Executes each
run:block sequentially inside that container via your provider'sexec(e.g.docker exec,nerdctl exec,podman exec). - Substitutes
${{ env.VAR }},${{ secrets.VAR }}, and${{ matrix.VAR }}expressions.
What devx ci run Does NOT Do
INTENTIONAL LIMITATION
uses: actions are NOT executed. Third-party composite and JavaScript actions like actions/setup-go, actions/upload-artifact, or golangci/golangci-lint-action are skipped with a visible warning.
This is a deliberate design decision. Emulating uses: faithfully is why nektos/act is 50,000+ lines of code and still struggles with environment parity. We trade completeness for reliability — the 80% of CI logic that lives in run: blocks is what developers actually need to debug locally.
Workaround: If a uses: action is critical for your local debugging, add the equivalent shell commands directly to a run: block in your workflow. For example, instead of relying on actions/setup-go, ensure Go is installed in your container image.
Supported Features
| Feature | Status |
|---|---|
run: shell blocks | ✅ Full support |
strategy.matrix expansion | ✅ Including include/exclude |
needs: job dependencies (DAG) | ✅ Parallel tiers |
env: at workflow/job/step | ✅ Full merge chain |
if: conditionals | ✅ Simple equality/inequality; complex expressions fail-open |
${{ secrets.X }} | ✅ Injected from devx Vault providers |
${{ matrix.X }} | ✅ From expanded matrix |
${{ env.X }} | ✅ From merged environment |
${{ github.* }} | ⚠️ Stubbed (e.g., event_name → "push") |
${{ runner.* }} | ⚠️ Stubbed (e.g., os → "Linux") |
working-directory: | ✅ Per-step |
shell: | ✅ bash/sh |
continue-on-error: | ✅ Step-level |
timeout-minutes: | ✅ Step-level |
uses: actions | ❌ Skipped with warning |
${{ steps.id.outputs.X }} | ❌ Not supported |
services: containers | 🔜 Planned |
Flags
--job Run only specific job(s) by name (comma-separated)
--image Override the container image (default: auto-detect from devcontainer.json)
--runtime Container runtime: podman, docker, or nerdctl (default: auto-detected)
--json Structured JSON output
--dry-run Show execution plan without creating containers
-y Non-interactive mode (auto-select first workflow)Container Image Resolution
devx ci run resolves the container image in this order:
--imageflag — if provided, always used.devcontainer.json— if found in the project, uses the declaredimage.ubuntu:latest— fallback with a warning that tools may be missing.
Parallel Output
When matrix jobs run in parallel, output uses Docker Compose-style prefixed streaming:
build·dar·amd │ go build -ldflags="-s -w" -o devx .
build·dar·arm │ go build -ldflags="-s -w" -o devx .
build·lin·amd │ go build -ldflags="-s -w" -o devx .
build·lin·arm │ go build -ldflags="-s -w" -o devx .Each job gets a unique color-coded prefix. Lines are guaranteed not to interleave mid-line.
Comparison vs nektos/act
devx ci run | nektos/act | |
|---|---|---|
run: blocks | ✅ | ✅ |
uses: actions | ❌ Skipped | ⚠️ Partial (many break) |
| Matrix parallelism | ✅ Real goroutines | ❌ Sequential |
| Image | Auto-detect or custom | Requires massive 20GB runner image |
| Secret injection | Native devx Vault | Manual .secrets file |
| Setup complexity | Zero (uses existing Podman) | Requires Docker + large images |
| Codebase size | ~800 lines | 50,000+ lines |
