Kubernetes itself doesn’t actually care how you get your container images into its registry or how you tell it to run them; it just wants to know that the image is there and what your desired state is. GitHub Actions is just one tool among many that can automate that process for you.
Let’s see it in action.
Imagine you’ve got a simple Nginx deployment. Your deployment.yaml might look like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: your-dockerhub-username/my-nginx:latest # This is what we'll update
ports:
- containerPort: 80
And your service.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer # Or ClusterIP, NodePort depending on your setup
Now, you want to build this Nginx image whenever you push to your main branch, push it to Docker Hub, and then update your Kubernetes deployment to use the new image. Here’s a GitHub Actions workflow that does just that:
name: Deploy to Kubernetes
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: your-dockerhub-username/my-nginx:${{ github.sha }} # Tag with commit SHA
cache-from: type=registry,ref=your-dockerhub-username/my-nginx:buildcache
cache-to: type=registry,ref=your-dockerhub-username/my-nginx:buildcache
- name: Set up Kubeconfig
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG_DATA }}
- name: Deploy to Kubernetes
run: |
kubectl apply -f deployment.yaml
kubectl set image deployment/my-nginx-deployment nginx=your-dockerhub-username/my-nginx:${{ github.sha }} --record
Let’s break down what’s happening:
-
on: push: branches: - main: This triggers the workflow every time code is pushed to themainbranch. -
actions/checkout@v3: This action checks out your repository’s code so the workflow can access yourDockerfileand Kubernetes manifests. -
docker/setup-buildx-action@v2: This sets up Docker Buildx, a multi-platform builder that allows for more efficient Docker image building. -
docker/login-action@v2: This step securely logs you into your Docker Hub account using secrets you’ve stored in GitHub. -
docker/build-push-action@v4: This is where the magic happens. It builds your Docker image using theDockerfilein your repository and pushes it to Docker Hub. Crucially, it tags the image with the Git commit SHA (${{ github.sha }}). This ensures that each deployment is tied to a specific version of your code. We also use build cache to speed up subsequent builds. -
azure/k8s-set-context@v3: This action configureskubectlto connect to your Kubernetes cluster. You’ll need to store your cluster’skubeconfigfile as a GitHub secret namedKUBECONFIG_DATA. -
kubectl apply -f deployment.yaml: This applies your deployment manifest. If the deployment doesn’t exist, it creates it. If it does, it will ensure the desired state is maintained. -
kubectl set image deployment/my-nginx-deployment nginx=your-dockerhub-username/my-nginx:${{ github.sha }} --record: This is the core deployment command. It tells Kubernetes to update themy-nginx-deploymentto use the new image we just built and pushed. Thenginx=part specifies which container within the deployment needs to be updated (matching thenamein yourdeployment.yaml). The--recordflag is useful for auditing changes.
The actual "deployment" in Kubernetes is an ongoing process. When you run kubectl set image, Kubernetes doesn’t instantly swap out all your running containers. Instead, it initiates a rolling update. It will gradually terminate old pods and start new ones with the updated image, ensuring zero downtime if your application is designed for it. The Deployment object in Kubernetes manages this entire lifecycle, handling the desired number of replicas, update strategy, and rollbacks.
What most people miss is that the image: your-dockerhub-username/my-nginx:latest in your deployment.yaml is often a red herring in automated deployments. You never want to hardcode :latest in a production deployment. Using the commit SHA as the tag (your-dockerhub-username/my-nginx:${{ github.sha }}) is the standard practice because it provides immutability and traceability. Each commit gets a unique image tag, and your Kubernetes deployment then points to that specific, immutable image.
The next step you’ll likely encounter is managing Kubernetes secrets and configurations dynamically within your CI/CD pipeline, rather than relying on static YAML files.