A workflow file that scans, checks, tests and builds on every change, and the habits that make CI worth having.
GitHub Actions is GitHub's built-in CI service. A workflow is a YAML file in .github/workflows/ in your repository. GitHub starts it on events you choose and runs its jobs on a fresh machine.
Cheap checks first. Any failure stops the line.
The example workflow:
name:CIon:push:branches:[main]pull_request:permissions:contents:readjobs:test:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:python-version:"3.12"cache:pip-name:Installrun:pip install -r requirements.txt -r requirements-dev.txt-name:Scan for secretsrun:python secret_scan.py $(git ls-files '*.py' '*.yaml' '*.yml' '*.toml')-name:Lint and format checkrun:ruff check . && ruff format --check .-name:Testrun:pytest -qimage:needs:testruns-on:ubuntu-lateststeps:-uses:actions/checkout@v4-name:Build imagerun:docker build -t ship-it:${{ github.sha }} .
Word
Meaning
on
The events that start the workflow (here: a push to main, and any pull request)
jobs
Groups of steps. Each job runs on its own machine, in parallel unless needs: orders them
uses
Run a ready-made action published in another repository
run
Run a shell command
secrets.NAME
An encrypted value stored in the repository settings and never printed in logs
permissions
What the automatic token may do. Start with read-only
The steps run in order, cheapest first: scan for secrets, check style (ruff is a fast tool for linting, which means spotting likely mistakes, and for formatting), run the tests, and only then build the image. The first red step stops the line, so a quick problem is reported quickly.
On this machine the checks passed: ruff check printed "All checks passed!", ruff format --check said "3 files already formatted", and pytest -q printed 4 passed.
The folder name and the file location
The example keeps the workflow at .github/workflows/ci.yml inside ship-it. In your repository the workflow must be at .github/workflows/ at the top level of the repository, or GitHub will not run it. If you copy ship-it into a larger repository, move that file (and adjust the paths in it).
Good CI habits
Fast. Aim for under five minutes. Cache dependencies (cache: pip).
Required. In the repository settings, make the checks mandatory before merging to main. This is called branch protection.
Reproducible. The pipeline installs from the same pinned requirement files you use locally. requirements.txt here pins exact versions.
Careful with secrets. Give a secret only to the step that needs it. Workflows started by pull requests from forks do not receive secrets, on purpose. Never print them.
Pin third-party actions. Prefer a full commit hash over a tag like @v4 for actions you do not control, to guard against tampering.
Fail loudly. A red build that people learn to ignore makes the pipeline worthless.
From CI to CD
Once CI is green on main, a deploy job can push the image to a registry (a store for images) and update the running service. Keep the order: build once, test that exact build, deploy that same image.
What you will use daily at work
Practice
What it looks like
Multi-stage builds
A first stage installs and compiles, and only the result is copied into a small final image.
Image scanning
Tools scan images for known vulnerable libraries in CI.
Pinned versions or digests
The base image is pinned by version, or by digest for exact repeatability.
A registry
Built images are pushed to a registry and deployed by tag, never rebuilt on the server.
Branch protection and required checks
main cannot be changed unless CI is green and a person has reviewed.
Dependency update bots
Dependabot or Renovate open pull requests for new library versions, and CI checks them.
CI caching
Layer caches and dependency caches keep pipelines under a few minutes.
Environments and approvals
Deploy jobs wait for a person's approval before touching production.
Draft: to be checked against the current Docker and GitHub Actions documentation before this page goes live.
Interview angle
Answer in your head first, then open it.
Why install dependencies before copying the code in a Dockerfile?
Docker caches layers. Libraries change rarely and code often, so installing first means most rebuilds reuse the slow step.
How do you pass secrets to a container?
At run time through environment variables or the platform's secret mechanism, never in the image or the repository.
What does your CI pipeline do?
Scan for secrets, lint and format, run tests, build the image, and block merging when any step fails.
Container versus virtual machine?
A container shares the host's kernel and isolates processes; a VM runs a full guest operating system. Containers are lighter and faster to start.
Why run as a non-root user?
A compromised app then has fewer rights inside, and on the host.