This directory contains CI/CD workflows for the QBO-FIRS application. The workflows automate building, testing, and deploying the application to Kubernetes using DigitalOcean Container Registry.
Triggers: Pull requests and pushes to any branch
Purpose: Validates code quality and builds
Jobs:
- Lint - Runs ESLint on the codebase
- Test - Runs unit tests and generates coverage reports
- Build - Builds the NestJS application
- Docker Build - Builds Docker images (without pushing)
- Security Scan - Scans Docker images for vulnerabilities using Trivy
- Validate K8s - Validates Kubernetes manifests
Triggers: Push to main branch or manual workflow dispatch
Purpose: Deploys to production environment
Jobs:
- Build and Push - Builds production Docker image and pushes to DigitalOcean registry
- Deploy - Deploys to
qbo-firs-prodnamespace in Kubernetes - Notify - Sends deployment status notification
Image Tags:
latest- Latest production releasemain-{sha}- Git commit SHA- Semantic versions if tagged
Triggers: Push to develop branch, PRs to develop, or manual dispatch
Purpose: Deploys to development environment
Jobs:
- Build and Push - Builds development Docker image
- Test - Runs tests before deployment
- Deploy - Deploys to
qbo-firs-devnamespace - Notify - Sends deployment status
Image Tags:
dev- Latest development releasedev-latest- Alternative dev tagdev-{sha}- Git commit SHA
Triggers: Manual workflow dispatch only
Purpose: Deploy specific image tags to any environment
Inputs:
environment- Choose production or developmentimage_tag- Specify the Docker image tag to deployskip_build- Option to skip building and use existing image
Configure these secrets in your GitHub repository settings (Settings > Secrets and variables > Actions):
| Secret Name | Description | How to Get |
|---|---|---|
DIGITALOCEAN_TOKEN |
DigitalOcean API token | Create in DigitalOcean Console |
K8S_CLUSTER_NAME |
Kubernetes cluster name | Your DigitalOcean Kubernetes cluster name |
- Go to DigitalOcean API Tokens
- Click Generate New Token
- Name:
GitHub Actions - QBO FIRS - Scopes: Select both Read and Write
- Click Generate Token
- Copy the token immediately (it won't be shown again)
- Add to GitHub Secrets as
DIGITALOCEAN_TOKEN
# List your clusters
doctl kubernetes cluster list
# Get cluster name
doctl kubernetes cluster get <cluster-id> --format NameBefore the first deployment, create Kubernetes secrets manually:
# Configure kubectl
doctl kubernetes cluster kubeconfig save <cluster-name>
# Create namespace
kubectl create namespace qbo-firs-prod
# Create secret
kubectl create secret generic qbo-firs-secret \
--from-literal=QB_CLIENT_ID='your_production_client_id' \
--from-literal=QB_CLIENT_SECRET='your_production_client_secret' \
--from-literal=QB_WEBHOOK_VERIFIER='your_production_verifier' \
--from-literal=SUPABASE_URL='https://your-project.supabase.co' \
--from-literal=SUPABASE_ANON_KEY='your_production_supabase_key' \
--from-literal=RABBITMQ_URL='amqp://user:password@rabbitmq:5672' \
--namespace=qbo-firs-prod# Create namespace
kubectl create namespace qbo-firs-dev
# Create secret
kubectl create secret generic qbo-firs-secret \
--from-literal=QB_CLIENT_ID='your_dev_client_id' \
--from-literal=QB_CLIENT_SECRET='your_dev_client_secret' \
--from-literal=QB_WEBHOOK_VERIFIER='your_dev_verifier' \
--from-literal=SUPABASE_URL='https://your-dev-project.supabase.co' \
--from-literal=SUPABASE_ANON_KEY='your_dev_supabase_key' \
--from-literal=RABBITMQ_URL='amqp://user:password@rabbitmq:5672' \
--namespace=qbo-firs-devSetting up GitHub environments adds an extra layer of control and visibility:
- Go to
Settings > Environments - Click New environment
- Create two environments:
productiondevelopment
- Protection Rules:
- Required reviewers: Add team members who must approve deployments
- Wait timer: Optional delay before deployment
- Deployment branches: Only
mainbranch
- Environment Secrets: Can override repository secrets if needed
- URL: https://your-production-domain.com
- Protection Rules:
- Deployment branches:
developand feature branches
- Deployment branches:
- URL: https://your-dev-domain.com
- Create a feature branch from
develop - Make your changes and commit
- Push to
developbranch - CI runs automatically
- On push to
develop, deployment workflow triggers
- Merge PR to
mainbranch - CI validates the code
- Deployment workflow builds and deploys automatically
- If environments are configured, wait for approval
- Go to
Actionstab in GitHub - Select Manual Deploy workflow
- Click Run workflow
- Choose options:
- Environment: production or development
- Image tag: Enter tag (e.g.,
latest,dev,v1.2.3) - Skip build: Check to deploy existing image
- Click Run workflow
# Tag a specific commit
git tag v1.2.3
git push origin v1.2.3
# Use Manual Deploy workflow with tag `v1.2.3`- Go to Actions tab
- Select a workflow from the left sidebar
- Click on a run to see details
- Expand jobs to see step-by-step logs
# Configure kubectl
doctl kubernetes cluster kubeconfig save <cluster-name>
# Check production
kubectl get pods -n qbo-firs-prod
kubectl get svc -n qbo-firs-prod
kubectl get ingress -n qbo-firs-prod
# Check development
kubectl get pods -n qbo-firs-dev
kubectl get svc -n qbo-firs-dev
kubectl get ingress -n qbo-firs-dev
# Check specific deployment
kubectl describe deployment qbo-firs -n qbo-firs-prod
# View logs
kubectl logs -f deployment/qbo-firs -n qbo-firs-prod- Use Manual Deploy workflow
- Select the environment to rollback
- Enter the previous working image tag
- Check Skip build
- Run workflow
# View rollout history
kubectl rollout history deployment/qbo-firs -n qbo-firs-prod
# Rollback to previous version
kubectl rollout undo deployment/qbo-firs -n qbo-firs-prod
# Rollback to specific revision
kubectl rollout undo deployment/qbo-firs -n qbo-firs-prod --to-revision=2# List images in DigitalOcean registry
doctl registry repository list-tags qbo-firs
# List all tags
doctl registry repository list-tags qbo-firs --format Tag,UpdatedAt# Delete specific tag
doctl registry repository delete-tag qbo-firs old-tag-name
# Run garbage collection
doctl registry garbage-collection startCheck:
- Docker build errors in workflow logs
- Dockerfile syntax
- Dependencies in package.json
Fix:
# Test locally
docker build --target production -t test .Check:
- Kubernetes secrets exist
- kubectl can connect to cluster
- Deployment manifests are valid
Fix:
# Verify secrets
kubectl get secret qbo-firs-secret -n qbo-firs-prod
# Test manifests
kubectl apply --dry-run=client -f k8s/production/Check:
- Image exists in registry
- Image name and tag are correct
- Kubernetes can access DigitalOcean registry
Fix:
# Verify image exists
doctl registry repository list-tags qbo-firs
# Check pod events
kubectl describe pod <pod-name> -n qbo-firs-prodCheck:
- Application is starting correctly
- Health endpoint is accessible
- Port configuration is correct
Fix:
# Check pod logs
kubectl logs <pod-name> -n qbo-firs-prod
# Exec into pod
kubectl exec -it <pod-name> -n qbo-firs-prod -- sh
# Test health endpoint inside pod
curl http://localhost:8443/companies/health/check- Always test in development first - Deploy to dev environment before production
- Use semantic versioning - Tag releases with version numbers (v1.2.3)
- Monitor deployments - Watch logs and metrics after deployment
- Keep secrets secure - Never commit secrets to repository
- Regular updates - Keep dependencies and base images updated
- Backup configurations - Keep backup of working configurations
- Document changes - Update CHANGELOG for each deployment
main (production)
├── develop (development)
│ ├── feature/new-feature
│ ├── bugfix/fix-issue
│ └── hotfix/urgent-fix
- main - Production-ready code, triggers prod deployment
- develop - Development code, triggers dev deployment
- feature/ - New features, tested via CI
- bugfix/ - Bug fixes, tested via CI
- hotfix/ - Urgent fixes, can be merged directly to main
For issues with:
- Workflows - Check GitHub Actions logs
- Kubernetes - Check pod logs and events
- DigitalOcean - Contact DigitalOcean support
- Application - Check application logs