Exercises: CI/CD Basics — GitHub Actions Mini-Project for New Hires
One-line learning outcome: Gain practical experience writing GitHub Actions workflows that run tests, lint code, build a Docker image, and deploy to a target, with verifiable checkpoints.
Estimated total time: 6–8 hours (can be split across days)
Audience: Junior developers, bootcamp grads, new hires, and mentors building onboarding exercises.
Prerequisites
- A small app repo (Node, Python, or Go) with tests — this guide uses a Python Flask example but shows alternatives.
- GitHub account and repository
- Familiarity with Git and basic Docker
Exercise roadmap (4 progressive tasks)
- Task 1 — Run tests on pull requests (1–1.5 hrs)
- Task 2 — Add linting and type checks to CI (1–1.5 hrs)
- Task 3 — Build and publish a Docker image on merge (2 hrs)
- Task 4 — Deploy a built image to a host (1.5–2 hrs)
Task 1: Run tests on pull requests
Goal: Create a workflow that runs pytest (or chosen test runner) on PRs and reports status.
Hands-on lab
- Create workflow folder and file
mkdir -p .github/workflows
cat > .github/workflows/ci.yml <<'YML'
name: CI
on:
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest -q
YML
- Open a pull request with a small test that fails to observe workflow reporting.
Expected outcome
The workflow runs and GitHub shows the checks on the PR; failing tests show red X and logs show pytest output.
Exercises
- Add a matrix strategy to test against multiple Python versions. (Medium)
- Make tests run on push to main with a tag naming convention. (Medium)
Checkpoint: PRs trigger CI and failing tests block merge by default (set branch protection if available).
Task 2: Add linting and type checks
Goal: Integrate linters (flake8/black) and optional type checks (mypy) into CI.
Hands-on lab
# Add to requirements-dev.txt
flake8==5.0.4
black==23.1.0
mypy==1.2.0
# Update workflow steps
- name: Lint
run: |
pip install -r requirements-dev.txt
black --check .
flake8
mypy .
Exercises
- Fail the workflow on lint errors and suggest fixes in PR comments using an action. (Medium)
- Auto-format with black in a separate workflow on push to a dev branch. (Medium)
Checkpoint: Lint and type checks run in CI; PRs with style issues fail the check.
Task 3: Build and publish a Docker image
Goal: Build an image on merge to main and push to Docker Hub or GitHub Packages.
Hands-on lab
- Create a Dockerfile at repo root (simple example for Flask)
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["gunicorn","-w","2","-b","0.0.0.0:8080","app:app"] - Add workflow steps to build and push (example uses Docker Hub secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN)
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/my-app:${{ github.sha }}
Exercises
- Tag images with semantic versions using git tags. (Medium)
- Scan images for vulnerabilities using a security scanner action and fail on high severity. (Medium)
Checkpoint: A new image is visible in the registry after merging to main.
Task 4: Deploy the built image to a host
Goal: Automate deployment of the image to a host such as Render, Heroku (container registry), or a simple VM via SSH.
Hands-on lab (using Render/Heroku as example)
- For Render: connect repo, set service as "Web Service" and pick the Docker build option; alternatively configure the Render deploy step to pull image from Docker Hub.
- For Heroku Container Registry:
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: 'my-app'
dockerfile_path: './Dockerfile'
Exercises
- Implement a blue-green deployment using two services and a swap step. (Hard)
- Notify Slack on successful deploy with an action and include changelog summary. (Medium)
Checkpoint: Merging to main triggers a deploy and the live site shows the new version tag or commit SHA.
Testing & Assessment rubric
- Workflow correctness (40%): workflows trigger as specified and complete successfully.
- Reproducibility (25%): others can run the same repo locally and in CI using documented steps.
- Security & Secrets (15%): sensitive info stored as secrets, images scanned if required.
- Extras (20%): tagging, notifications, performance of deploys.
Sample solution sketches
See solutions/ci branch: contains one workflow for PR tests + lint + matrix; a "release" workflow that builds, tags by semver (when tag pushed), pushes image to Docker Hub, and triggers a simple deploy job.
Alternative languages & trade-offs
- Node.js: use npm test and a Node matrix. Docker image build is similar; watch for differing base images.
- Go: single static binary image reduces attack surface and build time in CI.
Recommended tooling & reading
- GitHub Actions docs, docker/build-push-action, docker/login-action
- Security scanning: Trivy, Snyk
- Optional: Dependabot, Renovate for dependency updates
Common stumbling blocks & debugging hints
- Secrets missing: check repo settings and ensure names match in workflow.
- Runner caching: use actions/cache for pip/npm to speed CI.
- Auth failures to registry: verify token permissions and correct registry URL.
Accessibility considerations
- Include captions/transcripts for demo screencasts and use alt text for images in READMEs.
- Provide command-line outputs in text form for screen readers.
Finish this mini-project by writing a short onboarding doc that explains how CI/CD is organized in your repo and lists how to roll back a bad deployment (30–60 minutes). That document makes an excellent interview- or onboarding artifact.