(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisbon

How to do a canary deploy on Kubernetes with GitHub Actions: step by step

João Barros 10 de September de 2026 4 min read

This tutorial shows how to do a canary deploy on Kubernetes with GitHub Actions to roll out new versions gradually and reduce risk. I explain why canary deployments are useful; then I detail a concrete flow with Kubernetes manifests, GitHub Actions and simple verification.

Prerequisites

  • GitHub account and a repository with the application code (e.g., public or private Docker image).
  • Accessible Kubernetes cluster (minikube, kind or a cloud cluster) with kubectl configured.
  • GitHub Actions runner with permissions for kubectl (use KUBECONFIG secrets or a token).
  • Basic Kubernetes knowledge: Deployment, Service and labels.

Step 1: Prepare Kubernetes manifests to support canary

Create a primary Deployment and prepare a separate Deployment for the canary. The idea is to have two Deployments (app-primary, app-canary) and a Service that balances by label. This way we can control traffic to the canary by changing replicas or weights in the Ingress/Service.

# deployment-primary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-primary
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      role: primary
  template:
    metadata:
      labels:
        app: myapp
        role: primary
    spec:
      containers:
      - name: myapp
        image: myrepo/myapp:stable
        ports:
        - containerPort: 80

# deployment-canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      role: canary
  template:
    metadata:
      labels:
        app: myapp
        role: canary
    spec:
      containers:
      - name: myapp
        image: myrepo/myapp:canary
        ports:
        - containerPort: 80

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

Step 2: Configure GitHub Actions to build image and perform canary deploy

Create a workflow that builds the canary image, pushes it to the registry and applies the manifests to the cluster. Use a job that updates only the canary Deployment to the new tag; that way the primary keeps the stable version.

# .github/workflows/canary-deploy.yaml
name: Canary Deploy
on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
    - name: Build and push image
      uses: docker/build-push-action@v4
      with:
        push: true
        tags: myrepo/myapp:${{ github.sha }}
        # configure registry credentials via secrets
    - name: Configure kubectl
      run: |
        echo "$KUBECONFIG" > kubeconfig
        export KUBECONFIG=$PWD/kubeconfig
      env:
        KUBECONFIG: ${{ secrets.KUBECONFIG }}
    - name: Update canary deployment image
      run: |
        kubectl set image deployment/app-canary myapp=myrepo/myapp:${{ github.sha }}
        kubectl rollout status deployment/app-canary --timeout=60s

Step 3: Gradually increase traffic to the canary

There are several ways to increase traffic: increase the canary replicas, use Ingress/Service with weights (e.g., Istio, Traefik) or manipulate selectors. Here we show the simple approach: increase the canary replicas and later reduce the primary's.

# scale-canary.sh  (run via GitHub Action or manually)
kubectl scale deployment/app-canary --replicas=2
# after verification
kubectl scale deployment/app-primary --replicas=2
# finally promote the canary to primary by changing images and restoring replicas
kubectl set image deployment/app-primary myapp=myrepo/myapp:${CANARY_TAG}
kubectl scale deployment/app-canary --replicas=0
kubectl scale deployment/app-primary --replicas=3

Step 4: Monitoring and automatic rollback

Configure liveness/readiness health checks and use a simple script to check metrics (errors, latency) or the readiness state. In the example below, a workflow step checks endpoints and rolls back if the error rate increases.

# simplified example of verification via curl
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://myapp-svc/health)
if [ "$STATUS" != "200" ]; then
  kubectl set image deployment/app-canary myapp=myrepo/myapp:stable
  kubectl rollout status deployment/app-canary --timeout=60s
  exit 1
fi

Verify the result

Confirm that the canary is running the new image and serving traffic only to a subset of users. Use kubectl to inspect:

kubectl get deployments
kubectl get pods -l role=canary -o wide
kubectl describe deployment app-canary
kubectl logs deployment/app-canary --tail=50

If you use an Ingress or a weighted Service, check metrics in the Ingress controller or observability tools (Prometheus/Grafana). Test endpoints and confirm that rollback works by simulating failures.

Conclusion

Canary deploys on Kubernetes with GitHub Actions allow reducing release risk by controlling traffic gradually. Next steps: integrate Istio/Linkerd for traffic shifting by weight, automate metrics with Prometheus and create automatic gates in the workflow. Tip: start with replicas and simple health checks before introducing a service mesh — what is the biggest concern you want to mitigate with canaries?