Redirect Chapter 28: Docker & Git Essentials | AI Fundamentals
← Back to Tutorials Chapter 28

Docker & Git Essentials

The Docker Series: What Docker Is

By this point in the book you have trained models, but you have not yet shipped one. The hardest part of deploying machine learning is not the model itself; it is reproducing the exact environment where the model ran. A model that works on your laptop can fail instantly on a colleague's machine because a library version differs by one digit.

Docker solves this by packaging an application together with everything it needs to run: the code, the runtime, libraries, environment variables, and configuration. The resulting bundle is called a container. Containers are lightweight, isolated environments that run on any machine that has Docker installed, so a container built once runs identically on a laptop, a server, or in the cloud.

Memory hook: The old slogan "it works on my machine" becomes irrelevant. With Docker, your container is the machine, so "works" travels with the container.

Images vs Containers

Beginners often mix up the two central terms. An image is an immutable template; a container is a running instance of that template.

You build images with a file called Dockerfile, then docker run starts a container from that image whenever you need it.

Docker vs Virtual Machines

Virtual machines (VMs) also isolate environments, but the two approaches work very differently under the hood:

Neither tool replaces the other. Teams often run VMs for strict isolation and infrastructure boundaries, while containers become the default for packaging applications.

Installing Docker

  1. Download Docker Desktop from the official Docker website for Windows or macOS. Linux users install the Docker Engine package through their package manager.
  2. On Windows, Docker Desktop requires the Windows Subsystem for Linux (WSL 2). The installer guides you through enabling it.
  3. After installation, open a terminal and verify the install: docker --version.
  4. Confirm the engine is running with docker info, or by checking the Docker Desktop icon in the system tray.
Heads-up: Docker Desktop is a graphical application. Even if you run everything from the terminal, Docker Desktop must stay running in the background, otherwise commands return a "cannot connect to the Docker daemon" error.

Essential Docker Commands

The daily workflow of a Docker user revolves around a handful of commands:

docker pull python:3.11       # download an image
docker run python:3.11        # create and start a container
docker ps                     # list running containers
docker ps -a                  # list all containers, stopped or running
docker stop friendly_lewis    # stop a container by name or ID
docker build -t my-app .      # build an image from a Dockerfile
docker exec -it my-app bash   # open a shell inside a running container
docker images                 # list local images

Creating a Docker Image with a Dockerfile

A Dockerfile is a plain text file of instructions that Docker executes top to bottom to build an image. A minimal machine learning example looks like this:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]

Build the image from the same folder as the Dockerfile:

docker build -t my-app:latest .

Pushing an Image to Docker Hub

To share an image, upload it to a registry. Docker Hub is the default public registry.

  1. Create a free account at hub.docker.com and log in from the terminal: docker login.
  2. Tag the image with your username so the registry knows where it belongs: docker tag my-app username/my-app:v1.
  3. Upload it: docker push username/my-app:v1.
  4. Anyone can then pull it: docker pull username/my-app:v1.

Use descriptive tags such as v1.0 or latest; the tag is the version label of the image.

Docker Compose

Real projects rarely consist of a single container. A web app might need an API container, a database container, and a model-serving container together. Docker Compose lets you describe the whole stack in one file and start everything with a single command.

version: "3"
services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret

Run docker compose up to build and start all services, and docker compose down to stop them. Compose wires the services together on a shared network, so the web service can reach the database simply by its service name.

Heads-up: Older Docker versions used the command docker-compose (with a hyphen) as a separate binary. Modern versions prefer docker compose as a sub-command. The syntax is otherwise the same.

Git: Version Control for Everything

While Docker solves the "runs everywhere" problem, Git solves the "who changed what and when" problem. Git is a distributed version control system that records the complete history of a project. Every change becomes a commit, and you can always compare, revert, or branch the history.

For machine learning, Git matters because experiments multiply quickly. A model trained on a different data split, a refactored preprocessing step, a changed seed, and suddenly you cannot remember which version produced your best result. Git gives you a timestamped, labelled history of every step.

Installing Git

  1. Download the Git installer from the official Git website and run it, accepting the defaults for a beginner.
  2. Verify the installation in a new terminal: git --version.
  3. Set your identity once, because every commit records who made it:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

First Steps: init, add, commit, status

Start versioning a folder by turning it into a repository:

git init                 # create a new repository in the current folder
git status               # show what has changed since the last commit
git add model.py         # stage a single file
git add .                # stage every changed file
git commit -m "add model script"

Never commit secrets or large binary files. Add a .gitignore file listing patterns such as *.csv or .env so Git ignores them.

merge, push, checkout, and log

As projects grow you create branches: separate lines of development. The default branch is usually called main. The commands below cover the essentials:

git log --oneline         # show the commit history in compact form
git checkout feature       # switch to an existing branch
git checkout -b experiment # create and switch to a new branch
git merge feature          # fold the feature branch into the current branch
git push origin main       # upload local commits to a remote repository
Tip: A very common pattern is git checkout -b to start a branch, commit your work, then git checkout main and git merge the branch back. Branches are cheap, so create one for every idea.

Resolving a Merge Conflict

A merge conflict occurs when two branches changed the same lines of the same file in different ways, and Git cannot decide which version wins. Git does not panic; it pauses the merge, marks the conflicting file, and lets you decide.

The conflicted file contains markers that Git inserts. A snippet of a Python file might look like this:

def learning_rate():
<<<<<<< HEAD
    return 0.001
=======
    return 0.01
>>>>>>> tuning-branch

Resolving the conflict is a manual edit:

  1. Open the file in your editor and read both versions.
  2. Delete the conflict markers (<<<<<<<, =======, >>>>>>>) and keep the code you want, or combine both ideas.
  3. Stage the resolved file: git add filename.py.
  4. Finish the merge: git commit. Git supplies a default merge message you can keep or edit.

Conflicts look intimidating the first time, but they are a normal part of collaborative development. The golden rule is to read the two sides, choose deliberately, and never delete a colleague's work without saying so.

Exercise: Build a minimal Docker image that runs python -c "print('containerized!')", run it, and push it to Docker Hub under your username. Then create a Git repository for a small project, make two branches that edit the same line, merge them to provoke a conflict, and resolve it. Confirm with git log --oneline that both branches are now part of the history.