CI/CD is an essential part of modern software development. It helps teams automatically test, build, and deploy applications whenever code changes.
In this guide, we’ll build an interview-ready understanding of CI/CD using Ruby on Rails API + React + PostgreSQL + GitHub Actions, then look at how CircleCI provides similar capabilities.
1. What Is CI/CD?
Continuous Integration (CI)
CI automatically validates code changes whenever a developer pushes code or opens a pull request.
Typical tasks include:
- Running RSpec tests.
- Running React component tests.
- Checking code quality with RuboCop and ESLint.
- Building the React application.
- Running security and dependency checks.
The goal is to detect problems before code reaches the main branch.
Continuous Delivery vs. Continuous Deployment
Continuous Delivery: Every change that passes the pipeline is ready to deploy, but production deployment may require manual approval.
Continuous Deployment: Every change that passes the required checks is automatically deployed to production.
A common workflow is:
Developer pushes code
|
v
GitHub Actions starts CI
|
v
Run tests and linting
|
v
Build React application
|
v
All checks pass?
/ \
No Yes
| |
Fail Merge to main
|
v
Deploy to staging
|
v
Smoke tests / approval
|
v
Deploy to production
⚙️ 2. What Is GitHub Actions?
GitHub Actions is GitHub’s automation platform. It executes workflows when configured events occur, such as a push, pull request, scheduled time, or manual trigger.
Important concepts:
| Concept | Meaning |
|---|---|
| Workflow | Complete automated process defined in YAML |
| Event | Trigger, such as push or pull_request |
| Job | Group of steps executed on a runner |
| Runner | Machine that executes a job |
| Step | Individual command or reusable action |
| Action | Reusable unit of automation |
| Secret | Sensitive value such as a deployment token |
| Artifact | File produced by a workflow, such as a build output or test report |
| Cache | Reusable dependency data that can speed up future runs |
A workflow lives inside:
.github/
workflows/
ci.yml
GitHub’s official guides explain workflow concepts, Ruby CI, and Node.js CI.
🏗️ 3. GitHub Actions for Rails + React
Assume this project structure:
rails_react_app/
├── app/
├── config/
├── spec/
├── Gemfile
├── .ruby-version
├── .github/
│ └── workflows/
│ └── ci.yml
└── frontend/
├── src/
├── package.json
├── package-lock.json
└── .nvmrc
We’ll create two CI jobs:
rails-test: Prepare PostgreSQL, run database setup, RSpec, and RuboCop.frontend-test: Install JavaScript dependencies, run frontend tests, and build React.
GitHub Actions can execute these independent jobs in parallel, reducing overall pipeline time.
Step 1: Create the workflow
Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
rails-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: rails_react_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d rails_react_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/rails_react_test
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Prepare database
run: bundle exec rails db:prepare
- name: Run RSpec
run: bundle exec rspec
- name: Run RuboCop
run: bundle exec rubocop
frontend-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v7
with:
node-version-file: frontend/.nvmrc
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Lint React code
run: npm run lint
- name: Test React components
run: npm run test:ci
- name: Build React
run: npm run build
This example assumes your Rails project uses RSpec, RuboCop, PostgreSQL, and a React frontend with the specified npm scripts. Configure the frontend’s test:ci script to run your test runner in CI mode, such as vitest run. Adjust the PostgreSQL and runtime versions to match your application.
Step 2: Understand what happens
When a pull request targets main:
- GitHub starts the workflow.
- Two isolated runner jobs start.
- The Rails job prepares PostgreSQL and runs backend checks.
- The frontend job installs dependencies, tests React, and produces a production build.
- If either job fails, the workflow fails.
Configure these jobs as required status checks in your repository’s branch protection or ruleset settings.
A successful workflow does not automatically mean that code is deployed. This configuration implements CI, not CD.
4. Caching and Artifacts
These two features serve different purposes.
Caching avoids repeatedly downloading dependencies. For example, bundler-cache: true handles Bundler dependency installation and caching, while setup-node can cache npm’s download cache.
Artifacts preserve files generated during a workflow. Examples include test reports, screenshots, coverage reports, or compiled application output. Use artifact upload/download actions when another job needs those files.
Do not confuse dependency caches with deployable release artifacts. For more detail, see GitHub’s caching guide and artifact guide.
5. How Do We Implement CD?
After CI passes and the code is merged into main, we can add a deployment job.
A simplified example:
deploy-staging:
needs: [rails-test, frontend-test]
if: >-
github.event_name == 'push' &&
github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Deploy application
run: ./script/deploy-staging.sh
The deployment script must implement the actual deployment for your platform. For example, it could deploy a container image to a container platform or invoke your hosting provider’s deployment tooling.
For production, configure a separate GitHub environment with restricted deployment branches and approval rules where appropriate.
Security best practices:
- Store credentials in GitHub Actions secrets or environment secrets, never in source code.
- Prefer OpenID Connect (OIDC) with short-lived cloud credentials when supported, rather than long-lived cloud access keys.
- Restrict workflow permissions to the minimum required.
- Pin third-party actions to reviewed commit SHAs in security-sensitive production workflows.
- Run database migrations as part of a controlled release process, and make migrations compatible with rolling deployments.
See GitHub Actions deployment security for OIDC and environment protection.
6. How Does CircleCI Fit In?
CircleCI is another CI/CD platform. It provides the same broad capabilities: executing tests, building applications, caching dependencies, coordinating jobs, and deploying successful builds.
The main difference is the configuration platform and its integration with your repository.
In GitHub Actions, workflow files live in .github/workflows/. In CircleCI, the primary configuration file is:
.circleci/config.yml
A simplified CircleCI configuration for the same Rails + React project looks like this:
version: 2.1
jobs:
test:
docker:
- image: cimg/ruby:3.3-node
- image: cimg/postgres:16.0
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: rails_react_test
environment:
RAILS_ENV: test
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/rails_react_test
steps:
- checkout
- run: bundle install
- run: bundle exec rails db:prepare
- run: bundle exec rspec
- run: bundle exec rubocop
- run:
name: Test and build React
command: |
cd frontend
npm ci
npm run lint
npm run test:ci
npm run build
workflows:
test:
jobs:
- test
This is a starter example. Match the Ruby and Node versions to your project, configure database readiness and dependency caching, and verify that the selected images meet your requirements. A production setup would typically separate backend and frontend jobs when parallel execution is beneficial.
How do we integrate CircleCI?
- Create a CircleCI account and connect your GitHub repository.
- Add
.circleci/config.yml. - Configure project permissions, environment variables, secrets, and required contexts.
- Run the pipeline and inspect failures in CircleCI’s job logs.
- Add deployment jobs that depend on successful test jobs and use protected deployment environments.
For more details, see the CircleCI configuration guide and JavaScript quickstart.
GitHub Actions vs. CircleCI: GitHub Actions is a natural starting point when your source code and pull requests already live on GitHub. CircleCI is also a strong option, especially when a team already uses its workflows, caching, test-splitting capabilities, and deployment integrations. You usually don’t need both platforms running the same pipeline unless there is a clear reason.
🎯 7. Questions to Remember
Q: What is the difference between CI and CD?
CI automatically builds and tests code changes. CD automates making validated changes ready for release or deploying them, depending on whether the process is continuous delivery or continuous deployment.
Q: Why use separate jobs for Rails and React?
They have different runtime dependencies and can run independently. Separate jobs allow parallel execution, clearer logs, and more targeted debugging.
Q: What happens if the tests fail?
The job fails, and dependent jobs such as deployment are skipped. Configure branch protection to prevent merging unless the required checks pass.
Q: How do you make a pipeline faster?
Cache dependencies, parallelize independent jobs, split large test suites, remove redundant work, and run expensive E2E tests selectively.
Q: How do you secure deployments?
Use least-privilege workflow permissions, protected environments, restricted secrets, reviewed workflow actions, and short-lived cloud credentials through OIDC where supported.
🎯 Final Recommendation
For a new Rails API + React application, start with GitHub Actions because it integrates directly into your repository and pull-request workflow.
Build the pipeline incrementally: lint and test → build → deploy to staging → smoke test → deploy to production. Add caching, parallelization, protected environments, security scans, and deployment approvals as your application’s requirements grow.
The goal of CI/CD is not simply to automate deployments. It is to make every release repeatable, testable, secure, and recoverable.