06. CI & Docker
In this section, we will set up Continuous Integration (CI) using GitHub Actions and containerize our application with Docker. This will allow us to automate testing and deployment, ensuring that our application is always in a releasable state.
6.1 Update Project Board
Update Project Board
- Go to your repository on GitHub
- Navigate to the Projects tab
- Open your project board
- Find the issue card for "Introduce CI and Docker" (should be in "Todo" or "Backlog" column)
- Drag the card to the "In Progress" column
6.2 Download Configuration Files
Important: The configuration files for this section are provided in the course repository. Download them before proceeding.
Download from GitHub Repository
You will need to download four files from the cse120-ucm/workshop repository.
- Dockerfile: Click here to view, then click the "Download raw file" icon (or "Raw" button) to save it.
- .dockerignore: Click here to view, then save it.
- test.yml: Navigate to
.github/workflows/test.ymlhere and save it. - release.yml: Navigate to
.github/workflows/release.ymlhere and save it.
Upload to Your Repository
- In your Codespace, create the
.github/workflows/directory if it doesn't exist. - Upload
test.ymlto.github/workflows/test.yml. - Upload
release.ymlto.github/workflows/release.yml. - Upload
Dockerfileto the project root. - Upload
.dockerignoreto the project root.
Note: You can upload files by dragging them into the VS Code file explorer, or by using the "Upload Files" option when right-clicking in the file explorer.
6.3 Review GitHub Actions Workflow
This is a GitHub Actions workflow that runs tests, linting, and type checking on every pull request to the main branch.
Open .github/workflows/test.yml and review its content:
name: Test
on:
pull_request:
branches: [ main ]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install the project
run: uv sync --all-extras --dev
- name: Run tests
run: uv run pytest
- name: Run linters
run: uv run ruff check
- name: Run type checker
run: uv run mypy app
6.4 Review Dockerfile
Docker allows you to package your application with all its dependencies into a container that can run anywhere. Containers are lightweight, portable, and ensure your application runs consistently across different environments.
Open the Dockerfile in your project root and review its content:
FROM python:3.13-slim AS builder
# Minimal system deps (curl for healthcheck only)
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN pip install --upgrade pip uv
# Copy project metadata & sources for build
COPY pyproject.toml ./
COPY README.md ./
COPY app ./app
# Build wheel & sdist into dist/
RUN uv build
FROM python:3.13-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN pip install --upgrade pip uv
# Copy built artifacts from builder stage
COPY --from=builder /app/dist /tmp/dist
# Install wheel (prefer wheel over sdist)
RUN uv pip install /tmp/dist/*.whl --system --no-cache
# Copy only runtime essentials (optional: keep README for transparency)
COPY README.md ./
COPY app ./app
# Expose port (Render sets $PORT; fastapi/uvicorn will bind to it)
EXPOSE 8000
# HEALTHCHECK (optional - Render has native health checks; still useful locally)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8000/health || exit 1
# Default command uses dynamic PORT with fallback
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
6.5 Review Release Workflow
This GitHub Actions workflow automates the release process using semantic versioning and builds/pushes a Docker image to GitHub Container Registry (GHCR) on every merged pull request to main.
Open .github/workflows/release.yml and review its content.
Note: Make sure to check the variable names for app-id and private-key.
name: Release
on:
pull_request:
types: [closed]
branches:
- main
# Global permissions kept minimal; job-level overrides applied.
permissions:
contents: read
concurrency: release
jobs:
release:
name: Build and Release Python Package
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
permissions:
contents: write
packages: write
outputs:
released: ${{ steps.release.outputs.released }}
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}
- id: release
name: Python Semantic Release
uses: python-semantic-release/python-semantic-release@master
with:
github_token: ${{ steps.generate_token.outputs.token }}
- name: Publish Release (GitHub)
if: steps.release.outputs.released == 'true'
uses: python-semantic-release/publish-action@main
with:
github_token: ${{ steps.generate_token.outputs.token }}
tag: ${{ steps.release.outputs.tag }}
docker:
name: Build and Release Docker Image
needs: release
runs-on: ubuntu-latest
if: needs.release.outputs.released == 'true'
permissions:
contents: read
packages: write
id-token: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout (main)
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ needs.release.outputs.version }}
ghcr.io/${{ github.repository }}:latest
labels: ${{ steps.meta.outputs.labels }}
6.6 Commit CI/CD Configuration
Let's commit and push these changes to your repository.
VS Code UI:
1. Go to the Source Control panel (icon with three branches)
2. Stage all changes by clicking the + icon next to Changes
3. In the message box, type:
```
ci: add GitHub Actions workflows and Docker configuration
closes #5
```
Note: Replace `#5` with the actual issue number if it's different.
- Click the checkmark icon to commit
Terminal: Alternatively, you can run the following commands in the terminal:
Stop the FastAPI server if it's running with Ctrl+C. Then open your terminal and run.
git add .
git commit -m "ci: add GitHub Actions workflows and Docker configuration
closes #5"
git push origin fastapi-setup
Note: Replace #5 with your actual CI/CD issue number.
Learn More
Understanding GitHub Actions
GitHub Actions automate your workflow with CI/CD:
Key Concepts: - Workflow: Automated process defined in YAML - Job: Set of steps that execute on the same runner - Step: Individual task (run command, use action) - Runner: Server that runs your workflows - Action: Reusable unit of code
Common Triggers:
- push: When code is pushed
- pull_request: When PR is opened/updated
- schedule: Run on a schedule (cron)
- workflow_dispatch: Manual trigger
Understanding Docker Multi-Stage Builds
Our Dockerfile uses multi-stage builds for efficiency:
- Builder Stage: Compiles and builds the application
- Uses full Python image with build tools
- Installs dependencies
-
Creates wheel distribution
-
Runtime Stage: Runs the application
- Uses minimal Python image
- Only includes runtime dependencies
- Results in smaller, more secure image
Benefits: - Smaller final image size - Faster deployment - Improved security (fewer tools in production) - Better layer caching
Understanding Semantic Versioning
The release workflow uses semantic versioning (semver):
- MAJOR (1.x.x): Breaking changes
- MINOR (x.1.x): New features, backwards compatible
- PATCH (x.x.1): Bug fixes, backwards compatible
Conventional commits automatically determine version bumps:
- feat: → Minor version bump
- fix: → Patch version bump
- feat!: or BREAKING CHANGE: → Major version bump