Skip to main content

CI/CD Pipelines

Overview

This article documents the continuous integration and deployment pipeline for the uzdavines.ai platform. The pipeline uses GitHub Actions to validate pull requests, build applications, and trigger deployments to Render -- ensuring that only tested, passing code reaches production.

  • Why it matters: Automated CI/CD eliminates manual deployment steps, reduces human error, and provides a consistent, auditable path from code change to production. Path-filtered workflows ensure that only affected applications are rebuilt, saving time and resources.
  • What you will learn: The end-to-end pipeline flow from PR validation through production deployment, how path filtering targets specific applications, the relationship between GitHub Actions workflows and Render deploy hooks, and key design decisions like disabled auto-deploy and squash merge strategy.

Deployments are fully automated through GitHub Actions. Each application has two workflows: a PR check (validation on pull requests) and a deploy (production deployment on merge to main).

Pipeline Flow

Developer pushes branch

v
Open Pull Request

v
pr-check-{app}.yml <-- Runs on PR to main
┌─────────────────┐ (path-filtered)
│ Checkout code │
│ Setup Node 20 │
│ npm ci │
│ npm run build │
└─────────────────┘

v
PR approved + merged

v
deploy-{app}.yml <-- Runs on push to main
┌─────────────────┐ (path-filtered)
│ Checkout code │
│ Setup Node 20 │
│ npm ci │
│ npm run build │
│ curl deploy hook│ ───────> Render builds + deploys
└─────────────────┘

Path Filtering

Each workflow only triggers when relevant files change:

WorkflowTriggers On
deploy-homepage.ymlapps/homepage/**, packages/shared/**
deploy-docs.ymlapps/docs/**, packages/shared/**
deploy-api.ymlapps/api/**, packages/shared/**

Changes to packages/shared/** trigger all workflows since shared code affects every application.

Workflow Files

PR Check

Validates that the application builds successfully. Runs on pull requests targeting main.

name: PR Check — {App}

on:
pull_request:
branches: [main]
paths:
- "apps/{app}/**"
- "packages/shared/**"

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- run: npm ci
- run: npm run build --workspace=apps/{app}

Production Deploy

Builds the application and triggers a Render deploy hook. Runs when changes are pushed to main.

name: Deploy {App}

on:
push:
branches: [main]
paths:
- "apps/{app}/**"
- "packages/shared/**"

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"
cache-dependency-path: package-lock.json
- run: npm ci
- run: npm run build --workspace=apps/{app}
- name: Trigger Render Deploy
run: curl -X POST "${{ secrets.RENDER_{APP}_DEPLOY_HOOK }}"

All workflows install from the single root package-lock.json with one npm ci at the repo root, then build the target workspace with npm run build --workspace=apps/{app}. There are no per-app lockfiles — the monorepo uses npm workspaces, so dependencies hoist to the root and resolve from that one lockfile.

Render Deploy Hooks

Deploy hooks are stored as GitHub repository secrets:

Secret NameService
RENDER_HOMEPAGE_DEPLOY_HOOKHomepage (Web Service)
RENDER_DOCS_DEPLOY_HOOKDocs (Static Site)
RENDER_API_DEPLOY_HOOKAPI (Web Service)

When the deploy hook is called, Render pulls the latest code from the repository and runs its own build process.

Render Build Configuration

When a deploy hook fires, Render pulls the repo and runs its own build. Because this is an npm-workspaces monorepo with a single root package-lock.json (no per-app lockfiles), the install resolves the hoisted dependency tree from that one lockfile whether the service builds from the repo root or from an apps/<app> Root Directory.

ApplicationWorkspace (build tool)Output
Homepageapps/homepage (next build).next/
Docsapps/docs (docusaurus build)build/
APIapps/api (tsc)dist/

Key Design Decisions

  • Render auto-deploy is disabled — GitHub Actions controls all deployments for consistency and visibility
  • Path filtering prevents unnecessary builds when unrelated apps change
  • Squash merge keeps main history clean with one commit per feature
  • onBrokenLinks: 'throw' in Docusaurus catches broken documentation links at build time, preventing deployment of broken docs