AI ENGINEER00-06 · Containers, Git & CI
20/90
Sign in

Layer 00 · Foundations · Ch 06

Containers, Git & CI

Turn a working service into a reviewable, repeatable package that automated checks can prove is ready to deploy.

44 min readdifficulty beginnerdepth use

Working code on one laptop is not yet a deployable system.

You need to record the change, let other people review it, package the application consistently, and prove the package passes agreed checks. Git, containers, and continuous integration form that path.

Lesson details

Module
AI engineering foundations
Lesson
6 · Containers, Git & CI
Difficulty
Beginner
Learning time
44 minutes

Before you start

  • Modern Python tooling and tests
  • A small API service
  • Basic data and storage responsibilities

Skills you will gain

  • Create a focused Git branch and commit
  • Explain the difference between an image and a container
  • Read and modify a multi-stage Dockerfile
  • Design a useful GitHub Actions quality workflow
  • Explain the jobs of infrastructure as code and Kubernetes

Why this matters

AI services often depend on Python, system libraries, model clients, parsers, and configuration. A missing package or different runtime can turn a correct change into a failed deployment.

This workflow makes changes reviewable and environments repeatable. It also creates an audit trail: which source commit produced which image, which checks passed, and which version is running.

The intuition

The complete path

FIG 00-06.1 · FROM FOCUSED COMMIT TO THE SAME DEPLOYED IMAGEbranchfocused commitpull requestreviewable changecontinuous integration1 · lint + typecheck2 · tests + accessibility3 · production buildevidence, not hopeimageimmutable tagdeploysame bitsfailure → repair the change
FIG 00-06.1— a branch and pull request enter continuous integration. Fast checks run before slower tests and the production build. Failure returns the change for repair. Success permits review and merge, then produces an image tagged with an immutable identifier for deployment.

The diagram is a chain of evidence. Each stage answers a different question:

  1. Branch: is the change isolated from stable work?
  2. Commit: is there one understandable snapshot?
  3. Pull request: can another person review the intention and evidence?
  4. Continuous integration: does it work from a clean checkout?
  5. Image: are the runtime and dependencies packaged?
  6. Deployment: are we running the exact package that passed?

Step 1 — Record a focused change with Git

Git is a distributed version-control system. Version control records project history and helps people work on changes without overwriting each other.

A repository is the project plus its Git history. A branch is a movable name pointing to a line of commits. A Git commitGit commitA named snapshot of tracked project changes together with its parent history and an explanatory message. Small focused commits make review, debugging, rollback, and collaboration easier because each snapshot represents one coherent change. is a snapshot of tracked changes with an explanatory message and a link to its parent snapshot.

git switch -c feature/health-check
git status
git add src/health.py tests/test_health.py
git diff --cached
git commit -m "feat: add dependency health check"
git push -u origin feature/health-check

These commands deliberately stage exact files. Before committing, git diff --cached shows the snapshot you are about to create.

A useful commit:

  • represents one coherent change;
  • includes the test or documentation needed for that change;
  • does not quietly contain secrets or generated files;
  • uses a message that explains the outcome;
  • can be reverted without removing unrelated work.

Git tracks snapshots, not a magical backup of everything on your computer. Untracked files are not in a commit. Ignored secrets are not protected merely because .gitignore exists; if a secret was committed once, it remains in history until you rotate it and deliberately repair the history.

Branch, merge, and rebase

Two common ways to combine work are:

  • Merge: create a commit joining two histories. Existing commit identities stay unchanged.
  • Rebase: replay commits onto a new base, creating new commit identities and a straighter history.

Neither is universally “correct”. Follow the repository’s collaboration rules. Do not rebase commits that other people are already using unless the team expects rewritten history.

A pull request is a hosted review conversation around a proposed branch. It is not a Git object itself. Reviewers inspect the diff, discuss decisions, and use automated checks as evidence before merge.

Step 2 — Package the runtime as an image

A container imageContainer imageAn immutable, versioned package containing an application, its runtime, its dependencies, and startup instructions. A container runtime starts isolated container processes from the same image, helping development, testing, and production run the same packaged software. is an immutable package of files and startup instructions. A container is a running process created from that image.

This distinction matters:

Image Container
Packaged template Running process
Built and tagged Started and stopped
Immutable layers Has temporary writable state
Stored in a registry Runs on a host

A container registry stores and distributes images. Examples include GitHub Container Registry and cloud-provider registries.

PythonPackage a small FastAPI service
FROM python:3.13-slim

WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
    && uv sync --frozen --no-dev

COPY src ./src

USER 10001
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]

Important lines

FROM python:3.13-slim
Chooses a small base image. In production, pin the image by digest as well as a readable version tag.
COPY pyproject.toml uv.lock ./
Copies stable dependency metadata before frequently changing source so Docker can reuse the dependency layer.
uv sync --frozen --no-dev
Installs the exact locked runtime dependencies and rejects an out-of-date lockfile.
USER 10001
Runs the service without root privileges, reducing the impact of a compromise.
CMD [...]
Uses the executable form so signals reach the server process correctly during shutdown.

This is a teaching example. Exact uv paths and virtual-environment copying depend on how the project configures uv. The production build must actually run in CI; a plausible Dockerfile is not evidence.

Build context and .dockerignore

The build context is the set of files Docker can see during a build. A .dockerignore file prevents unnecessary or sensitive files from entering that context:

.git
.venv
__pycache__
.env
*.pyc
test-results

Do not rely on .dockerignore as secret management. Build secrets need a mechanism that avoids placing secret values in image layers or build logs.

Why layers and ordering matter

Each Dockerfile instruction creates or changes an image layer. Docker can reuse unchanged earlier layers.

If you copy the whole source tree before installing dependencies, changing one README line invalidates every later layer. Copy lockfiles first, install dependencies, then copy source. The expensive dependency layer can remain cached while application code changes.

Step 3 — Use a multi-stage build

A multi-stage build has more than one FROM instruction. One stage contains build tools. A later stage receives only the artefacts needed at runtime.

FIG 00-06.2 · LEAVE BUILD TOOLS OUT OF THE RUNTIME IMAGEbuilder stagelarge · used while buildingcompiler + package toolssource + tests + dev depsbuilt runtime artefactsCOPY onlywhat production needsruntime stagesmaller · deployedPython + runtime depsapplication + non-root userfewer bytes · smaller attack surfacebuild tools stay behind
FIG 00-06.2— the builder stage may contain compilers, source, tests, and development dependencies. Only the required runtime artefacts are copied into the final stage, which runs as a non-root user.
FROM python:3.13-slim AS builder
WORKDIR /build
COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
    && uv sync --frozen --no-dev

FROM python:3.13-slim AS runtime
WORKDIR /app
COPY --from=builder /build/.venv /app/.venv
COPY src ./src
ENV PATH="/app/.venv/bin:$PATH"
USER 10001
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]

Benefits include:

  • fewer packages and files in production;
  • smaller transfer and storage cost;
  • a smaller attack surface;
  • a clearer difference between “needed to build” and “needed to run”.

Smaller is not automatically secure. Scan dependencies, patch base images, avoid secrets, run with minimal permissions, and rebuild regularly.

Step 4 — Rebuild from clean state in CI

Continuous integrationContinuous integrationThe practice of automatically building and checking every proposed code change in a clean environment. A continuous-integration pipeline commonly runs linting, type checks, tests, security checks, and a production build so reviewers receive repeatable evidence before merging., shortened to CI, automatically checks every proposed change in a clean environment.

A CI workflow should provide fast, relevant feedback. Run cheap checks first and expensive checks later or in parallel.

name: Quality

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v6
        with:
          enable-cache: true
      - run: uv sync --frozen
      - run: uv run ruff check .
      - run: uv run mypy src
      - run: uv run pytest
      - run: docker build -t study-api:${{ github.sha }} .

GitHub Actions is GitHub’s workflow service. The file lives under .github/workflows/. A runner is the machine that executes the listed steps.

Important workflow rules:

  • Pin third-party actions to trusted versions; high-security projects may pin full commit SHAs.
  • Use least-privilege permissions.
  • Never print secrets.
  • Give jobs timeouts.
  • Cache downloaded dependencies, not untrusted build output blindly.
  • Cancel superseded runs when only the latest commit matters.
  • Protect main so required checks and review must pass.

CI is not a deployment by itself. Continuous delivery keeps a change deployable and may require approval. Continuous deployment automatically releases every passing change. Teams often use CI plus an explicit production approval.

Step 5 — Tag immutable artefacts

An image tag such as latest can move. It does not prove which source produced the running bytes.

Use an immutable identifier:

ghcr.io/example/study-api:5d3253f4c7...

The full Git commit SHA connects:

source commit → CI run → image digest → deployment

An image digest is a content-derived identifier such as sha256:.... A tag is a human-friendly pointer; a digest identifies exact image content. Production deployment records should preserve both.

Rollback then means selecting a previous known-good image, not rebuilding old source and hoping the toolchain still resolves the same artefact.

LAB 00-06.A · DECIDE WHETHER A CHANGE MAY DEPLOY
IDLE⌘↵ to run

The code is not a CI system. It demonstrates the gate’s logic: deployment is permitted only when every required piece of evidence is present.

Step 6 — Describe infrastructure in version control

Infrastructure as codeInfrastructure as codeA way to describe cloud resources, networks, permissions, and configuration in version-controlled files rather than creating them manually through a dashboard. Tools compare the desired description with real infrastructure and apply reviewed, repeatable changes., shortened to IaC, describes infrastructure in reviewed files.

Instead of clicking “create database” in a dashboard, a configuration may declare:

resource "example_database" "course" {
  engine         = "postgres"
  storage_gb     = 20
  backup_enabled = true
}

An IaC tool calculates a plan: the difference between declared desired state and actual infrastructure. A reviewer inspects the plan before an approved apply changes real resources.

IaC helps with:

  • repeatable environments;
  • review and history;
  • disaster recovery;
  • drift detection;
  • consistent security settings.

It does not make infrastructure changes harmless. A reviewed configuration can still delete data, expose a network, or create large bills. Protect state files, review destructive plans carefully, and separate environments.

IaC basics are important now. Provider-specific modules and remote-state architecture are optional advanced work for teams operating substantial cloud infrastructure.

Step 7 — Know what Kubernetes solves

KubernetesKubernetesA container orchestration system that keeps declared workloads running across a cluster of machines. It schedules containers, replaces failed instances, rolls out new images, exposes services, and scales replicas, but adds operational complexity that small applications may not need. is a container orchestration system. Orchestration means coordinating many running containers across machines.

Core terms:

  • A cluster is the collection of machines and Kubernetes control components.
  • A Pod is the smallest scheduled unit, commonly containing one application container.
  • A Deployment declares an application image and desired number of replicas.
  • A Service gives a stable network address to changing Pods.
  • A ConfigMap holds non-secret configuration.
  • A Secret stores sensitive values, though additional encryption and access controls still matter.

Kubernetes continually compares desired state with observed state. If a Pod dies and the Deployment asks for three replicas, Kubernetes starts a replacement.

It also supports rolling updates, health probes, resource requests, autoscaling, and scheduling. That capability comes with substantial operational complexity.

Do not choose Kubernetes because “production systems use it”. A managed container platform or a single virtual machine may be safer and cheaper for a small service. Kubernetes becomes compelling when you genuinely need coordinated workloads, standardised platform controls, sophisticated rollout, or cluster-level scheduling.

Worked example — ship a document assistant

Trace one change:

  1. Create feature/citation-health-check.
  2. Add a /health/dependencies endpoint and focused tests.
  3. Inspect and commit only the endpoint and tests.
  4. Open a pull request explaining the expected response and failure mode.
  5. CI recreates locked dependencies.
  6. Ruff, mypy, unit tests, accessibility checks, and the production build pass.
  7. CI builds an image tagged with the commit SHA.
  8. A reviewer approves and merges.
  9. A staging environment deploys that exact image digest.
  10. A smoke test checks the endpoint without exposing secret details.
  11. Production approval promotes the same digest.
  12. Monitoring detects elevated failures; rollback selects the previous digest.

Notice what does not happen: production does not run git pull, reinstall today’s newest packages, and build a new untested image on the server. Build once, verify once, promote the same artefact.

In a real AI system

Common mistakes

Putting unrelated fixes, formatting, and upgrades into one large commit.

Fix — Create focused commits and pull requests. Small coherent diffs are easier to understand, test, revert, and trust.

Copying `.env`, credentials, or cloud keys into an image.

Fix — Keep secrets out of source, build context, layers, and logs. Inject them at runtime through the platform's secret mechanism and rotate any exposed value.

Running the application as root inside the container.

Fix — Create or select a non-root user, grant only required filesystem access, and test startup under that identity.

Using `latest` as the only production image identifier.

Fix — Tag with the full source commit and record the content digest. Rollback must identify exact known-good bytes.

Running tests locally but not rebuilding in a clean CI environment.

Fix — CI should start from a fresh checkout, install from the lockfile, run checks, and build the deployable artefact.

Adopting Kubernetes before the team has a real orchestration problem.

Fix — Start with the simplest platform that meets reliability needs. Add Kubernetes when its scheduling and platform capabilities repay its operational cost.

Exercises

warmup · 1

A branch contains a bug fix, formatting changes across fifty unrelated files, and a dependency upgrade. Why is this hard to review, and how would you improve it?

Reveal solution

The reviewer cannot easily separate behavioural risk from mechanical noise, and reverting one concern would also revert the others. Split the work into focused commits or separate pull requests: one for the bug fix with its test, one mechanical formatting change, and one dependency upgrade with its own validation.

core · 2

A Dockerfile copies the entire repository before installing dependencies. Why can changing README.md force an expensive dependency reinstall, and what ordering is better?

Reveal solution

Docker caches a build step only while that step and all earlier inputs remain unchanged. Copying the whole repository invalidates the copy layer whenever README.md changes, so every later dependency layer rebuilds. Copy dependency metadata and the lockfile first, install dependencies, then copy frequently changing application source.

stretch · 3

A team deploys the tag `latest` to production. The new version fails. Explain why rollback is uncertain and propose a safer tagging strategy.

Reveal solution

latest is movable: it does not permanently identify the exact bytes that were deployed, and another push may already have changed it. Tag each image with an immutable identifier such as the full Git commit SHA, record that tag in the deployment, and roll back by redeploying the previous known-good SHA.

Knowledge check

Knowledge check

1What is the difference between an image and a container?
2Why copy dependency files before application source in a Dockerfile?
3Which image identifier is safest for an exact rollback?
4What problem does continuous integration primarily solve?

Practice activity

Practice · 10–20 minutes

Improve a risky Dockerfile

Goal: Rewrite a Dockerfile for reproducibility, cache reuse, and safer runtime permissions.

  1. Pin a readable Python base version.
  2. Set a working directory.
  3. Copy dependency metadata before source.
  4. Install locked runtime dependencies.
  5. Copy only the application source.
  6. Run as a non-root user with an executable-form command.

Expected result: A Dockerfile whose dependency layer survives source-only changes and whose service does not run as root.

Optional challenge: Split it into builder and runtime stages, then explain which files cross the stage boundary.

Reveal solution

Starter Dockerfile:

FROM python
COPY . .
RUN pip install fastapi uvicorn
CMD uvicorn src.app:app

One possible solution:

FROM python:3.13-slim
WORKDIR /app

COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
    && uv sync --frozen --no-dev

COPY src ./src
USER 10001
CMD ["uv", "run", "uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]

Also add .git, .env, .venv, caches, and test output to .dockerignore. In a real repository, run the production build and container smoke test; syntax alone is not proof.

Key takeaways

  • A focused Git commit records one coherent, reviewable snapshot.
  • An image is an immutable package; a container is a running process created from it.
  • Dockerfile ordering and multi-stage builds improve cache reuse and keep build tools out of production.
  • CI must recreate the project from a clean checkout and produce repeatable evidence.
  • Immutable tags and image digests connect deployed bytes to tested source and make rollback dependable.

Flashcards

Flashcards

1 of 8

Revision and interview questions

  1. Trace a code change from branch creation to an identified production image.
  2. Explain three ways to make a Python container image more reproducible or secure.
  3. Design a cheapest-first CI gate for an AI API and background worker.
  4. Compare the problems solved by Docker, infrastructure as code, and Kubernetes.

One-page sketchnote summary

Print me · one-page revision sheet

Containers, Git & CI

Main idea

A reliable delivery path preserves identity: one reviewed commit produces one checked image, and environments promote those same identified bytes.

Core terms

  • Commit — coherent source snapshot
  • Image — immutable application package
  • Container — running image process
  • CI — clean automated evidence
  • IaC — declared infrastructure
  • Kubernetes — container orchestration

One picture

branchfocused commitpull requestreviewable changecontinuous integration1 · lint + typecheck2 · tests + accessibility3 · production buildevidence, not hopeimageimmutable tagdeploysame bitsfailure → repair the change

Code pattern

git diff --cached
git commit
CI: check → test → build
deploy image@sha256:...

Watch out

  • Do not mix unrelated changes
  • Do not copy secrets into images
  • Do not run as root by default
  • Do not deploy only a movable latest tag
  • Do not adopt orchestration without a need

Remember

  • Review focused changes
  • Build once
  • Test from clean state
  • Promote the same image
  • Record exact deployed bytes

Three quick questions

  1. Which commit produced this image?
  2. Did CI build from the lockfile?
  3. Can we roll back exact bytes?

Lesson checklist

0 of 8 complete

Resources

Next up
Maths I: linear algebra & calculus
You can now build, store, package, and deliver a reliable service. Next, learn the mathematical language models use internally: vectors, matrices, similarity, gradients, and the chain rule.