Skip to main content

CI/CD Pipeline

GospeLib uses GitHub Actions for CI and ArgoCD for continuous deployment. Nx's affected commands ensure only changed projects are built and tested.

Pipeline Architecture

graph LR
PR["Push / PR"] --> Setup["setup<br/>(detect affected)"]
Setup --> Lint["lint<br/>(nx affected -t lint)"]
Setup --> TestJS["test-js<br/>(nx affected -t test)"]
Setup --> TestPy["test-python<br/>(pytest per service)"]
Setup --> TestGo["test-go<br/>(go test per service)"]
Lint & TestJS & TestPy & TestGo --> Build["build<br/>(Docker images)"]
Build --> Deploy["deploy<br/>(staging or prod)"]

CI Pipeline (All Branches + PRs)

Setup Job

Detects which projects were affected by the current change:

setup:
runs-on: ubuntu-latest
outputs:
affected: ${{ steps.affected.outputs.projects }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: nrwl/nx-set-shas@v4
- id: affected
run: echo "projects=$(pnpm nx show projects --affected --json)" >> $GITHUB_OUTPUT

Lint Job

lint:
needs: setup
steps:
- run: pnpm nx affected -t lint --parallel=4

Test Jobs

Tests run in parallel across three language ecosystems:

JobFrameworkInfrastructure
test-jsVitest (nx affected -t test)None
test-pythonpytest (per service)FalkorDB + PostgreSQL containers
test-gogo test -race (per service)None

Python tests spin up real database containers in CI:

test-python:
services:
falkordb:
image: falkordb/falkordb:latest
ports: ['6379:6379']
postgres:
image: pgvector/pgvector:pg16
env: { POSTGRES_DB: gospelib_test, POSTGRES_USER: test, POSTGRES_PASSWORD: test }
ports: ['5432:5432']

Each test job checks the affected output and only runs if its service was changed:

- name: Test content service
if: contains(fromJson(needs.setup.outputs.affected), 'content')
run: cd services/content && uv run pytest tests/ --cov -v

Build Job

After all tests pass, Docker images are built for affected services:

build:
needs: [lint, test-js, test-python, test-go]
steps:
- name: Build Docker images (affected services only)
run: |
for svc in gateway content auth billing ai notifications; do
if echo '${{ needs.setup.outputs.affected }}' | grep -q "$svc"; then
docker build -t gospelib-$svc:${{ github.sha }} services/$svc/
fi
done

CD Pipeline — Staging

Merges to main automatically deploy to staging via ArgoCD:

sequenceDiagram
participant Dev as Developer
participant GH as GitHub
participant CI as CI Pipeline
participant ECR
participant ArgoCD
participant K8s as Staging Cluster

Dev->>GH: Merge PR to main
GH->>CI: Trigger cd-staging.yml
CI->>CI: Build Docker images
CI->>ECR: Push gospelib-*:$SHA
CI->>GH: Update Kustomize image tags in repo
ArgoCD->>GH: Detect git change (poll)
ArgoCD->>K8s: Apply updated manifests
ArgoCD->>ArgoCD: Report health status

Key steps:

  1. Build and push Docker images to ECR
  2. Update Kustomize overlay image tags in the repo
  3. ArgoCD detects the git change and applies the new manifests
  4. Wait for ArgoCD to report healthy sync

CD Pipeline — Production

Production deployments are triggered by release tags:

main branch → auto deploy to staging + EAS preview builds
release/v*.*.* → create release PR → full test suite + E2E
→ merge → tag → deploy to production
→ EAS submit to App Store + Play Store

Mobile CI/CD (EAS Build)

Mobile builds are triggered on main merges via Expo EAS:

eas-build:
needs: test-js
if: github.ref == 'refs/heads/main'
steps:
- uses: expo/expo-github-action@v8
with: { token: ${{ secrets.EXPO_TOKEN }} }
- run: cd apps/mobile && eas build --platform all --profile preview --non-interactive

CI Environment

ToolVersionPurpose
Node.js22JS/TS ecosystem
pnpm9Package manager
Python3.12Python services
uvlatestPython package manager
Go1.23Go services
DockerlatestContainer builds

Nx Caching in CI

Nx caches build, test, lint, and typecheck results. With Nx Cloud, cache hits are shared across branches and CI runs:

env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

Release Strategy

Version tagging uses semantic versioning via Release Please:

  • 13 individually versioned components — all apps, services, and packages
  • Tag format: {component}/v{version} (e.g., gateway/v1.2.0)
  • Changelog sections: Features, Bug Fixes, Performance Improvements, Reverts, Documentation
  • Commit format: Conventional Commits enforced by commitlint