GitHub Actions
GitHub Actions is the CI/CD platform built into GitHub. It lets you automate build, test, and deployment workflows directly from your repository. For LLM applications, a well-designed pipeline ensures every push is validated, every image is scanned, and every deployment is reproducible.
Basic Workflow Structure
Every workflow lives in .github/workflows/ and has three core sections: triggers (on), jobs, and steps.
name: CI Pipeline
on:
push:
branches: [main]
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: |
pip install uv
uv pip install --system -r requirements.txt
- name: Run tests
run: pytest --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
Use pull_request for validation and push to main for deployment. Avoid triggering on every push to every branch — it wastes minutes.
Matrix Builds
Matrix builds let you test across multiple Python versions, operating systems, or dependency sets in parallel:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12"]
exclude:
- os: macos-latest
python-version: "3.10"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements.txt
- run: pytest
The fail-fast: false setting ensures all matrix combinations run even if one fails — critical for understanding the full compatibility picture.
Reusable Workflows
When multiple repositories share the same pipeline logic, use reusable workflows with workflow_call:
# .github/workflows/reusable-test.yml
name: Reusable Test
on:
workflow_call:
inputs:
python-version:
required: false
type: string
default: "3.12"
requirements-file:
required: false
type: string
default: "requirements.txt"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- run: pip install -r ${{ inputs.requirements-file }}
- run: pytest --cov
Call it from any repository workflow:
jobs:
call-tests:
uses: my-org/.github/.github/workflows/reusable-test.yml@main
with:
python-version: "3.12"
secrets: inherit
Use reusable workflows (workflow_call) for multi-job pipelines. Use composite actions (action.yml) for sharing individual steps. Reusable workflows support secrets and matrices; composite actions are simpler and faster.
Secrets Management
Never hardcode API keys or credentials. Use GitHub Secrets and reference them with ${{ secrets.NAME }}:
| Secret Type | Scope | Use Case |
|---|---|---|
| Repository secrets | Single repo | API keys, deploy tokens |
| Environment secrets | Per environment | Production DB passwords |
| Organization secrets | All repos in org | Shared signing keys |
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Requires environment protection approval
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
docker build -t myapp:prod .
docker push registry.example.com/myapp:prod
kubectl apply -f k8s/
Environment Protection Rules
Environments add a gate between staging and production. Configure them in Settings → Environments:
- Required reviewers — require manual approval before deployment
- Wait timer — add a delay (e.g., 5 minutes) for rollback window
- Branch restrictions — only allow deploys from
main - Environment secrets — separate credentials per environment
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "Deploying to staging..."
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # Requires manual approval
steps:
- run: echo "Deploying to production..."
Caching Strategies
Caching dramatically speeds up workflows. Cache dependencies, Docker layers, and build artifacts:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: docker-${{ runner.os }}-${{ hashFiles('Dockerfile') }}
restore-keys: |
docker-${{ runner.os }}-
- name: Build with cache
uses: docker/build-push-action@v5
with:
context: .
push: false
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
Always include the lock file hash (requirements.txt, package-lock.json) in the cache key. Otherwise you'll restore stale dependencies.
Complete Production Pipeline
Here is a production-grade pipeline combining everything:
name: Production Deploy
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
uses: ./.github/workflows/reusable-test.yml
secrets: inherit
security-scan:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
deploy:
needs: [test, security-scan]
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy
run: kubectl rollout restart deployment/myapp
Key Takeaways
- Use matrix builds to validate across Python versions and OS combinations
- Extract shared logic into reusable workflows for DRY pipelines
- Always use secrets for credentials — never hardcode or log them
- Add environment protection rules for production deployments
- Enable caching for dependencies and Docker layers to cut CI minutes