CI/CD Pipeline (Coolify era)
This document outlines our CI/CD pipeline for the ERP-Unlocked monorepo, which uses Docker Bake for efficient builds and GitHub Container Registry (GHCR) for image storage, with dual deployment targets: Coolify for Docker services and Cloudflare Pages for the marketing site.
Overview
Section titled “Overview”Our CI/CD pipeline follows these principles:
- Selective Builds: Only build and push Docker images for services that have changed
- Validation vs Deployment: PRs build for validation (no push), merges build and deploy
- Docker Bake: Consolidate all Docker build configurations in a single
docker-bake.hclfile - GHCR Integration: Store all images in GitHub Container Registry with proper tagging
- Dual Deployment Strategy:
- Docker Services (webapp, pdf-api, pdf-worker) → Coolify via GHCR images
- Marketing Site (static Astro) → Cloudflare Pages via separate deployment workflows
- GitFlow-inspired Branching: Use
main,staging,develop,feature/*,hotfix/*, andrelease/*branches
Deployment Architecture
Section titled “Deployment Architecture”Main CI/CD Pipeline (ci-cd-pipeline.yml)
Section titled “Main CI/CD Pipeline (ci-cd-pipeline.yml)”- Purpose: Validation and Docker image builds
- Docker Services: Builds images and pushes to GHCR for Coolify deployment
- Marketing Site: Only builds for validation (no deployment)
Separate Deployment Workflows
Section titled “Separate Deployment Workflows”deploy-production.yml: Deploys Docker services to Coolify + marketing site to Cloudflare Pagesdeploy-staging.yml: Deploys Docker services to Coolify + marketing site to Cloudflare Pages
Branching Strategy
Section titled “Branching Strategy”main: Production-ready code, protected branchstaging: Pre-production testing environment, protected branchdevelop: Integration branch for features and fixesfeature/*: Feature branches for ongoing developmenthotfix/*: Emergency fixes for production issuesrelease/*: Preparation for releases with version bumps
Build Process
Section titled “Build Process”1. Two-Stage Build Process
Section titled “1. Two-Stage Build Process”Our CI/CD pipeline separates validation from deployment:
On Pull Requests (Validation)
Section titled “On Pull Requests (Validation)”- Build Only: Validates that Docker images can be built successfully
- No Push: Images are not pushed to prevent unnecessary deployments
- Fast Feedback: Developers get quick validation without triggering deployments
On Merges to Main/Staging (Deployment)
Section titled “On Merges to Main/Staging (Deployment)”- Build and Push: Builds Docker images and pushes to GHCR
- Marketing Site Build: Builds static Astro marketing site
- Registry Tagging: Images are tagged with branch name and commit SHA for traceability
- Version Consistency: All deployed services use images from the same commit SHA
2. Selective Builds with Docker Bake
Section titled “2. Selective Builds with Docker Bake”The pipeline uses Docker Bake to build only the services that have changed:
- Change Detection: Detects changes in webapp, pdf-api, pdf-worker, and marketing directories
- Conditional Builds: Only builds services with detected changes
- Path Filters: Monitors specific directories and configuration files for each service
3. Docker Bake Configuration
Section titled “3. Docker Bake Configuration”The docker-bake.hcl file defines all build targets and their configurations. Note: The default values (BRANCH_NAME = "main", COMMIT_SHA = "latest") are fallbacks that get dynamically overridden by the workflow:
variable "REGISTRY" { default = "ghcr.io"}
variable "IMAGE_PREFIX" { default = "ghcr.io/your-org/erp-unlocked"}
variable "VERSION" { default = "latest"}
variable "BRANCH_NAME" { default = "main"}
variable "COMMIT_SHA" { default = "latest"}
group "default" { targets = ["webapp", "pdf-api", "pdf-worker"]}
target "webapp" { context = "." dockerfile = "apps/webapp/Dockerfile" tags = [ "${IMAGE_PREFIX}/webapp:${replace(BRANCH_NAME, "/", "-")}-${COMMIT_SHA}", "${IMAGE_PREFIX}/webapp:latest" ] cache-from = ["type=gha"] cache-to = ["type=gha,mode=max"] platforms = ["linux/amd64"]}
# Other targets defined similarly...4. GitHub Actions Workflow
Section titled “4. GitHub Actions Workflow”The .github/workflows/ci-cd-pipeline.yml file orchestrates the build process:
- Detect Changes: Determines which services have changed using path filters
- Validate Configuration: Checks that the Docker Bake file is valid
- Dynamic Variable Override: Passes current branch and commit to Docker Bake:
Terminal window --set *.args.BRANCH_NAME=${GITHUB_REF_NAME}--set *.args.COMMIT_SHA=${GITHUB_SHA} - Conditional Execution:
- PRs:
build-validationjob runs (builds without push) - Merges:
build-and-pushjob runs (builds and pushes to registry)
- PRs:
- Marketing Site: Builds Astro site only on merges to main/staging (no deployment)
- Notify: Provides detailed summary showing validation vs deployment mode
Service Change Detection
Section titled “Service Change Detection”The pipeline monitors these paths for changes:
webapp: - 'apps/webapp/**' - 'packages/**' - 'pnpm-lock.yaml' - 'docker-bake.hcl'
pdf-api: - 'apps/pdf-api/**' - 'packages/pdf-shared/**' - 'pyproject.toml' - 'uv.lock' - 'docker-bake.hcl'
pdf-worker: - 'apps/pdf-worker/**' - 'packages/pdf-shared/**' - 'pyproject.toml' - 'uv.lock' - 'docker-bake.hcl'
marketing: - 'pnpm-lock.yaml'Local Testing with Act
Section titled “Local Testing with Act”You can test the GitHub Actions workflows locally using Act before pushing changes to the repository. This helps identify issues early and reduces the need for multiple commits to fix workflow problems.
1. Setup for Local Testing
Section titled “1. Setup for Local Testing”- Install Act: Follow the installation instructions for your platform
- Create Configuration Files:
.actrc: Configuration for Act.env.act: Environment variables and secrets
Example .actrc:
-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest-P ubuntu-22.04=ghcr.io/catthehacker/ubuntu:act-22.04--secret-file .env.act--env-file .env.actExample .env.act:
GITHUB_TOKEN=your_github_token2. Using the Test Script
Section titled “2. Using the Test Script”We provide a script to make testing workflows easier:
# Test a specific job in a workflow./scripts/test-workflows.sh ci-cd-pipeline.yml detect-changes
# Run the job (not just dry run)./scripts/test-workflows.sh ci-cd-pipeline.yml detect-changes run
# Test validation workflow./scripts/test-workflows.sh validate.yml validate3. Common Testing Issues
Section titled “3. Common Testing Issues”- Authentication Errors: Ensure you have a valid GitHub token in
.env.act - Missing Dependencies: Act might not install all dependencies correctly
- Docker Access: Make sure your user has permissions to run Docker commands
- Resource Limitations: Some jobs might require more resources than available locally
4. What to Test Before Pushing
Section titled “4. What to Test Before Pushing”Before pushing changes to the repository, test at least:
- detect-changes: Ensure path filters work correctly
- validate: Check that validation steps pass
- build-and-push: Verify Docker Bake configuration (with
--dryrun)
Deployment Process
Section titled “Deployment Process”1. Coolify Setup for Automatic Image Pulls
Section titled “1. Coolify Setup for Automatic Image Pulls”A. Add GHCR Registry to Coolify
Section titled “A. Add GHCR Registry to Coolify”- Go to Coolify dashboard → Settings → Docker Registries
- Add:
- Registry URL:
ghcr.io - Username: Your GitHub username
- Password: GitHub Personal Access Token with
read:packagesscope
- Registry URL:
B. Configure Each Service in Coolify
Section titled “B. Configure Each Service in Coolify”For each app (webapp, pdf-api, pdf-worker):
- Type: Docker Image
- Image:
ghcr.io/<your-org>/erp-unlocked/webapp:<branch>-<sha>ghcr.io/<your-org>/erp-unlocked/pdf-api:<branch>-<sha>ghcr.io/<your-org>/erp-unlocked/pdf-worker:<branch>-<sha>
- Restart Policy:
unless-stopped - Network:
coolify(default) - Environment Variables:
- Set as needed (e.g.,
NODE_ENV,REDIS_URL, etc.)
- Set as needed (e.g.,
- Public Access: Only for webapp
C. Service Configuration Details
Section titled “C. Service Configuration Details”Web Service
Section titled “Web Service”Name: erp-unlocked-webImage: ghcr.io/<your-org>/erp-unlocked/webapp:main-latestPublic Access: Yes (port 4321)Environment Variables: NODE_ENV=production PDF_PROCESSOR_URL=http://erp-unlocked-pdf-api:8000 REDIS_URL=redis://redis:6379/0 PORT=4321PDF API Service
Section titled “PDF API Service”Name: erp-unlocked-pdf-apiImage: ghcr.io/<your-org>/erp-unlocked/pdf-api:main-latestPublic Access: NoEnvironment Variables: REDIS_URL=redis://redis:6379/0 DATABASE_URL=<your-database-url>PDF Worker Service
Section titled “PDF Worker Service”Name: erp-unlocked-pdf-workerImage: ghcr.io/<your-org>/erp-unlocked/pdf-worker:main-latestPublic Access: NoEnvironment Variables: REDIS_URL=redis://redis:6379/0 DATABASE_URL=<your-database-url>2. Automatic Deployment Options
Section titled “2. Automatic Deployment Options”Option 1: Tag-Based Deployments
Section titled “Option 1: Tag-Based Deployments”Configure each service in Coolify to track a specific tag (e.g., main-latest for production, staging-latest for staging). Coolify will automatically pull new images when they are pushed with these tags.
Option 2: Webhook Deployments
Section titled “Option 2: Webhook Deployments”- Create a webhook in Coolify for each service
- Add the webhook URL to GitHub Secrets as
COOLIFY_PRODUCTION_WEBHOOKandCOOLIFY_STAGING_WEBHOOK - Create a GitHub Actions workflow that triggers these webhooks after successful builds
Image Tagging Strategy
Section titled “Image Tagging Strategy”Images are tagged using the following convention:
<branch>-<commit-sha>: Specific version (e.g.,main-abc1234)<branch>-latest: Latest build for a branch (e.g.,main-latest)latest: Latest production build (frommainbranch)
Build Triggers and Behavior
Section titled “Build Triggers and Behavior”Normal Operation
Section titled “Normal Operation”- PRs to main/staging/develop: Validation builds only (no deployment)
- Merges to main/staging: Full build and deployment
- Marketing Site: Only builds and deploys on merges, not PRs
Special Build Triggers
Section titled “Special Build Triggers”You can force a full build (all services) using:
- Manual Trigger: Run the workflow manually with
full_build=true - Commit Message: Include
[full-build]in your commit message - Release Branch: Pushes to
release/*branches always build all services
Marketing Site Deployment
Section titled “Marketing Site Deployment”The marketing site (Astro static site) has special handling across two workflow types:
Main CI/CD Pipeline (ci-cd-pipeline.yml)
Section titled “Main CI/CD Pipeline (ci-cd-pipeline.yml)”- PRs: Validates marketing site builds but does not deploy
- Merges to main/staging: Builds for validation only (no deployment)
- Purpose: Ensures marketing site builds successfully before merge
Deployment Workflows (deploy-production.yml, deploy-staging.yml)
Section titled “Deployment Workflows (deploy-production.yml, deploy-staging.yml)”- Cloudflare Pages Deployment: Separate workflows handle actual deployment to Cloudflare Pages
- Triggered: Manual or automated deployment triggers (independent of main CI/CD pipeline)
- Environment: Production workflow deploys to production Cloudflare Pages, staging to staging environment
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”-
Image Not Found in Coolify:
- Verify the image was successfully pushed to GHCR
- Check GHCR authentication in Coolify
- Ensure the image name and tag match exactly
-
Service Cannot Connect to Another Service:
- Verify all services are on the same Coolify network
- Check that service names in environment variables match Coolify service names
- Ensure internal ports are correctly exposed
-
Build Failing:
- Check the GitHub Actions logs for specific errors
- Verify that
docker-bake.hclis correctly formatted - Ensure Dockerfiles exist at the specified paths
Maintenance Tasks
Section titled “Maintenance Tasks”- Rotate GitHub PAT: Every 90 days, generate a new GitHub Personal Access Token and update it in Coolify
- Clean Up Old Images: Periodically remove unused images from GHCR to save space
- Update Base Images: Regularly update base images in Dockerfiles for security patches