CI/CD Pipeline
PhotoFlow uses GitLab CI/CD with a modular template-based architecture for consistent deployments across all services.
Template Structure
PhotoFlow CI/CD templates are organized in devops/ci-templates/:
ci-templates/
├── config/
│ ├── docker.yml # Docker configuration
│ ├── variables.yml # Global variables
│ └── runners.yml # Runner configurations
├── jobs/
│ ├── docker-build.yml # Build jobs
│ ├── helm-deploy.yml # Deployment jobs
│ ├── mr-validation.yml # MR checks
│ └── gitops-update.yml # GitOps updates
├── pipelines/
│ └── dotnet-service.yml # Complete .NET pipeline
└── helm-templates/ # Helm value templates
Pipeline Stages
1. Check Stage
Runs on merge requests for code quality validation:
mr-check:
extends: .mr_check_dotnet
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
Validates:
YAML syntax
Code formatting
Linting rules
Unit tests
2. Build Stage
Builds Docker images for each environment:
build-dev:
extends: .docker_build_all
variables:
IMAGE_TAG: dev
rules:
- if: '$CI_COMMIT_REF_NAME == $DEV_BRANCH'
build-uat:
extends: .docker_build_all
variables:
IMAGE_TAG: uat
rules:
- if: '$CI_COMMIT_REF_NAME == $UAT_BRANCH'
when: manual
build-prod:
extends: .docker_build_all
variables:
IMAGE_TAG: ${CI_COMMIT_TAG:-latest}
rules:
- if: '$CI_COMMIT_TAG'
when: manual
3. Deploy Stage
Deploys to Kubernetes using Helm:
deploy-dev:
extends: .helm_deploy_dev
needs: [build-dev]
rules:
- if: '$ENABLE_DEV_DEPLOY == "true"'
deploy-prod:
extends: .helm_deploy_prod
needs: [build-prod]
rules:
- if: '$ENABLE_PROD_DEPLOY == "true"'
when: manual
Image Tag Convention
Environment | Branch | Tag |
|---|
Development | develop | dev
|
Beta | develop | beta
|
UAT | uat | uat
|
Production | main/master | latest or git tag
|
Service Pipeline Configuration
Backend API Example
# pfl01y25-be-app/.gitlab-ci.yml
include:
- project: 'shining-tree/devops/ci-templates'
ref: develop
file: 'pipelines/dotnet-service.yml'
- project: 'shining-tree/devops/ci-templates'
ref: develop
file: 'jobs/gitops-update.yml'
default:
tags:
- pfl-docker
variables:
SERVICE_NAME: "pfl-be-api"
HELM_RELEASE_NAME: "pfl-be-api"
# Registry
REGISTRY_URL: "registry.shiningtree.co"
IMAGE_PATH: "photo-flow/backend/pfl01y25-be-app"
# Components
ENABLE_API_COMPONENT: "true"
ENABLE_GRPC_COMPONENT: "false"
# Helm Chart
HELM_CHART_OCI: "oci://registry.gitlab.com/nextera-devops/nera-helm-chart/helm/shin-service"
HELM_CHART_VERSION: "1.0.0"
# Values files
DEV_VALUES_FILE: "devops/IaC/environments/dev.yaml"
UAT_VALUES_FILE: "devops/IaC/environments/uat.yaml"
PROD_VALUES_FILE: "devops/IaC/environments/prod.yaml"
# Branches
DEV_BRANCH: "develop"
UAT_BRANCH: "uat"
PROD_BRANCH_PRIMARY: "main"
# Namespaces
DEV_NAMESPACE: "photoflow-dev"
UAT_NAMESPACE: "photoflow-uat"
PROD_NAMESPACE: "photoflow"
# Toggle
ENABLE_DEV_DEPLOY: "true"
ENABLE_PROD_DEPLOY: "true"
Frontend Example
# pfl01y25-fe-web/.gitlab-ci.yml
include:
- project: 'shining-tree/devops/ci-templates'
ref: develop
file: 'pipelines/angular-app.yml'
variables:
SERVICE_NAME: "pfl-fe-web"
IMAGE_PATH: "photo-flow/frontend/pfl01y25-fe-web"
DEV_NAMESPACE: "photoflow-dev"
Helm Deployment
Deploy Script Logic
# Auto-compute IMAGE_TAG from DEPLOY_ENV
case "$DEPLOY_ENV" in
dev) IMAGE_TAG="dev" ;;
beta) IMAGE_TAG="beta" ;;
uat) IMAGE_TAG="uat" ;;
prod) IMAGE_TAG="${CI_COMMIT_TAG:-latest}" ;;
esac
# Resolve namespace
case "$DEPLOY_ENV" in
dev) NAMESPACE="${DEV_NAMESPACE}" ;;
uat) NAMESPACE="${UAT_NAMESPACE}" ;;
prod) NAMESPACE="${PROD_NAMESPACE}" ;;
esac
# Helm deploy
helm upgrade --install $HELM_RELEASE_NAME $CHART \
-n $NAMESPACE \
-f $HELM_VALUES_BASE/_global.yaml \
-f $HELM_VALUES_BASE/_ingress.yaml \
-f $HELM_VALUES_BASE/environments/$DEPLOY_ENV.yaml \
--set image.tag="$IMAGE_TAG" \
--set image.repository="$IMAGE_REPO"
Values File Structure
devops/IaC/
├── _global.yaml # Global settings
├── _ingress.yaml # Ingress configuration
├── _containers/ # Container definitions
│ ├── api.yaml
│ └── worker.yaml
└── environments/
├── dev.yaml # Dev overrides
├── uat.yaml # UAT overrides
└── prod.yaml # Production settings
Environment Configuration
Production
Namespace: photoflow
URL: https://api.photoflow.vn
Trigger: Manual on main branch or git tag
Image Tag: Git tag or latest
GitOps Integration
PhotoFlow uses GitOps for production deployments:
gitops-update:
stage: deploy
variables:
GITOPS_REPO: "https://gitlab.com/photo-flow/photoflow-gitops.git"
GITOPS_BRANCH: "main"
script:
- git clone $GITOPS_REPO
- cd photoflow-gitops
- yq e -i ".image.tag = \"$IMAGE_TAG\"" apps/$SERVICE_NAME/values.yaml
- git commit -am "Update $SERVICE_NAME to $IMAGE_TAG"
- git push
Rollback Procedures
Helm Rollback
# List releases
helm history $HELM_RELEASE_NAME -n $NAMESPACE
# Rollback to previous
helm rollback $HELM_RELEASE_NAME -n $NAMESPACE
# Rollback to specific revision
helm rollback $HELM_RELEASE_NAME 5 -n $NAMESPACE
Deployment Rollback
# View rollout history
kubectl rollout history deployment/$DEPLOY_NAME -n $NAMESPACE
# Rollback to previous
kubectl rollout undo deployment/$DEPLOY_NAME -n $NAMESPACE
# Rollback to specific revision
kubectl rollout undo deployment/$DEPLOY_NAME --to-revision=2 -n $NAMESPACE
Pipeline Troubleshooting
Common Issues
Issue | Cause | Solution |
|---|
Build fails | Docker build error | Check Dockerfile, dependencies |
Deploy times out | Pod startup issues | Check pod logs, resources |
Helm error | Invalid values | Validate YAML syntax |
Runner not found | Tag mismatch | Verify runner tags |
Debugging Commands
# Check pipeline status
gitlab-runner status
# View pod logs
kubectl logs -n $NAMESPACE deployment/$DEPLOY_NAME
# Check Helm release
helm get values $HELM_RELEASE_NAME -n $NAMESPACE
# Describe failing pod
kubectl describe pod -n $NAMESPACE -l app=$DEPLOY_NAME
Security
Secrets Management
Secrets are stored in GitLab CI/CD variables:
REGISTRY_PASSWORD - Container registry auth
KUBECONFIG - Kubernetes cluster access
GOOGLE_CREDENTIALS - Google Cloud auth
DATABASE_PASSWORD - Production DB password
Image Scanning
container-scan:
stage: check
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL $IMAGE_NAME:$IMAGE_TAG
Last modified: 25 February 2026