GitHub Actions doesn't have a first-party Alibaba Cloud integration the way it does for AWS or Azure, but that turns out not to matter — the aliyun CLI and kubectl cover everything you need, and the pipeline ends up just as clean. Here's the version I run for shipping a containerized service to Container Service for Kubernetes (ACK).
The RAM permissions, scoped correctly
Before touching the pipeline, create a RAM user dedicated to CI — never reuse your own credentials or a broad admin key:
aliyun ram CreateUser --UserName ci-deploy
aliyun ram AttachPolicyToUser \
--PolicyType System \
--PolicyName AliyunContainerRegistryFullAccess \
--UserName ci-deploy
aliyun ram AttachPolicyToUser \
--PolicyType System \
--PolicyName AliyunCSFullAccess \
--UserName ci-deploy
AliyunCSFullAccess is broader than most pipelines need — if you're running this in production, write a custom policy scoped to the specific ACK cluster ID rather than every cluster in the account.
The pipeline
name: Deploy to Alibaba Cloud
on:
push:
branches: [main]
env:
REGISTRY: registry.ap-southeast-1.aliyuncs.com
NAMESPACE: my-app-namespace
IMAGE: my-app
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
run: |
echo "${{ secrets.ACR_PASSWORD }}" | docker login \
-u "${{ secrets.ACR_USERNAME }}" \
--password-stdin ${{ env.REGISTRY }}
- name: Build and push image
run: |
IMAGE_TAG=${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}
docker build -t $IMAGE_TAG .
docker push $IMAGE_TAG
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
- name: Set up kubectl for ACK
uses: aliyun/ack-set-context-action@v1
with:
access-key-id: ${{ secrets.ALICLOUD_ACCESS_KEY }}
access-key-secret: ${{ secrets.ALICLOUD_SECRET_KEY }}
cluster-id: ${{ secrets.ACK_CLUSTER_ID }}
- name: Roll out the new image
run: |
kubectl set image deployment/my-app \
my-app=${{ env.IMAGE_TAG }} \
-n production
kubectl rollout status deployment/my-app -n production --timeout=120s
A few things doing real work in that last step:
kubectl rollout statusblocks the job until the rollout actually succeeds or times out — without it, a broken image ships "successfully" and fails silently in production five minutes later.- Tagging the image with
github.sharather thanlatestmeans every deploy is traceable back to an exact commit, which matters the first time you need to answer "what changed" during an incident.
Handling secrets correctly
Store ACR_USERNAME, ACR_PASSWORD, ALICLOUD_ACCESS_KEY, ALICLOUD_SECRET_KEY, and ACK_CLUSTER_ID as encrypted repository or environment secrets in GitHub — never as plaintext in the workflow file, and never echoed to logs. If your repo supports GitHub Environments, gate the production environment behind a required reviewer so a merge to main doesn't deploy unattended.
Adding a staging gate
For anything beyond a side project, split the workflow so main deploys to staging automatically and production requires a manual promotion:
deploy-production:
needs: build-and-deploy
runs-on: ubuntu-latest
environment: production # requires manual approval if configured
steps:
- name: Promote staging image to production
run: |
kubectl set image deployment/my-app \
my-app=${{ needs.build-and-deploy.outputs.image_tag }} \
-n production
What this buys you over doing it by hand
The whole point of wiring this up is that a deploy becomes a git push instead of a person SSHing in and running commands from memory. Once this pipeline exists, the next useful step is layering in the cost and scaling practices I cover in the Kubernetes on Alibaba Cloud article — the deployment mechanics here stay the same regardless of how the cluster underneath is provisioned.