GitHub Actions- Complete Guide

Documentation • 2026 Edition

GitHub Actions
The Definitive Guide

From manual FTP uploads to fully automated CI/CD pipelines. Master workflows, jobs, runners, and real-world deployment strategies with Docker & Kubernetes.

What is GitHub Actions?

A built-in CI/CD and automation platform native to GitHub that lets you build, test, and deploy code directly from your repository using simple YAML configuration.

The Old Way (Manual Deployment)

  • Developer finishes code on their local machine
  • Runs tests manually (or forgets entirely)
  • Connects to the server via FTP or SSH
  • Uploads files one by one, hoping nothing breaks
  • Restarts services manually, crossing fingers
  • Discovers at 2 AM that production is down

With GitHub Actions

  • Developer pushes code to GitHub
  • Automated tests run instantly
  • Code is built and packaged automatically
  • Deployed to staging, then to production
  • Team is notified via Slack on success or failure
  • Full audit trail of every deployment in Git history

Core Concepts

Understanding the building blocks that make up every GitHub Actions workflow.

Workflow

A configurable automated process defined in a YAML file inside .github/workflows/. A repository can have multiple workflows — one for CI, one for deployment, one for nightly tests, etc.

📦

Job

A set of steps that execute on the same runner. Jobs run in parallel by default but can be configured to run sequentially using the needs keyword for dependency chains.

🔧

Step

An individual task within a job. Each step is either a shell command (using run) or a reference to a reusable action (using uses). Steps execute sequentially within their job.

🖥️

Runner

A server that runs your workflows. GitHub provides hosted runners (Ubuntu, Windows, macOS) or you can register your own self-hosted runners for specialised hardware or network access.

🎬

Action

A reusable unit of code that performs a specific task. Can be sourced from the GitHub Marketplace, another repository, or defined locally. Think of it as a plugin for your pipeline.

📡

Event

A trigger that starts a workflow. Common events include push, pull_request, schedule (cron), workflow_dispatch (manual), and release.

How They Fit Together

1
Event
Push / PR / Schedule
2
Workflow
.yml triggers
3
Job(s)
Build / Test / Deploy
4
Steps
Run on Runner
5
Result
Pass ✓ / Fail ✕

Anatomy of a Workflow File

Every workflow lives in .github/workflows/ and follows a clear, hierarchical YAML structure.

name
A human-readable name for the workflow, displayed in the GitHub Actions tab. Optional but highly recommended.
on
Defines the event(s) that trigger the workflow — push, pull_request, schedule, workflow_dispatch, etc. Can filter by branches, paths, and tags.
env
Environment variables available to all jobs and steps. Can also be defined at job or step level for more granular scope.
jobs
The core of the workflow. Each job has a unique ID, runs on a specified runner, and contains an ordered list of steps. Jobs run in parallel unless dependencies are declared.
steps
Sequential tasks inside a job. Each step can checkout code, install dependencies, run tests, or invoke a reusable action from the marketplace.
📄 .github/workflows/basic.yml
# ── Minimal Workflow Structure ──

name: My First Workflow          # Display name

on:                                  # Trigger events
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:                                 # Global environment variables
  NODE_ENV: production

jobs:                                # Define jobs
  build:                              # Job ID
    runs-on: ubuntu-latest           # Runner
    steps:
      - name: Checkout code
        uses: actions/checkout@v4    # Reusable action

      - name: Run tests
        run: npm test               # Shell command

Event Triggers Deep Dive

GitHub Actions supports dozens of events. Here are the most commonly used ones and how to configure them.

EventDescriptionExample Use Case
pushFires when code is pushed to a branchRun CI on every commit to main
pull_requestFires on PR open, sync, or reopenRun tests before merging to main
scheduleCron-based schedule (UTC)Nightly builds, weekly security scans
workflow_dispatchManual trigger via GitHub UI / APIOn-demand deployments, hotfixes
releaseFires when a release is publishedPublish packages to npm / NuGet
workflow_runFires after another workflow completesDeploy after CI passes
📄 Advanced trigger configuration
on:
  # ── Branch & path filtering ──
  push:
    branches: [ main, release/* ]
    paths:
      - 'src/**'                     # Only trigger when source code changes
      - '!src/**/*.md'               # Ignore markdown changes
    tags:
      - 'v*'                         # Trigger on version tags

  # ── Cron schedule (daily at midnight UTC) ──
  schedule:
    - cron: '0 0 * * *'

  # ── Manual trigger with input parameters ──
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

Scenario 1: Manual to Automated Web Deployment

A company migrating from manual FTP/SSH uploads to a fully automated CI/CD pipeline deploying to live servers.

🏢 Company: Acme Web Solutions

Problem: Three developers manually build a React + Node.js application, then SSH into the production server, pull changes, run npm install and npm run build, then restart PM2. Deployments take 30–45 minutes, often fail halfway, and have caused three outages in the last quarter.

Goal: Automate the entire process — push to main triggers tests, builds, and deploys to the live Ubuntu server automatically with zero-downtime.

Pipeline Architecture

1
Push to main
Developer merges PR
2
Lint & Test
ESLint + Jest
3
Build
npm run build
4
Deploy
SSH + rsync
5
Verify & Notify
Health check + Slack
📄 .github/workflows/deploy-to-live-server.yml
# ═══════════════════════════════════════════════════════════════════
#  ACME WEB SOLUTIONS — Automated Deployment to Live Server
#  Replaces manual SSH + FTP workflow with full CI/CD pipeline
# ═══════════════════════════════════════════════════════════════════

name: 🚀 Build & Deploy to Production

on:
  push:
    branches: [ main ]              # Only deploy from main branch
  workflow_dispatch:                   # Allow manual trigger for hotfixes
    inputs:
      skip_tests:
        description: 'Skip tests (emergency hotfix only)'
        type: boolean
        default: false

env:
  NODE_VERSION: '20'
  APP_NAME: 'acme-webapp'
  DEPLOY_PATH: '/var/www/acme-webapp'

jobs:
  # ── JOB 1: Lint & Test ──
  test:
    name: 🧪 Lint & Test
    runs-on: ubuntu-latest
    if: ${{ !inputs.skip_tests }}   # Skip for emergency hotfixes
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'              # Cache node_modules for speed

      - name: Install dependencies
        run: npm ci                  # Clean install (faster, deterministic)

      - name: Run ESLint
        run: npm run lint

      - name: Run unit tests
        run: npm test -- --coverage

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  # ── JOB 2: Build ──
  build:
    name: 🔨 Build Application
    runs-on: ubuntu-latest
    needs: test                      # Wait for tests to pass
    if: ${{ always() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build production bundle
        run: npm run build
        env:
          REACT_APP_API_URL: ${{ secrets.PRODUCTION_API_URL }}

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: production-build
          path: build/
          retention-days: 7

  # ── JOB 3: Deploy to Live Server ──
  deploy:
    name: 🚀 Deploy to Production
    runs-on: ubuntu-latest
    needs: build                     # Wait for build to succeed
    environment:
      name: production              # GitHub Environment (for protection rules)
      url: https://acme-web.com
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: production-build
          path: build/

      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy via rsync (zero-downtime)
        run: |
          # Sync build files to server (only changed files)
          rsync -avz --delete \
            -e "ssh -i ~/.ssh/deploy_key" \
            build/ \
            ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }}:${{ env.DEPLOY_PATH }}/

      - name: Restart application server
        run: |
          ssh -i ~/.ssh/deploy_key \
            ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \
            "cd ${{ env.DEPLOY_PATH }} && \
             npm ci --production && \
             pm2 reload ${{ env.APP_NAME }} --update-env"

      - name: Verify deployment (health check)
        run: |
          sleep 10
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://acme-web.com/api/health)
          if [ "$STATUS" != "200" ]; then
            echo "❌ Health check failed with status $STATUS"
            exit 1
          fi
          echo "✅ Deployment verified — server is healthy"

      - name: Notify team on Slack
        if: always()                  # Send notification even on failure
        uses: slackapi/slack-github-action@v1.27.0
        with:
          payload: |
            {
              "text": "${{ job.status == 'success' && '✅' || '❌' }} Deployment to production ${{ job.status }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*${{ job.status == 'success' && '✅ Deployed' || '❌ Failed' }}* — `${{ github.sha }}` by ${{ github.actor }}"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
💡
Key secrets to configure: Navigate to your repo → Settings → Secrets and variables → Actions and add: SSH_PRIVATE_KEY, SSH_USER, SERVER_HOST, PRODUCTION_API_URL, and SLACK_WEBHOOK_URL.
🎯
Result: Acme's deployment time dropped from 30–45 minutes of manual work to under 4 minutes of fully automated CI/CD. Zero outages since adoption, and every deployment is auditable through Git history.

Scenario 2: Docker & Kubernetes Deployment

Containerising the application and deploying to a Kubernetes cluster for horizontal scaling, rolling updates, and self-healing infrastructure.

🏢 Company: Acme Web Solutions — Phase 2

Evolution: After succeeding with direct server deployment, Acme scales to multiple services. A single server can no longer handle traffic spikes. They adopt Docker for consistent environments and Kubernetes for orchestration, autoscaling, and zero-downtime rolling updates.

Stack: Docker → GitHub Container Registry (GHCR) → Kubernetes (AKS / EKS / GKE)

Container Pipeline Architecture

1
Push Tag
v1.2.3 release
2
Test
Unit + Integration
3
Docker Build
Multi-stage image
4
Push to GHCR
Container registry
5
K8s Deploy
Rolling update

Step A — The Dockerfile

🐳 Dockerfile (multi-stage build)
# ── Stage 1: Build ──
FROM node:20-alpine AS builder
WORKDIR /app

# Copy package files first (leverages Docker layer caching)
COPY package*.json ./
RUN npm ci

# Copy source code and build
COPY . .
RUN npm run build

# ── Stage 2: Production ──
FROM node:20-alpine AS production
WORKDIR /app

# Only copy what we need for production
COPY --from=builder /app/build ./build
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev

# Security: run as non-root user
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER appuser

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

CMD ["node", "build/server.js"]

Step B — Kubernetes Manifests

☸️ k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: acme-webapp
  labels:
    app: acme-webapp
spec:
  replicas: 3                        # Run 3 pods for high availability
  selector:
    matchLabels:
      app: acme-webapp
  strategy:
    type: RollingUpdate              # Zero-downtime rolling deployment
    rollingUpdate:
      maxSurge: 1                    # Add 1 new pod at a time
      maxUnavailable: 0              # Never reduce below desired count
  template:
    metadata:
      labels:
        app: acme-webapp
    spec:
      containers:
        - name: webapp
          image: ghcr.io/acme/webapp:IMAGE_TAG   # Replaced by CI/CD
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:               # K8s auto-restarts unhealthy pods
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:              # Only route traffic when ready
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
          env:
            - name: NODE_ENV
              value: "production"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: acme-secrets
                  key: database-url
---
apiVersion: v1
kind: Service
metadata:
  name: acme-webapp-service
spec:
  type: LoadBalancer
  selector:
    app: acme-webapp
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP

Step C — The GitHub Actions Workflow

📄 .github/workflows/docker-k8s-deploy.yml
# ═══════════════════════════════════════════════════════════════════
#  ACME — Docker Build & Kubernetes Deployment Pipeline
#  Builds container image, pushes to GHCR, deploys to K8s cluster
# ═══════════════════════════════════════════════════════════════════

name: 🐳 Docker Build & K8s Deploy

on:
  push:
    tags:
      - 'v*.*.*'                    # Trigger on semantic version tags

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
  K8S_NAMESPACE: production

permissions:
  contents: read
  packages: write                   # Needed to push to GHCR

jobs:
  # ── JOB 1: Run Tests ──
  test:
    name: 🧪 Test Suite
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run lint
      - run: npm test

  # ── JOB 2: Build & Push Docker Image ──
  build-and-push:
    name: 🐳 Build & Push Image
    runs-on: ubuntu-latest
    needs: test
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-

      - name: Build and push Docker image
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha       # GitHub Actions layer cache
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64  # Multi-arch

  # ── JOB 3: Deploy to Kubernetes ──
  deploy:
    name: ☸️ Deploy to Kubernetes
    runs-on: ubuntu-latest
    needs: build-and-push
    environment:
      name: production
      url: https://acme-web.com
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure kubectl
        uses: azure/setup-kubectl@v4

      - name: Set Kubernetes context
        uses: azure/k8s-set-context@v4
        with:
          method: kubeconfig
          kubeconfig: ${{ secrets.KUBE_CONFIG }}

      - name: Update image tag in manifest
        run: |
          cd k8s
          TAG="${{ github.ref_name }}"            # e.g., v1.2.3
          IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}"
          sed -i "s|IMAGE_TAG|${TAG}|g" deployment.yml
          echo "🐳 Deploying image: ${IMAGE}"

      - name: Apply Kubernetes manifests
        run: |
          kubectl apply -f k8s/deployment.yml -n ${{ env.K8S_NAMESPACE }}

      - name: Wait for rollout to complete
        run: |
          kubectl rollout status deployment/acme-webapp \
            -n ${{ env.K8S_NAMESPACE }} \
            --timeout=300s

      - name: Verify pods are running
        run: |
          kubectl get pods -n ${{ env.K8S_NAMESPACE }} -l app=acme-webapp
          echo ""
          echo "📊 Deployment details:"
          kubectl describe deployment acme-webapp -n ${{ env.K8S_NAMESPACE }} | head -30

      - name: Rollback on failure
        if: failure()
        run: |
          echo "❌ Deployment failed — initiating rollback"
          kubectl rollout undo deployment/acme-webapp -n ${{ env.K8S_NAMESPACE }}
          kubectl rollout status deployment/acme-webapp -n ${{ env.K8S_NAMESPACE }}
⚠️
Rollback safety: The workflow automatically rolls back to the previous deployment if kubectl rollout status fails. Combined with Kubernetes' maxUnavailable: 0 strategy, this ensures zero-downtime even when a deployment goes wrong.

Secrets & Security

Never hardcode credentials. GitHub provides encrypted secrets at the repository and organisation level.

Repository Secrets

Available to workflows in a single repository. Set them at Settings → Secrets and variables → Actions. Referenced as ${{ secrets.NAME }}.

Environment Secrets

Scoped to a specific environment (e.g., staging, production). Support protection rules like required reviewers and wait timers before deployment proceeds.

Organisation Secrets

Shared across repositories within an organisation. Configured at the org level and can be restricted to specific repos or made available to all.

GITHUB_TOKEN

Automatically generated for each workflow run. Provides scoped access to the repository's API, packages, and more. No setup required — just use ${{ secrets.GITHUB_TOKEN }}.

🔒
Security best practices: Secrets are masked in logs automatically. Never echo them. Always use the permissions key to follow the principle of least privilege. Pin actions to full commit SHAs instead of tags for supply-chain security (e.g., actions/checkout@a5ac7e... instead of @v4).

Matrix Builds

Run the same job across multiple configurations — Node versions, operating systems, or database engines — in parallel.

📄 Matrix strategy example
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false               # Don't cancel other jobs if one fails
      matrix:
        os: [ ubuntu-latest, windows-latest, macos-latest ]
        node-version: [ 18, 20, 22 ]
        exclude:
          - os: macos-latest          # Skip Node 18 on macOS
            node-version: 18
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
📊
This configuration generates 8 parallel jobs (3 OS × 3 Node versions − 1 exclusion). Each runs simultaneously on its own runner, giving you broad test coverage in the same time as a single run.

Marketplace Actions

The GitHub Marketplace offers thousands of pre-built actions. Here are the most essential ones used across the industry.

ActionPurposeUsage
actions/checkout@v4Clone your repository into the runnerPresent in virtually every workflow
actions/setup-node@v4Install and cache Node.jsJavaScript/TypeScript projects
actions/setup-dotnet@v4Install and cache .NET SDK.NET / C# projects
actions/cache@v4Cache dependencies across runsSpeed up npm, NuGet, pip installs
docker/build-push-action@v5Build and push Docker imagesContainer-based deployments
azure/k8s-set-context@v4Connect to Kubernetes clustersK8s deployments (AKS/EKS/GKE)
actions/upload-artifact@v4Store build outputs between jobsPass artifacts across job boundaries
slackapi/slack-github-actionSend notifications to SlackDeployment alerts and status updates

Best Practices

Production-proven patterns to keep your pipelines fast, secure, and maintainable.

Cache Aggressively

Use actions/cache or built-in caching in setup actions. Caching node_modules or .nuget/packages can cut install times from minutes to seconds.

🔒

Pin Action Versions

Use full commit SHAs instead of version tags for third-party actions. This prevents supply-chain attacks where a compromised tag could inject malicious code into your pipeline.

🧩

Use Reusable Workflows

Extract common CI/CD patterns into shared workflow files using workflow_call. Centralise your standards and eliminate copy-paste across repositories.

📏

Least Privilege Permissions

Always declare explicit permissions at the workflow or job level. Default token permissions are often too broad. Lock them to only what each job actually needs.

🧪

Fail Fast, Fix Fast

Put the cheapest, fastest checks (linting, type checking) first. If code style fails in 10 seconds, there's no point running a 5-minute integration test suite.

📦

Environments & Approvals

Use GitHub Environments with protection rules to gate production deployments. Require manual approval from senior engineers before any code reaches live servers.

Where & How to Practice — Free of Cost

You don't need a company infrastructure or a credit card to learn CI/CD hands-on. Here's a complete free-tier stack to practice everything in this guide.

🎯 The Good News

GitHub Actions is completely free for public repositories — unlimited minutes, unlimited runs. Even for private repos, the free tier gives you 2,000 minutes/month on Linux runners. That's more than enough to practice daily.

Step-by-Step: Your Free Practice Lab

1 GitHub — Your CI/CD Engine (Free)

Create a free GitHub account and a public repository. This gives you unlimited Actions minutes. Push any project — even a simple Node.js "Hello World" — and start writing .github/workflows/ files immediately.

💻 Quick start commands
# Create your practice repo
mkdir github-actions-lab && cd github-actions-lab
git init
npm init -y

# Add a simple test script to package.json
echo '{ "scripts": { "test": "echo Running tests... && exit 0" } }' > package.json

# Create your first workflow
mkdir -p .github/workflows
touch .github/workflows/ci.yml

# Push to GitHub and watch the Actions tab light up!
git add -A && git commit -m "My first CI pipeline"
git remote add origin https://github.com/YOUR_USER/github-actions-lab.git
git push -u origin main
PlanMinutes/MonthStorageCost
Public reposUnlimited500 MB artifactsFree
Private repos (Free tier)2,000 (Linux)500 MB artifactsFree

2 Docker Desktop — Build & Test Containers Locally (Free)

Docker Desktop is free for personal use and small businesses. Install it, write your Dockerfile, and test your container builds locally before pushing them through GitHub Actions.

🐳 Local Docker practice
# Build your image locally first — iterate fast
docker build -t my-app:dev .

# Run and test it
docker run -p 3000:3000 my-app:dev

# Verify the health endpoint
curl http://localhost:3000/api/health

# Once it works locally, push to GHCR via Actions (free for public repos)
# GitHub Container Registry (ghcr.io) is free for public packages
💡
Pro tip: Use act (github.com/nektos/act) to run GitHub Actions workflows locally using Docker. It simulates the GitHub runner on your machine — perfect for debugging workflows without pushing commits.

3 Kubernetes — Free Local & Cloud Options

You don't need a paid cloud cluster. Several tools give you a fully functional K8s cluster on your laptop or for free in the cloud.

Minikube

Runs a single-node Kubernetes cluster inside a VM or Docker container on your machine. The most popular local option.

minikube start --driver=docker
kubectl get nodes
# You now have a fully working K8s cluster!
kubectl apply -f k8s/deployment.yml
minikube service acme-webapp-service

Kind (K8s in Docker)

Creates K8s clusters using Docker containers as "nodes." Extremely lightweight, starts in seconds, perfect for CI/CD testing.

kind create cluster --name practice
kubectl cluster-info --context kind-practice
# Multi-node cluster? Easy:
kind create cluster --config kind-config.yml

K3s / K3d

A lightweight, certified Kubernetes distribution by Rancher. K3d wraps K3s in Docker for easy local clusters. Uses minimal RAM.

k3d cluster create mycluster
kubectl get pods --all-namespaces
# Full K8s API — just lighter weight

Docker Desktop (Built-in)

Docker Desktop ships with a built-in single-node Kubernetes cluster. Just enable it in Settings → Kubernetes → Enable Kubernetes.

# No install needed if Docker Desktop is running
# Settings → Kubernetes → ✅ Enable
kubectl config use-context docker-desktop
kubectl get nodes

4 Free Cloud Platforms for Deployment Targets

To practise the deploy step of your pipelines (not just build and test), you need somewhere to deploy to. These platforms offer generous free tiers:

PlatformFree TierBest For Practising
Render Free web services, static sites, PostgreSQL (90 days) Deploying Node.js/Python apps via GitHub push
Railway $5 free credit/month, Docker deployments Container deployments, databases, full-stack apps
Fly.io 3 shared VMs, 3 GB storage free Docker container deployments globally
Vercel Unlimited static/serverless deploys (hobby) Next.js, React, static site deployments
GitHub Pages Free static site hosting from any repo Static HTML/CSS/JS deployments via Actions
Oracle Cloud Always-free ARM VMs (4 cores, 24 GB RAM!) Full Linux server — SSH deploy, Docker, K3s cluster
Azure (Student/Free) $200 credit (30 days) + 12 months free services AKS, App Service, Azure Container Instances
Google Cloud $300 credit (90 days) + always-free tier GKE Autopilot, Cloud Run, Compute Engine
AWS 12-month free tier + always-free services EC2, ECS, EKS (free control plane), S3, Lambda

5 Interactive Learning Playgrounds

Hands-on sandboxes where you can learn without installing anything locally:

Killercoda

Free browser-based sandboxes for Kubernetes, Docker, Linux, and CI/CD. Guided scenarios with real terminals — no setup needed. Excellent for K8s practice.

killercoda.com

Play with Docker

Free 4-hour Docker playground in the browser. Spin up multiple Docker nodes and practice container builds, networking, and multi-host setups instantly.

labs.play-with-docker.com

Play with Kubernetes

Free browser-based K8s cluster for 4 hours. Practise deployments, services, and rolling updates without any cloud account.

labs.play-with-k8s.com

GitHub Skills

Official GitHub-authored courses that run inside real repositories. Includes a dedicated "GitHub Actions" learning path with hands-on exercises.

skills.github.com

Recommended Practice Roadmap

Week 1–2
GitHub Actions basics. Create a public repo, write a workflow that runs npm test on push. Experiment with different triggers, matrix builds, and caching. Deploy a static site to GitHub Pages via Actions.
Week 3–4
Docker & container CI. Write a multi-stage Dockerfile. Build and push images to GHCR via Actions. Use act to run workflows locally. Deploy containers to Render or Railway free tier.
Week 5–6
Kubernetes deployment. Set up Minikube or Kind locally. Write deployment manifests. Create a full pipeline: push tag → test → build image → deploy to local K8s. Practice rolling updates and rollbacks.
Week 7–8
Production patterns. Add environment protection rules, multi-environment workflows (staging → production), Slack notifications, and automatic rollbacks. Set up a K3s cluster on Oracle Cloud free tier for a real server deployment target.
🏆
Total cost: £0. Every tool and platform listed above has a free tier sufficient for learning. A public GitHub repo + Docker Desktop + Minikube gives you the complete CI/CD experience — from writing workflows to deploying containers to Kubernetes — all on your local machine.

GitHub Actions Documentation • Prepared for Acme Web Solutions • 2026

Built with precision. Deployed with confidence.

Post a Comment

Previous Post Next Post