CI/CD Pipelines
CI/CD for Python services runs ruff, pytest, image build, and staged deploy on every merge. Pipelines enforce the same commands developers run locally with uv and pin Python 3.14.0 in CI matrices.
Recipe
# .github/workflows/ci.yml (excerpt)
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --frozen
- run: uv run ruff check .
- run: uv run pytest -qWhen to reach for this:
- Every team merge to main should be deploy-candidate quality
- Docker image build after tests pass
- Promotion dev → staging → prod with approvals
- GitOps tag bump triggers cluster rollout
Working Example
GitHub Actions: test, build/push image, deploy staging with OIDC to AWS.
name: ci-cd
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.14"
- run: uv sync --frozen
- run: uv run ruff check .
- run: uv run pytest -q --cov=app
build:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/ci-ecr-push
aws-region: us-east-1
- uses: docker/build-push-action@v6
with:
push: true
tags: 123456789012.dkr.ecr.us-east-1.amazonaws.com/api:${{ github.sha }}
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "kubectl set image deployment/api api=...:${{ github.sha }}"What this demonstrates:
uv sync --frozenmatches lockfile in CI- OIDC role assumption avoids static AWS keys
- Image tagged with
github.shafor traceability - Deploy job gated on
environment: stagingapproval rules
Deep Dive
Pipeline Stages
| Stage | Gates |
|---|---|
| Lint/test | ruff, mypy optional, pytest |
| Build | Dockerfile, scan image |
| Deploy dev | auto on main |
| Deploy prod | manual approval + smoke test |
Local/CI Parity
uv run ruff check . && uv run pytest -q
docker build -t api:local .Python Notes
- run: uv run python -m build # library packages
- run: uv publish # PyPI when releasing libs not servicesGotchas
- CI uses different Python than prod - subtle bugs. Fix: pin 3.14 in workflow and Docker image.
- Skipping tests on docs-only changes - path filters can miss coupled code. Fix: conservative triggers or monorepo rules.
:latestdeploy tag - rollback impossible. Fix: immutable SHA tags only.- Secrets in workflow YAML - use GitHub Environments secrets and OIDC.
- No smoke test post-deploy - broken health endpoint in prod. Fix: curl
/healthstep after deploy.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| GitLab CI | GitLab hosting | GitHub-only org |
| Buildkite | Self-hosted agents | Simple GitHub Actions enough |
| ArgoCD GitOps | K8s deploy drift detection | Single VM docker compose |
FAQs
What must run in prebuild?
This repo runs lint-docs in prebuild - keep docs valid when shipping content with code.
uv vs pip in CI?
uv faster and lockfile-native - align with manifest uv 0.6+ pin.
How do I cache deps?
setup-uv with cache enabled or Docker buildkit cache mounts for uv sync layer.
Database migrations in CD?
Separate job before traffic shift - never only in app startup without coordination.
How do I promote staging to prod?
Retag tested SHA or replay deploy job with prod environment approval - same artifact, no rebuild.
Feature flags vs branches?
Trunk-based with flags preferred; long-lived env branches rot.
How do I run integration tests?
pytest marker with docker-compose service deps in CI job or ephemeral preview env.
Monorepo paths?
paths: filters per package - but run smoke whole-system tests on shared lib changes.
How does ruff 0.9+ fit?
Single tool for lint+format - fast CI step before pytest.
Rollback strategy?
Redeploy previous image tag - document in Runbook linked from Deployment Best Practices.
Related
- Dockerizing Python - image build
- Testing Infrastructure Code - CI policy patterns
- Zero-Downtime Deploys - rollout safety
- pytest Setup - test layout
- Deployment Best Practices - pipeline checklist
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.