Operating principles for Reed's CI actions development and usage.
CI must reflect local development as closely as possible.
Environment drift between local and CI causes:
- "Works on my machine" failures
- Wasted debugging time
- False confidence in passing tests
- Different behavior in production
-
Use the same tools locally and in CI
- Local:
nix develop -c mix test - CI:
nix develop -c mix test - NOT Local:
nix develop→ CI:erlef/setup-beam
- Local:
-
Same dependency versions
- Commit
flake.lockandmix.lock - CI uses exact same locked versions
- No "latest" or floating versions
- Commit
-
Same environment
- Nix flake defines environment once
- Both local and CI source from same flake
- No CI-specific setup that can't run locally
-
Verifiable parity
- Can run CI commands locally:
nix develop -c mix test - Can replicate CI environment:
nix develop - No hidden CI configuration
- Can run CI commands locally:
Use when local development uses Nix:
nix-setup+nix-elixir-test+nix-elixir-quality- Runs
nix develop -c mix <command> - Perfect local/CI match
Use when local development doesn't use Nix:
elixir-setup+elixir-test+elixir-quality- Uses
erlef/setup-beam - Document exact versions in README
Use for transition or broad compatibility:
- Primary job: Nix (matches local)
- Secondary jobs: Matrix (version coverage)
- Best of both worlds
Only deviate when absolutely necessary:
-
OS differences (local: macOS, CI: Linux)
- Document in README
- Test both if critical
-
Performance optimizations (CI caching)
- Must not change behavior
- Must be reproducible locally
-
Security constraints (CI secrets)
- Use same secret mechanism locally (sops, 1Password)
- Document access method
❌ Don't:
- Use different Elixir/OTP versions in CI vs local
- Add CI-only dependencies
- Skip tests locally that run in CI
- Use
latesttags in CI - Configure CI-specific behavior
✅ Do:
- Run exact same commands locally and in CI
- Use Nix flakes for environment definition
- Commit lock files
- Test CI workflow locally before pushing
- Document environment setup
Before pushing CI changes, verify locally:
# Clone the repo fresh
cd /tmp
git clone <repo>
cd <repo>
# Run CI commands exactly
nix develop -c mix deps.get
nix develop -c mix test
nix develop -c mix format --check-formatted
nix develop -c mix credo --strict
# Should match CI results exactlyTemplates generated from this repo must:
- Include
flake.nixfor reproducible environments - Provide Nix-based CI workflow by default
- Document how to run CI commands locally
- Commit both
flake.lockandmix.lock
- All new workflows must follow local/CI parity
- Pull requests should include "tested locally" confirmation
- Document any necessary deviations
- Prefer rejecting features that break parity
steps:
- uses: actions/checkout@v4
- uses: reed/ci/actions/nix-setup@main
- uses: reed/ci/actions/nix-elixir-test@mainLocal: nix develop -c mix test ← Same command CI runs
jobs:
test-nix: # Primary: matches local
steps:
- uses: reed/ci/actions/nix-setup@main
- uses: reed/ci/actions/nix-elixir-test@main
test-matrix: # Secondary: version coverage
strategy:
matrix:
elixir: ["1.17", "1.18"]
steps:
- uses: reed/ci/actions/elixir-setup@main
- uses: reed/ci/actions/elixir-test@mainNix job matches local, matrix provides extra coverage.
# CI
- uses: erlef/setup-beam@v1
with:
elixir-version: "1.18"
# Local: nix develop (Elixir 1.17)Version mismatch breaks parity.
Actions should be small, focused, and composable.
Every action needs:
- Clear description
- Input/output documentation
- Usage example
- Branding (icon, color)
- Keep actions simple
- Prefer composite over JavaScript
- Use official actions when possible
- Enable caching by default
- Use parallel jobs
- Optimize for common case
- Local/CI parity (must have)
- Composability (should have)
- Performance (nice to have)
When in conflict, prioritize parity over performance.
Before merging workflow changes:
- Can run CI commands locally
- Uses same environment locally and in CI
- Lock files committed
- Documentation updated
- Examples provided
- Deviations documented and justified