Chapter 9 CI/CD Integration and Automation
Chapter 9 CI/CD Integration and Automation
Learning Objectives
- Understand Docker’s role in CI/CD workflows
- Master Docker-based continuous integration configuration
- Learn to implement automated image building and pushing
- Implement automated deployment of containerized applications
- Master multi-environment deployment strategies
Knowledge Point Details
9.1 CI/CD Fundamentals
9.1.1 CI/CD Workflow Overview
Traditional CI/CD Workflow:
Dockerized CI/CD Workflow:
9.1.2 Advantages of Docker in CI/CD
Environment Consistency:
# Development environment
docker run -p 3000:3000 myapp:dev
# Test environment
docker run -p 3000:3000 myapp:test
# Production environment
docker run -p 3000:3000 myapp:prod
Fast Deployment:
# Blue-green deployment
docker service update --image myapp:v2.0 web-service
# Rolling update
docker service update --update-parallelism 2 --update-delay 10s myapp
9.2 Jenkins Integration
9.2.1 Jenkins Environment Setup
Running Jenkins with Docker:
# Create Jenkins data volume
docker volume create jenkins_home
# Run Jenkins container
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
jenkins/jenkins:lts
Jenkins Configuration File:
# docker-compose.yml
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JAVA_OPTS=-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true
networks:
- jenkins-network
jenkins-agent:
image: jenkins/inbound-agent:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
networks:
- jenkins-network
volumes:
jenkins_home:
networks:
jenkins-network:
driver: bridge
9.2.2 Jenkins Pipeline Configuration
Jenkinsfile Example:
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'your-registry.com'
IMAGE_NAME = 'myapp'
IMAGE_TAG = "${BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/your-repo/myapp.git'
}
}
stage('Build Docker Image') {
steps {
script {
docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
docker.build("${IMAGE_NAME}:latest")
}
}
}
stage('Test') {
steps {
script {
// Run test container
sh """
docker run --rm \
-v \$(pwd):/app \
${IMAGE_NAME}:${IMAGE_TAG} \
npm test
"""
}
}
}
stage('Security Scan') {
steps {
script {
// Use Trivy to scan image
sh """
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image ${IMAGE_NAME}:${IMAGE_TAG}
"""
}
}
}
stage('Push to Registry') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push()
docker.image("${IMAGE_NAME}:latest").push()
}
}
}
}
stage('Deploy to Staging') {
steps {
script {
// Deploy to test environment
sh """
docker service update \
--image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
staging_web
"""
}
}
}
stage('Integration Tests') {
steps {
script {
// Run integration tests
sh """
docker run --rm \
--network staging_network \
cypress/included:latest \
--spec "cypress/integration/**/*"
"""
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
input 'Deploy to production?'
script {
// Production environment deployment
sh """
docker service update \
--image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
--update-parallelism 2 \
--update-delay 30s \
production_web
"""
}
}
}
}
post {
always {
// Clean up build images
sh "docker image prune -f"
}
success {
echo 'Pipeline succeeded!'
}
failure {
echo 'Pipeline failed!'
}
}
}
Multi-branch Build Strategy:
pipeline {
agent any
stages {
stage('Build & Test') {
steps {
script {
def imageTag = env.BRANCH_NAME == 'main' ? 'latest' : env.BRANCH_NAME
// Build image
def image = docker.build("myapp:${imageTag}")
// Run tests
image.inside {
sh 'npm test'
}
// Decide whether to push based on branch
if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME.startsWith('release/')) {
docker.withRegistry('https://your-registry.com', 'registry-credentials') {
image.push()
}
}
}
}
}
}
}
9.3 GitLab CI Integration
9.3.1 GitLab CI Configuration
.gitlab-ci.yml Basic Configuration:
# .gitlab-ci.yml
stages:
- build
- test
- security
- deploy-staging
- deploy-production
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
LATEST_TAG: $CI_REGISTRY_IMAGE:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
image: docker:latest
script:
- docker build -t $IMAGE_TAG .
- docker build -t $LATEST_TAG .
- docker push $IMAGE_TAG
- docker push $LATEST_TAG
only:
- main
- develop
- merge_requests
test:
stage: test
image: docker:latest
script:
- docker pull $IMAGE_TAG
- docker run --rm $IMAGE_TAG npm test
- docker run --rm $IMAGE_TAG npm run lint
coverage: '/Coverage: \d+\.\d+/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
security_scan:
stage: security
image: docker:latest
script:
- docker pull $IMAGE_TAG
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy:latest image --format table $IMAGE_TAG
allow_failure: true
deploy_staging:
stage: deploy-staging
image: docker:latest
script:
- echo "Deploying to staging..."
- docker service update --image $IMAGE_TAG staging_web
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy_production:
stage: deploy-production
image: docker:latest
script:
- echo "Deploying to production..."
- docker service update --image $IMAGE_TAG production_web
environment:
name: production
url: https://example.com
when: manual
only:
- main
9.3.2 Multi-Environment Deployment
Environment-Specific Configuration:
# .gitlab-ci.yml
.deploy_template: &deploy_template
image: docker:latest
script:
- docker-compose -f docker-compose.$ENVIRONMENT.yml up -d
- docker system prune -f
deploy_dev:
<<: *deploy_template
stage: deploy
variables:
ENVIRONMENT: development
environment:
name: development
only:
- develop
deploy_staging:
<<: *deploy_template
stage: deploy
variables:
ENVIRONMENT: staging
environment:
name: staging
only:
- main
deploy_production:
<<: *deploy_template
stage: deploy
variables:
ENVIRONMENT: production
environment:
name: production
when: manual
only:
- main
Environment Configuration Files:
# docker-compose.development.yml
version: '3.8'
services:
web:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DEBUG=true
# docker-compose.production.yml
version: '3.8'
services:
web:
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
ports:
- "80:3000"
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
memory: 256M
reservations:
memory: 128M
9.4 GitHub Actions Integration
9.4.1 Basic Workflow
.github/workflows/ci-cd.yml:
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- 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=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run tests
run: |
docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} npm test
security-scan:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
deploy-staging:
runs-on: ubuntu-latest
needs: [build-and-test, security-scan]
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Deploy to staging
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker service update --image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} staging_web
deploy-production:
runs-on: ubuntu-latest
needs: [build-and-test, security-scan]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker service update \
--image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--update-parallelism 2 \
--update-delay 30s \
production_web
9.4.2 Matrix Build Strategy
Multi-version Build:
name: Multi-version Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
platform: [linux/amd64, linux/arm64]
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker image
uses: docker/build-push-action@v4
with:
context: .
platforms: ${{ matrix.platform }}
build-args: |
NODE_VERSION=${{ matrix.node-version }}
tags: myapp:node${{ matrix.node-version }}-${{ matrix.platform }}
push: false
test:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
test-suite: [unit, integration, e2e]
steps:
- uses: actions/checkout@v3
- name: Run ${{ matrix.test-suite }} tests
run: |
docker run --rm myapp:latest npm run test:${{ matrix.test-suite }}
9.5 Automated Deployment Strategies
9.5.1 Blue-Green Deployment
Blue-Green Deployment Script:
#!/bin/bash
# blue-green-deploy.sh
IMAGE_TAG=$1
SERVICE_NAME="web-service"
NETWORK_NAME="app-network"
# Get current active deployment
CURRENT_COLOR=$(docker service inspect --format '{{.Spec.Labels.color}}' ${SERVICE_NAME} 2>/dev/null || echo "blue")
# Determine new color
if [ "$CURRENT_COLOR" = "blue" ]; then
NEW_COLOR="green"
else
NEW_COLOR="blue"
fi
echo "Current deployment: $CURRENT_COLOR"
echo "New deployment: $NEW_COLOR"
# Deploy new version
docker service create \
--name ${SERVICE_NAME}-${NEW_COLOR} \
--network $NETWORK_NAME \
--label color=$NEW_COLOR \
--replicas 3 \
$IMAGE_TAG
# Wait for new service to be ready
echo "Waiting for new service to be ready..."
while [ $(docker service ps ${SERVICE_NAME}-${NEW_COLOR} --filter "desired-state=running" --format "{{.CurrentState}}" | grep -c "Running") -ne 3 ]; do
sleep 5
done
# Health check
echo "Performing health check..."
if curl -f http://localhost:8080/health; then
echo "Health check passed. Switching traffic..."
# Update load balancer configuration
docker service update \
--label-add color=$NEW_COLOR \
nginx-lb
# Remove old service
if docker service ls --filter name=${SERVICE_NAME}-${CURRENT_COLOR} --format "{{.Name}}" | grep -q ${SERVICE_NAME}-${CURRENT_COLOR}; then
docker service rm ${SERVICE_NAME}-${CURRENT_COLOR}
fi
# Rename new service
docker service update --name ${SERVICE_NAME} ${SERVICE_NAME}-${NEW_COLOR}
echo "Deployment completed successfully!"
else
echo "Health check failed. Rolling back..."
docker service rm ${SERVICE_NAME}-${NEW_COLOR}
exit 1
fi
9.5.2 Rolling Update
Kubernetes Rolling Update:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web
image: myapp:latest
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Docker Swarm Rolling Update:
# Configure rolling update parameters
docker service update \
--image myapp:v2.0 \
--update-parallelism 2 \
--update-delay 30s \
--update-failure-action rollback \
--update-monitor 60s \
web-service
9.5.3 Canary Release
Canary Release Configuration:
#!/bin/bash
# canary-deploy.sh
NEW_IMAGE=$1
CANARY_PERCENTAGE=${2:-10}
# Calculate canary instance count
TOTAL_REPLICAS=$(docker service inspect --format='{{.Spec.Replicas}}' production-web)
CANARY_REPLICAS=$((TOTAL_REPLICAS * CANARY_PERCENTAGE / 100))
echo "Deploying canary with $CANARY_REPLICAS replicas (${CANARY_PERCENTAGE}%)"
# Create canary service
docker service create \
--name production-web-canary \
--network production-network \
--label version=canary \
--replicas $CANARY_REPLICAS \
$NEW_IMAGE
# Monitor canary metrics
echo "Monitoring canary deployment..."
sleep 300
# Check error rate
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])" | jq '.data.result[0].value[1]' | tr -d '"')
if (( $(echo "$ERROR_RATE < 0.01" | bc -l) )); then
echo "Canary deployment successful. Promoting to full deployment..."
# Update production service
docker service update --image $NEW_IMAGE production-web
# Remove canary service
docker service rm production-web-canary
echo "Full deployment completed!"
else
echo "Canary deployment failed. Rolling back..."
docker service rm production-web-canary
exit 1
fi
9.6 Monitoring and Rollback
9.6.1 Deployment Monitoring
Prometheus Monitoring Configuration:
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'docker'
static_configs:
- targets: ['localhost:9323']
- job_name: 'app'
docker_sd_configs:
- host: unix:///var/run/docker.sock
port: 3000
relabel_configs:
- source_labels: [__meta_docker_container_label_monitoring]
target_label: __tmp_should_be_scraped
regex: true
- source_labels: [__tmp_should_be_scraped]
regex: true
target_label: __address__
replacement: ${1}:3000
rule_files:
- "deployment_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
Deployment Alert Rules:
# deployment_rules.yml
groups:
- name: deployment
rules:
- alert: DeploymentFailed
expr: up{job="app"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Application deployment failed"
description: "Application {{ $labels.instance }} is down for more than 2 minutes"
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} for instance {{ $labels.instance }}"
- alert: HighLatency
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High latency detected"
description: "95th percentile latency is {{ $value }}s for instance {{ $labels.instance }}"
9.6.2 Automatic Rollback
Health Check and Rollback Script:
#!/bin/bash
# auto-rollback.sh
SERVICE_NAME=$1
NEW_IMAGE=$2
HEALTH_ENDPOINT=$3
MAX_WAIT=300
WAIT_INTERVAL=10
echo "Updating service $SERVICE_NAME to $NEW_IMAGE"
# Get current image as rollback version
PREVIOUS_IMAGE=$(docker service inspect --format='{{.Spec.TaskTemplate.ContainerSpec.Image}}' $SERVICE_NAME)
# Perform update
docker service update --image $NEW_IMAGE $SERVICE_NAME
# Wait for service update to complete
echo "Waiting for service update to complete..."
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
REPLICAS_UPDATED=$(docker service ps $SERVICE_NAME --filter "desired-state=running" --format "{{.Image}}" | grep -c $NEW_IMAGE)
TOTAL_REPLICAS=$(docker service inspect --format='{{.Spec.Replicas}}' $SERVICE_NAME)
if [ $REPLICAS_UPDATED -eq $TOTAL_REPLICAS ]; then
echo "All replicas updated successfully"
break
fi
sleep $WAIT_INTERVAL
ELAPSED=$((ELAPSED + WAIT_INTERVAL))
done
# Health check
echo "Performing health check..."
sleep 30 # Wait for application startup
HEALTH_CHECK_ATTEMPTS=0
MAX_HEALTH_ATTEMPTS=10
while [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_ATTEMPTS ]; do
if curl -f -s $HEALTH_ENDPOINT > /dev/null; then
echo "Health check passed"
# Check error rate
ERROR_COUNT=$(curl -s "http://prometheus:9090/api/v1/query?query=increase(http_requests_total{status=~'5..'}[5m])" | jq '.data.result[0].value[1]' | tr -d '"' | cut -d. -f1)
if [ ${ERROR_COUNT:-0} -lt 10 ]; then
echo "Deployment successful!"
exit 0
else
echo "High error rate detected, initiating rollback..."
break
fi
else
echo "Health check failed, attempt $((HEALTH_CHECK_ATTEMPTS + 1))"
HEALTH_CHECK_ATTEMPTS=$((HEALTH_CHECK_ATTEMPTS + 1))
sleep 30
fi
done
# Perform rollback
echo "Rolling back to previous version: $PREVIOUS_IMAGE"
docker service update --image $PREVIOUS_IMAGE $SERVICE_NAME
# Wait for rollback to complete
echo "Waiting for rollback to complete..."
sleep 60
# Verify rollback
if curl -f -s $HEALTH_ENDPOINT > /dev/null; then
echo "Rollback completed successfully"
exit 1
else
echo "Rollback failed, manual intervention required"
exit 2
fi
Practical Case Study
Case 1: Microservices CI/CD Pipeline
Scenario Description: Build a complete CI/CD pipeline for a microservices architecture including frontend, API gateway, user service, and order service.
Project Structure:
microservices-app/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── api-gateway/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── user-service/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── order-service/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
├── docker-compose.yml
├── .gitlab-ci.yml
└── deploy/
├── staging.yml
└── production.yml
GitLab CI Configuration:
# .gitlab-ci.yml
stages:
- build
- test
- deploy-staging
- integration-test
- deploy-production
variables:
DOCKER_DRIVER: overlay2
SERVICES: "frontend api-gateway user-service order-service"
.build_template: &build_template
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- cd $SERVICE_DIR
- docker build -t $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA
only:
changes:
- "$SERVICE_DIR/**/*"
build-frontend:
<<: *build_template
variables:
SERVICE_NAME: frontend
SERVICE_DIR: frontend
build-api-gateway:
<<: *build_template
variables:
SERVICE_NAME: api-gateway
SERVICE_DIR: api-gateway
build-user-service:
<<: *build_template
variables:
SERVICE_NAME: user-service
SERVICE_DIR: user-service
build-order-service:
<<: *build_template
variables:
SERVICE_NAME: order-service
SERVICE_DIR: order-service
.test_template: &test_template
stage: test
image: docker:latest
services:
- docker:dind
script:
- docker run --rm $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA npm test
test-frontend:
<<: *test_template
variables:
SERVICE_NAME: frontend
needs: ["build-frontend"]
test-api-gateway:
<<: *test_template
variables:
SERVICE_NAME: api-gateway
needs: ["build-api-gateway"]
test-user-service:
<<: *test_template
variables:
SERVICE_NAME: user-service
needs: ["build-user-service"]
test-order-service:
<<: *test_template
variables:
SERVICE_NAME: order-service
needs: ["build-order-service"]
deploy-staging:
stage: deploy-staging
image: docker:latest
services:
- docker:dind
script:
- docker-compose -f deploy/staging.yml up -d
environment:
name: staging
url: https://staging.example.com
only:
- develop
integration-tests:
stage: integration-test
image: postman/newman:alpine
script:
- newman run tests/integration/postman_collection.json --environment tests/integration/staging.json
needs: ["deploy-staging"]
deploy-production:
stage: deploy-production
image: docker:latest
services:
- docker:dind
script:
- ./scripts/blue-green-deploy.sh
environment:
name: production
url: https://example.com
when: manual
only:
- main
Summary
Docker plays a core role in CI/CD workflows, achieving the following through containerization technology:
- Environment Consistency: Development, testing, and production environments are completely consistent
- Fast Deployment: Containers start quickly with high deployment efficiency
- Easy to Scale: Supports horizontal scaling and load balancing
- Version Management: Version control through image tags
- Rollback Capability: Quick rollback to previous versions
Best Practices Summary:
- Use multi-stage builds to optimize image size
- Implement image security scanning and vulnerability detection
- Adopt semantic versioning management strategy
- Implement automated testing and deployment
- Establish comprehensive monitoring and alerting mechanisms
- Develop emergency response and rollback strategies
Through proper CI/CD workflow design, development efficiency can be significantly improved, deployment risks reduced, and application quality and stability ensured.