GitHub Actions voor Rails in 2026: Een CI/CD Pipeline Die Écht Werkt
De meeste Rails CI-pipelines die ik overneem als fractional CTO zien er hetzelfde uit: traag, instabiel, en bij elkaar gehouden met plakband. Een build van 20 minuten die willekeurig faalt op systeemtests. Developers die met gekruiste vingers naar main pushen.
Dit is de pipeline die ik in 2026 op elk nieuw Rails-project deploy. Hij draait in minder dan 5 minuten, vangt echte problemen op, en deployt met vertrouwen.
De Complete Workflow
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- run: bundle exec rubocop --parallel
- run: bundle exec brakeman --no-pager
test:
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports: ["6379:6379"]
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
fail-fast: false
matrix:
ci_node: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Setup database
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
RAILS_ENV: test
run: bin/rails db:setup
- name: Run tests (split)
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
REDIS_URL: redis://localhost:6379/0
RAILS_ENV: test
CI_NODE_TOTAL: 4
CI_NODE_INDEX: ${{ matrix.ci_node }}
run: |
bundle exec rails test $(
find test -name "*_test.rb" | sort | \
awk "NR % $CI_NODE_TOTAL == $CI_NODE_INDEX"
)
deploy:
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
# Jouw deployment commando hier
# kamal deploy, cap production deploy, etc.
echo "Deploying..."
Laten we de belangrijkste beslissingen doorlopen.
Concurrency Control Bespaart Geld
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Als je drie commits snel achter elkaar pusht, heb je geen drie CI-runs nodig. Dit annuleert lopende runs voor dezelfde branch. Bij een druk team met 30+ PR’s per dag scheelt dit 30-40% op je Actions-rekening.
Eerst Linten, Dan Testen
De needs: lint dependency zorgt ervoor dat tests pas starten als linting slaagt. Waarom 4 parallelle runners verspillen aan een testsuite als er een Brakeman-waarschuwing is die de merge toch blokkeert?
Rubocop met --parallel gebruikt alle beschikbare cores. Op GitHub’s runners zijn dat er meestal 2, wat de linttijd ruwweg halveert.
Parallelle Test-Splitsing Zonder Betaalde Tools
De meeste guides wijzen je naar Knapsack Pro of CircleCI’s test splitting. Die werken prima, maar je krijgt 80% van het voordeel gratis:
find test -name "*_test.rb" | sort | \
awk "NR % $CI_NODE_TOTAL == $CI_NODE_INDEX"
Dit verdeelt testbestanden round-robin over nodes. Het is niet tijdsgebalanceerd zoals Knapsack, maar door te sorteren is de verdeling deterministisch. Met 4 nodes ga je typisch van 12 minuten naar 3-4 minuten.
Wanneer upgraden naar Knapsack Pro: Als je traagste node 2x langer duurt dan je snelste. Dan zijn je testbestanden te ongelijk voor round-robin.
Service Containers Goed Instellen
Een veelgemaakte fout: geen health checks instellen op service containers. Zonder die checks starten je tests voordat Postgres klaar is, en krijg je instabiele verbindingsfouten.
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
GitHub Actions wacht tot alle health checks slagen voordat steps uitgevoerd worden. Dit elimineert een hele categorie “werkt lokaal, faalt in CI” bugs.
De fail-fast: false Beslissing
strategy:
fail-fast: false
Standaard annuleert GitHub alle matrix-jobs als er één faalt. Dat klinkt efficiënt, maar het is verschrikkelijk voor debugging. Als node 2 faalt maar node 0 geannuleerd wordt, weet je niet of je één falende test hebt of twintig.
Zet fail-fast: false. Laat alle nodes afronden. Ken de volledige omvang van de schade.
Caching Die Écht Werkt
De ruby/setup-ruby action met bundler-cache: true regelt gem-caching automatisch. Schrijf geen eigen cache-keys voor bundler — de officiële action doet het beter en handelt cache-invalidatie op Gemfile.lock-wijzigingen af.
Voor Node.js assets (als je assets:precompile draait):
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
Systeemtests: Draai Ze Apart
Als je systeemtests hebt (Capybara + headless Chrome), draai ze in een aparte job:
system-test:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
- name: Systeemtests
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
RAILS_ENV: test
run: |
bin/rails db:setup
bin/rails test:system
- uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshots
path: tmp/screenshots/
De upload-artifact bij failure is cruciaal. Als een systeemtest faalt, krijg je de screenshot. Zonder debug je blind.
Branch Protection Rules
Een pipeline is zo goed als zijn handhaving. In je repo-instellingen:
- Vereis dat status checks slagen — selecteer de
lintentestjobs - Vereis dat branches up-to-date zijn — voorkomt het mergen van verouderde branches
- Vereis lineaire history — squash of rebase, nooit merge commits
Je pipeline zou ook zero-downtime migratiechecks moeten bevatten als onderdeel van de deploy-stap — strong_migrations draaien in CI vangt gevaarlijke migraties af voordat ze productie bereiken.
Dit voorkomt het “maar het slaagde op mijn branch” probleem waarbij twee PR’s individueel slagen maar breken wanneer ze gecombineerd worden.
Secrets Management
Gebruik geen repository secrets voor environment-specifieke config. Gebruik GitHub Environments:
deploy:
environment: production
steps:
- run: kamal deploy
Environments geven je:
- Verplichte reviewers — iemand moet productie-deploys goedkeuren
- Wachttijden — automatische vertraging voordat deploy start
- Deployment logs — wie deployde wat, wanneer
Wat Dit Oplevert
Met deze setup ziet een typische PR-cyclus er zo uit:
- Push → Lint draait (30 seconden)
- Lint slaagt → 4 test-nodes starten (3-4 minuten)
- Alles groen → Systeemtests draaien (2-3 minuten)
- Merge → Auto-deploy triggert
Totale tijd van push tot gedeployd: minder dan 8 minuten. Dat is snel genoeg dat developers niet van context wisselen terwijl ze wachten.
Combineer dit met feature flags voor riskante wijzigingen, en je deployment-vertrouwen gaat door het dak — zelfs op een vrijdagmiddag.
De pipeline is met opzet simpel. Elke slimme optimalisatie die ik uit CI-pipelines heb verwijderd, heeft ze betrouwbaarder gemaakt. Saaie CI is goede CI.
Veelgestelde Vragen
Hoe versnel ik een trage Rails CI-pipeline?
Begin met parallelle test-splitsing — verdeel testbestanden over 4 matrix-nodes met round-robin. Voeg bundler-cache: true toe voor gem-caching en draai linting vóór tests zodat je snel faalt op stijlproblemen. Deze drie wijzigingen brengen een pipeline van 15 minuten typisch terug naar minder dan 5 minuten. Als het nog steeds traag is, profileer je testsuite op trage individuele tests.
Moet ik GitHub Actions of CircleCI gebruiken voor Rails?
GitHub Actions is de betere standaardkeuze voor de meeste teams in 2026. Het is diep geïntegreerd met GitHub (branch protection, environments, deployments), de gratis tier is ruim, en de Actions marketplace dekt de meeste behoeften. CircleCI heeft nog voordelen in test splitting-intelligentie (Knapsack Pro integratie) en Docker layer caching, maar GitHub Actions verkleint die kloof elk jaar.
Hoe ga ik om met instabiele tests in CI?
Markeer eerst instabiele tests met een tag en volg ze. Draai niet gewoon de pipeline opnieuw — dat verbergt het probleem. Veelvoorkomende oorzaken: tijdsafhankelijke assertions, gedeelde database-state tussen tests, en systeemtests die niet wachten op asynchrone operaties. Gebruik fail-fast: false zodat je alle failures ziet, niet alleen de eerste. Los de hoofdoorzaak op in plaats van retries toe te voegen.
Is het veilig om automatisch te deployen vanuit CI bij merge naar main?
Ja, met waarborgen. Vereis dat CI status checks slagen, handhaaf branch protection rules, en gebruik GitHub Environments met verplichte reviewers voor productie-deploys. Voeg een wachttijd toe als je een afkoelperiode wilt. Deze setup zorgt ervoor dat elke merge intentioneel, getest en gereviewd is voordat het productie bereikt.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation