Deploy with GitHub Actions: GitHub Pages, Docker Images and Staged Releases

Testing is half of a pipeline; shipping is the other half. In this lesson you will deploy a site to GitHub Pages, publish a Playwright report, push a Docker image to GitHub Container Registry and build a staging-to-production pipeline with smoke tests and approvals.

Advertisement

📖 What You’ll Learn: CI vs CD, deploying to GitHub Pages, publishing a Playwright HTML report, building and pushing Docker images to ghcr.io on tags, a staged pipeline with environments, smoke tests and required reviewers, OIDC for cloud credentials, and rolling back.


🚀 From CI to CD

So far your workflows test code (Continuous Integration). Continuous Delivery/Deployment (CD) goes further: after the tests pass, the pipeline ships the result, a website, a Docker image, a package or a test report, automatically and the same way every time.

A typical pipeline:

push to main ──â–ļ build ──â–ļ test ──â–ļ deploy to staging ──â–ļ smoke tests ──â–ļ (approval) ──â–ļ deploy to production

This lesson shows three common deployment targets and the safety features around them. It builds on workflow syntax and secrets and environments.


🌐 Example 1: Deploy a Static Site to GitHub Pages

# .github/workflows/pages.yml
name: Deploy site to GitHub Pages
on:
  push:
    branches: [ main ]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - run: npm run build                    # creates ./dist
      - uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

In Settings, Pages, set Source to GitHub Actions. More in GitHub Pages.

Bonus for testers: publish the Playwright HTML report

Replace the build steps with your test run and upload playwright-report/ as the Pages artifact. The team then opens the latest report at a stable URL instead of downloading artifacts.

      - run: npx playwright install --with-deps
      - run: npx playwright test
        continue-on-error: true               # publish the report even when tests fail
      - uses: actions/upload-pages-artifact@v3
        with:
          path: ./playwright-report

âš ī¸ Common mistake: A Pages site from a public repository is public. Do not publish reports that contain sensitive data, screenshots of internal systems or traces with tokens.


đŸŗ Example 2: Build and Push a Docker Image to GitHub Container Registry

# .github/workflows/docker.yml
name: Docker image
on:
  push:
    tags: [ 'v*' ]                            # release when a version tag is pushed

permissions:
  contents: read
  packages: write

jobs:
  image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: |
            ghcr.io/acme/test-runner:${{ github.ref_name }}
            ghcr.io/acme/test-runner:latest

Tagging a release (git tags) with git tag -a v1.2.0 -m "Release 1.2.0" and git push origin v1.2.0 triggers the build. Image names on ghcr.io must be lowercase. Running tests in containers is covered in Playwright with Docker and Kubernetes.


đŸ–Ĩī¸ Example 3: Staging and Production with Approvals

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [ main ]

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false                   # never cancel a deployment halfway

jobs:
  test:
    uses: ./.github/workflows/playwright-tests.yml     # reusable test workflow
    with:
      base-url: https://qa.example.com
    secrets: inherit

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh staging
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

  smoke-staging:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci && npx playwright install --with-deps chromium
      - run: npx playwright test --grep @smoke --project=chromium
        env:
          BASE_URL: https://staging.example.com

  deploy-production:
    needs: smoke-staging
    runs-on: ubuntu-latest
    environment:
      name: production                         # configured with required reviewers
      url: https://www.example.com
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh production
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

What makes this safe:

FeatureProtection
needs chainProduction only after tests, staging deployment and smoke tests pass
Environment production with required reviewersA person approves the production job
Environment deployment branchesOnly main can deploy to production
Environment secretsStaging and production use different credentials
concurrency without cancelDeployments run one at a time and are never cut off
Reusable workflow for testsOne test definition for every pipeline

The deploy script depends on your hosting (cloud CLI, SSH, Kubernetes, a hosting provider’s action). For cloud providers, prefer OpenID Connect (OIDC): permissions: id-token: write plus the provider’s official login action, so no long-lived cloud keys are stored as secrets.


â†Šī¸ Rolling Back

  • Redeploy the previous version: re-run the deployment workflow for an earlier tag or commit (keep workflow_dispatch with a ref or version input).
  • Or revert the bad commit on main with git revert and let the pipeline deploy again (undo changes).

📝 Assignment

  1. Deploy a simple static HTML page from a repository to GitHub Pages with the Pages workflow.
  2. Publish a Playwright HTML report to Pages from a practice (public, non-sensitive) project.
  3. Create staging and production environments; add yourself as required reviewer for production and restrict it to main.
  4. Build the staging → smoke test → production chain with echo deploy steps and approve the production job.
  5. Push a v0.1.0 tag and build a Docker image to ghcr.io (any small Dockerfile).

❓ FAQ

🤔 How do I deploy with GitHub Actions?

Add a workflow that runs after tests (needs), uses an environment for the target, and runs the deployment step (a Pages action, a Docker push, a cloud CLI or a script) with secrets or OIDC credentials.

🤔 How do I add manual approval before production deployment?

Create a production environment with required reviewers and reference it with environment: production in the deploy job.

🤔 How do I publish a Playwright report with GitHub Actions?

Upload playwright-report/ with actions/upload-pages-artifact and deploy with actions/deploy-pages, or upload it as a normal artifact.

🤔 How do I push a Docker image to GitHub Container Registry?

Log in with docker/login-action to ghcr.io using GITHUB_TOKEN with packages: write permission, then build and push with docker/build-push-action.

🤔 What is OIDC in GitHub Actions?

OpenID Connect lets workflows get short-lived cloud credentials directly from the cloud provider, so you do not store long-lived keys as secrets.


đŸŽ¯ Next Steps

đŸŽŦ Learn Git and GitHub step by step: watch the series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

2 thoughts on “Deploy with GitHub Actions: GitHub Pages, Docker Images and Staged Releases”

Leave a Comment