KAHIBARO
Discord Login Register

24.6. GitHub Actions

Why GitHub Actions Matters for Backend Developers

GitHub Actions is GitHub’s built‑in CI/CD system. It lets you automatically run tests, build Docker images, lint code, or deploy your backend every time you push code or open a pull request.

For a backend developer, GitHub Actions is often the quickest way to:

You define all of this as code in YAML files stored in your repository.

Key idea: GitHub Actions = “automation as code” inside your GitHub repository.
You describe workflows in .yml files, and GitHub runs them on events like push, pull_request, or release.


Core Concepts: Workflows, Jobs, Steps

Workflows

A workflow is a full automation process, defined in a YAML file inside .github/workflows/.

Example: .github/workflows/ci.yml

yaml
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: echo "Running tests..."

Important things:

Common triggers for backend projects

TriggerWhen it runsExample use
pushAny push to repo or to selected branchesRun unit tests, lint
pull_requestWhen a PR is opened, synced, or reopenedValidate PR before merge
workflow_dispatchManually triggered from GitHub UIManual deploy
releaseWhen a GitHub Release is created or publishedBuild and publish Docker artifacts
scheduleAt fixed times via cron syntax, e.g. nightlyNightly tests, cleanup tasks

Example: run only on pushes to main:

yaml
on:
  push:
    branches: [ main ]

Jobs

A job runs on one virtual machine (runner). Jobs in a workflow are independent unless you define dependencies.

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps: ...
  build:
    runs-on: ubuntu-latest
    needs: test
    steps: ...

This is useful for backend CI:

  1. test job: run tests and linters.
  2. build job: only run if tests pass, build Docker image or artifacts.
  3. deploy job: only run if build passes and branch is main.

Steps

Each job has steps, which run sequentially inside that job.

Types of steps:

  1. Use an existing action with uses
  2. Run shell commands with run

Example:

yaml
steps:
  - uses: actions/checkout@v4
  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version: '3.12'
  - name: Install dependencies
    run: pip install -r requirements.txt
  - name: Run tests
    run: pytest

Actions and Runners

What is an Action?

An action is a reusable piece of logic you can include in your workflow. It can be:

You include them with uses:

yaml
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/build-push-action@v6

You can search actions in the Marketplace on GitHub.

Typical actions used in backend CI

ActionPurpose
actions/checkoutClone your repository into the runner
actions/setup-pythonInstall and configure Python
actions/cacheCache dependencies to speed up builds
docker/login-actionLog in to a container registry
docker/build-push-actionBuild and push Docker images

What is a Runner?

A runner is the machine that executes your jobs.

Types:

In most beginner backend projects, GitHub‑hosted ubuntu-latest is enough.

Example:

yaml
jobs:
  test:
    runs-on: ubuntu-latest

Creating Your First Workflow

Minimal “Hello World” Workflow

Create a file .github/workflows/hello.yml:

yaml
name: Hello
on: [push]
jobs:
  say-hello:
    runs-on: ubuntu-latest
    steps:
      - name: Print a message
        run: echo "Hello from GitHub Actions!"

What happens?

You can see the results in the “Actions” tab in your repository.

Simple Python Backend CI Workflow

Now a more realistic example for a Python backend that uses requirements.txt and pytest.

.github/workflows/ci.yml:

yaml
name: CI
on:
  push:
    branches: [ main, dev ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run tests
        run: pytest

What this does in plain words:

If any test fails, the workflow fails, and you will see a red “X” on the commit or PR.


Example: CI for a FastAPI Project

Assume you have a FastAPI project with:

You want:

You can create .github/workflows/fastapi-ci.yml:

yaml
name: FastAPI CI
on:
  push:
    branches: [ main, dev ]
  pull_request:
    branches: [ main, dev ]
jobs:
  lint-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install ruff mypy
      - name: Lint with ruff
        run: ruff check .
      - name: Type check with mypy
        run: mypy .
      - name: Run tests
        run: pytest

Key points:

Building and Pushing Docker Images

Most backend deployments use Docker. GitHub Actions can build and push your Docker image automatically.

Example: Build and Push to Docker Hub

Assume:

Workflow .github/workflows/docker.yml:

yaml
name: Build and Push Docker
on:
  push:
    branches: [ main ]
jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: |
            mydockeruser/my-backend:latest
            mydockeruser/my-backend:${{ github.sha }}

What it does:

Using Secrets Safely

In CI/CD, you often need secrets:

Never hardcode secrets in your workflows or source code.

Use GitHub Secrets instead.

Adding a Secret

  1. Go to your GitHub repository.
  2. Click “Settings” > “Secrets and variables” > “Actions”.
  3. Click “New repository secret”.
  4. Name it, for example PRODUCTION_DB_PASSWORD.
  5. Paste the value.

You can access it in workflows with ${{ secrets.NAME }}.

Example:

yaml
- name: Connect to database
  run: |
    echo "Using DB password, but not printing it"
  env:
    DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}

Here, DB_PASSWORD will be available inside the step as an environment variable but will not be printed. GitHub also masks secrets in logs.

Important:
Never echo your secrets directly. For example, echo $DB_PASSWORD will show it in logs. Avoid logging secrets at all times.


Conditional Jobs and Deployments

You usually want to run deployments only from certain branches, for example main or tags.

GitHub Actions supports if: conditions.

Example: Deploy Only from `main`

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      # deployment steps here

You can also combine conditions:

yaml
if: github.ref == 'refs/heads/main' && job.status == 'success'

Useful values in github context:


ExpressionMeaning
github.refRef string, e.g. refs/heads/main
github.event_nameEvent type, e.g. push, pull_request
github.shaCommit SHA

Example: Simple SSH Deployment

For a small backend app on a Linux server, you might want to:

Assume:

Workflow .github/workflows/deploy.yml:

yaml
name: Deploy
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up SSH
        uses: webfactory/ssh-agent@v0.8.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
      - name: Deploy to server
        run: |
          ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF'
          cd /opt/my-backend
          git pull origin main
          docker compose pull
          docker compose up -d
          EOF

This is a simplified example, but it shows the pattern:

  1. Trigger on push to main.
  2. Start an SSH agent using a secret key.
  3. SSH into your server and run deployment commands.

Using Caching to Speed Up Workflows

Dependency installation can be slow. GitHub Actions provides actions/cache to cache files between runs.

Example: Cache Python Dependencies

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run tests
        run: pytest

How it works:

Multi‑Job Pipelines for Backend CI/CD

To combine everything, you can create a multi‑job pipeline:

Example .github/workflows/pipeline.yml:

yaml
name: Backend Pipeline
on:
  push:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Run tests
        run: pytest
  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: mydockeruser/my-backend:${{ github.sha }}
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Set up SSH
        uses: webfactory/ssh-agent@v0.8.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
      - name: Deploy
        run: |
          ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF'
          cd /opt/my-backend
          docker pull mydockeruser/my-backend:${{ github.sha }}
          docker stop backend || true
          docker rm backend || true
          docker run -d --name backend -p 80:8000 mydockeruser/my-backend:${{ github.sha }}
          EOF

Flow:

  1. test runs first.
  2. If tests pass, build runs and pushes a Docker image for this commit.
  3. If build succeeds, deploy runs and uses that image on the server.

This is a simple end‑to‑end CI/CD pipeline for a backend service.


Summary

With these patterns, you can build reliable CI/CD pipelines for your backend projects directly in GitHub.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!