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
- Log in to Huawei Cloud Console
- Navigate to Container Registry (SWR)
- Create a new organization (e.g.,
sbmcrm) - 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
- Use Matrix Builds - Test on multiple Node.js versions
- Cache Dependencies - Speed up builds with caching
- Parallel Jobs - Run independent jobs in parallel
- Conditional Deployments - Only deploy on specific branches
- Rollback Strategy - Implement automatic rollback on failure
- Health Checks - Verify deployment before marking as complete
- Log Retention - Keep deployment logs for troubleshooting
Next Steps
- Set up GitHub repository secrets
- Configure Huawei Cloud SWR and ECS
- Create deployment scripts on ECS instances
- Test workflow on staging branch
- Enable branch protection rules
- Set up monitoring and notifications