Skip to main content

GitHub Actions CI/CD with Huawei Cloud

Complete guide for setting up continuous integration and continuous deployment using GitHub Actions with Huawei Cloud.

Overview

This guide covers:

  • GitHub Actions workflow setup
  • Huawei Cloud integration
  • Automated testing and deployment
  • Environment-specific deployments
  • Security best practices

Prerequisites

  • GitHub repository with SBM CRM Platform code
  • Huawei Cloud account
  • Huawei Cloud ECS (Elastic Cloud Server) instances
  • Huawei Cloud Container Registry (SWR) access
  • SSH access to deployment servers

GitHub Actions Setup

Create Workflow Directory

mkdir -p .github/workflows

Basic Workflow Structure

Create .github/workflows/ci-cd.yml:

name: CI/CD Pipeline

on:
push:
branches: [ main, staging, develop ]
pull_request:
branches: [ main ]

env:
REGISTRY: swr.ap-southeast-1.myhuaweicloud.com
IMAGE_NAME: sbmcrm-platform

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint

- name: Run tests
run: npm test

- name: Run type check
run: npm run typecheck

build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Build application
run: npm run build

- name: Build Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest .

deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
steps:
- uses: actions/checkout@v3

- name: Deploy to Huawei Cloud
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HUAWEI_HOST }}
username: ${{ secrets.HUAWEI_USER }}
key: ${{ secrets.HUAWEI_SSH_KEY }}
script: |
cd /opt/sbmcrm
docker-compose pull
docker-compose up -d
docker-compose exec -T api npm run migrate

Huawei Cloud Integration

Container Registry (SWR) Setup

1. Create Container Registry

  1. Log in to Huawei Cloud Console
  2. Navigate to Container Registry (SWR)
  3. Create a new organization (e.g., sbmcrm)
  4. Create a repository (e.g., sbmcrm-platform)

2. Configure Docker Login

Add to GitHub Actions workflow:

- name: Login to Huawei Cloud SWR
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HUAWEI_ACCESS_KEY }}
password: ${{ secrets.HUAWEI_SECRET_KEY }}

3. Push Docker Image

- name: Push to SWR
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

ECS (Elastic Cloud Server) Deployment

1. Prepare ECS Instance

# Install Docker on ECS
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Login to SWR
docker login -u {username} -p {password} swr.ap-southeast-1.myhuaweicloud.com

2. Deployment Script

Create deployment script on ECS:

#!/bin/bash
# /opt/sbmcrm/deploy.sh

set -e

cd /opt/sbmcrm

# Pull latest images
docker-compose pull

# Run migrations
docker-compose exec -T api npm run migrate

# Restart services
docker-compose up -d

# Health check
sleep 10
curl -f http://localhost:3000/health || exit 1

echo "Deployment successful"

Complete CI/CD Workflow

Full Workflow Example

name: CI/CD Pipeline

on:
push:
branches: [ main, staging, develop ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production

env:
REGISTRY: swr.ap-southeast-1.myhuaweicloud.com
IMAGE_NAME: sbmcrm-platform
NODE_VERSION: '18'

jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint

- name: Run tests
run: npm test -- --coverage

- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info

- name: Type check
run: npm run typecheck

build:
name: Build Docker Image
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to Huawei Cloud SWR
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HUAWEI_ACCESS_KEY }}
password: ${{ secrets.HUAWEI_SECRET_KEY }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
id: build
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

deploy-staging:
name: Deploy to Staging
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging-api.yourdomain.com
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Deploy to Huawei Cloud ECS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
port: ${{ secrets.STAGING_PORT }}
script: |
cd /opt/sbmcrm
export IMAGE_TAG=${{ github.sha }}
./deploy.sh

deploy-production:
name: Deploy to Production
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://api.yourdomain.com
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Deploy to Huawei Cloud ECS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
port: ${{ secrets.PRODUCTION_PORT }}
script: |
cd /opt/sbmcrm
export IMAGE_TAG=${{ github.sha }}
./deploy.sh

- name: Run smoke tests
run: |
sleep 30
curl -f https://api.yourdomain.com/health || exit 1

- name: Notify deployment
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Production deployment completed'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()

GitHub Secrets Configuration

Required Secrets

Configure in GitHub repository settings → Secrets and variables → Actions:

Huawei Cloud Secrets

HUAWEI_ACCESS_KEY=your_access_key
HUAWEI_SECRET_KEY=your_secret_key
HUAWEI_REGION=ap-southeast-1

Staging Environment

STAGING_HOST=staging-ecs-ip
STAGING_USER=ecs-user
STAGING_SSH_KEY=-----BEGIN RSA PRIVATE KEY-----...
STAGING_PORT=22

Production Environment

PRODUCTION_HOST=production-ecs-ip
PRODUCTION_USER=ecs-user
PRODUCTION_SSH_KEY=-----BEGIN RSA PRIVATE KEY-----...
PRODUCTION_PORT=22

Additional Secrets

SLACK_WEBHOOK=https://hooks.slack.com/services/...
DATABASE_URL=postgresql://user:pass@host:5432/db
REDIS_URL=redis://host:6379

Docker Configuration

Dockerfile

FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci

# Copy source code
COPY . .

# Build application
RUN npm run build

# Production image
FROM node:18-alpine

WORKDIR /app

# Install production dependencies only
COPY package*.json ./
RUN npm ci --only=production

# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node healthcheck.js

# Start application
CMD ["node", "dist/index.js"]

docker-compose.yml

version: '3.8'

services:
api:
image: ${REGISTRY}/sbmcrm-platform:${IMAGE_TAG:-latest}
container_name: sbmcrm-api
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
depends_on:
- postgres
- redis
networks:
- sbmcrm-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3

postgres:
image: postgres:13-alpine
container_name: sbmcrm-postgres
restart: unless-stopped
environment:
- POSTGRES_DB=sbmcrm_production
- POSTGRES_USER=sbmcrm
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- sbmcrm-network

redis:
image: redis:6-alpine
container_name: sbmcrm-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- sbmcrm-network

volumes:
postgres-data:
redis-data:

networks:
sbmcrm-network:
driver: bridge

Deployment Script

deploy.sh

#!/bin/bash
set -e

echo "Starting deployment..."

# Load environment variables
source .env

# Pull latest images
echo "Pulling latest images..."
docker-compose pull

# Backup database before migration
echo "Backing up database..."
docker-compose exec -T postgres pg_dump -U sbmcrm sbmcrm_production > backup_$(date +%Y%m%d_%H%M%S).sql

# Run database migrations
echo "Running migrations..."
docker-compose exec -T api npm run migrate || {
echo "Migration failed, rolling back..."
exit 1
}

# Restart services with zero downtime
echo "Restarting services..."
docker-compose up -d --no-deps api

# Wait for health check
echo "Waiting for health check..."
sleep 15

# Health check
HEALTH_CHECK_URL="http://localhost:3000/health"
MAX_RETRIES=10
RETRY_COUNT=0

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if curl -f $HEALTH_CHECK_URL > /dev/null 2>&1; then
echo "Health check passed!"
break
fi
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Health check failed, retrying... ($RETRY_COUNT/$MAX_RETRIES)"
sleep 5
done

if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
echo "Health check failed after $MAX_RETRIES retries"
echo "Rolling back..."
docker-compose rollback
exit 1
fi

# Clean up old images
echo "Cleaning up old images..."
docker image prune -f

echo "Deployment completed successfully!"

Environment-Specific Workflows

Staging Workflow

name: Deploy to Staging

on:
push:
branches: [ staging, develop ]
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v3

- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
cd /opt/sbmcrm-staging
git pull origin staging
docker-compose -f docker-compose.staging.yml up -d --build

Production Workflow

name: Deploy to Production

on:
push:
branches: [ main ]
workflow_dispatch:
inputs:
confirm:
description: 'Type "deploy" to confirm'
required: true

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v3

- name: Confirm deployment
if: github.event.inputs.confirm != 'deploy'
run: |
echo "Deployment not confirmed"
exit 1

- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
script: |
cd /opt/sbmcrm-production
./deploy.sh

Security Best Practices

1. Use Environment Secrets

Never hardcode secrets in workflows:

# ❌ Bad
password: mypassword123

# ✅ Good
password: ${{ secrets.DATABASE_PASSWORD }}

2. Limit SSH Key Permissions

# Create deployment user with limited permissions
sudo useradd -m -s /bin/bash deploy
sudo usermod -aG docker deploy

# Configure sudo for specific commands only
echo "deploy ALL=(ALL) NOPASSWD: /usr/bin/docker-compose" | sudo tee /etc/sudoers.d/deploy

3. Use Protected Branches

  • Enable branch protection rules
  • Require pull request reviews
  • Require status checks to pass
  • Require signed commits

4. Scan for Vulnerabilities

- name: Run security scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'

- name: Upload scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'

Monitoring and Notifications

Slack Notifications

- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: |
Deployment Status: ${{ job.status }}
Branch: ${{ github.ref }}
Commit: ${{ github.sha }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()

Email Notifications

- name: Send email notification
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: 'Deployment ${{ job.status }}'
to: devops@yourcompany.com
from: GitHub Actions
body: |
Deployment completed with status: ${{ job.status }}

Troubleshooting

Common Issues

1. Authentication Failed

# Verify Huawei Cloud credentials
docker login -u $HUAWEI_ACCESS_KEY -p $HUAWEI_SECRET_KEY swr.ap-southeast-1.myhuaweicloud.com

2. SSH Connection Failed

# Test SSH connection
ssh -i ~/.ssh/deploy_key user@ecs-ip

# Verify SSH key format
cat ~/.ssh/deploy_key | head -1 # Should start with -----BEGIN

3. Docker Build Failed

# Enable build logs
- name: Build
run: docker build --progress=plain -t image:tag .

4. Deployment Timeout

# Increase timeout
- name: Deploy
uses: appleboy/ssh-action@master
with:
timeout: 10m
script: |
# Your deployment script

Best Practices

  1. Use Matrix Builds - Test on multiple Node.js versions
  2. Cache Dependencies - Speed up builds with caching
  3. Parallel Jobs - Run independent jobs in parallel
  4. Conditional Deployments - Only deploy on specific branches
  5. Rollback Strategy - Implement automatic rollback on failure
  6. Health Checks - Verify deployment before marking as complete
  7. Log Retention - Keep deployment logs for troubleshooting

Next Steps

  1. Set up GitHub repository secrets
  2. Configure Huawei Cloud SWR and ECS
  3. Create deployment scripts on ECS instances
  4. Test workflow on staging branch
  5. Enable branch protection rules
  6. Set up monitoring and notifications