This episode takes the application to production: CI pipelines with GitHub Actions, caching and matrix builds for many Python versions, multi-stage Docker, running Gunicorn and Uvicorn, and deployment targets like Cloud Run, AWS Lambda, and Kubernetes.

Code that passes local tests isn't enough. Episode 20 takes you into the world of CI/CD and deployment: building automated pipelines with GitHub Actions, containerizing the application with Docker, and deploying to modern production targets.
We'll cover caching and matrix builds, multi-stage Docker, the Gunicorn and Uvicorn combination, and deployment targets like Cloud Run, AWS Lambda, and Kubernetes. By the end of the episode, you'll have a flow from commit to production.
GitHub Actions runs automated workflows from a YAML file:
name: CI
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"
- run: pip install -e ".[dev]"
- run: pyteston: push: branches: [main] triggers the workflow on push to main. actions/setup-python@v5 sets up Python 3.12, then runs install and test. Every commit is automatically tested — bugs are caught before they reach production.
Caching speeds up the pipeline by reusing dependencies:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -e ".[dev]"cache: "pip" makes GitHub Actions store the dependency cache between runs. Subsequent pipelines use the cache, drastically cutting install time. Caching is the standard for efficient CI.
A matrix build runs the same job for several versions:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- run: pip install -e ".[dev]"
- run: pytestmatrix: python-version: ["3.11", "3.12", "3.13"] runs the job for three Python versions. ${{ matrix.python-version }} is filled per combination. Matrix builds guarantee your library works on all supported versions.
Docker containerizes the application. Multi-stage separates build and runtime:
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml .
COPY src .
RUN pip wheel --no-cache-dir --wheel-dir /wheels .
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /wheels /wheels
COPY --from=builder /app /app
RUN pip install --no-cache-dir /wheels/*
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]The builder stage builds the dependency wheels, and the runtime stage only copies the built artifacts. The final image is small and secure because it doesn't include build tools. CMD runs Uvicorn on port 8000.
For production, combine Gunicorn as the process manager and Uvicorn as the worker:
gunicorn app.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000gunicorn app.main:app runs the application with four workers. --worker-class uvicorn.workers.UvicornWorker uses Uvicorn as an async worker. Gunicorn manages the process lifecycle while Uvicorn handles requests asynchronously — the best combination for FastAPI.
Cloud Run runs containers without managing servers:
gcloud run deploy belajar-api \
--image gcr.io/proyek/belajar-api \
--platform managed \
--region asia-southeast1 \
--allow-unauthenticatedgcloud run deploy belajar-api deploys the image to Cloud Run. The platform manages automatic scaling — from zero to thousands of requests per second. You only pay when there's traffic. Cloud Run suits stateless APIs.
Kubernetes orchestrates containers across many nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: belajar-api
spec:
replicas: 3
selector:
matchLabels:
app: belajar-api
template:
metadata:
labels:
app: belajar-api
spec:
containers:
- name: belajar-api
image: gcr.io/proyek/belajar-api:v0.1.0
ports:
- containerPort: 8000kind: Deployment declares the application with three replicas. Kubernetes keeps the replicas running, performs rolling updates, and manages scaling. Choose Kubernetes when you need full control over large-scale infrastructure.
Key takeaways:
In the next episode, episode 21, we'll cover observability, monitoring, and maintenance — metrics with Prometheus, tracing with OpenTelemetry, structured logging, error monitoring with Sentry, plus performance budgets and alerting. Your application will be observable and well maintained!