Chapter 20: Shipping to the World (Deployment & CI/CD)
Learning Objectives
- Understand that code is useless until it runs on a public server.
- Learn the concept of Containers (Docker).
- Understand CI/CD (Continuous Integration / Continuous Deployment).
- Complete the journey from code on a laptop to a live URL.
Prerequisites
Chapter 14: Version Control (Git). Chapter 18: Testing.
Why Does This Exist?
You wrote a web app. It works perfectly on your Mac. You email the code to your friend running Windows. It crashes instantly because they have the wrong version of Python installed.
This is the infamous "It works on my machine" problem.
Furthermore, how do you get this code onto a cloud server (like AWS)? Do you manually drag and drop files using FTP? What if you forget a file? What if the server crashes while copying?
We needed a way to package code into an indestructible, universal box, and an automated assembly line to safely transport that box to the internet.
History
In the 2000s, deploying code was a terrifying event. Companies had "Release Days" once a month where engineers stayed up until 3 AM manually copying files, praying nothing broke.
Then came Docker (2013). Docker allowed engineers to package their code, their language runtime, and their OS configuration into a single "Container".
Following that, CI/CD Pipelines automated the rest. Now, engineers just push to Git, and robots handle testing, building, and deploying in minutes.
Mental Model
Think of Global Shipping.
Before standard shipping containers, dock workers had to figure out how to stack a piano on top of a barrel of oil. It took weeks to load a ship, and things broke constantly.
Then, the standard steel Shipping Container was invented. It doesn't matter if it holds laptops or bananas; the cranes and ships handle the steel box exactly the same way.
Docker is the steel box for software. It doesn't matter if your code is Python, Java, or Ruby. Docker boxes it up, and AWS/Google Cloud cranes know exactly how to run the box.
CI/CD is the automated conveyor belt that takes your code, tests it, puts it in the steel box, and drives it to the server.
Internal Working
A Docker Container is not a full Virtual Machine. It doesn't emulate hardware.
It uses Linux kernel features (cgroups and namespaces) to trick a process into thinking it has its own private operating system, hard drive, and network. It is incredibly lightweight, booting up in milliseconds.
A CI/CD Pipeline (like GitHub Actions) is just a remote cloud computer that listens for Git events. When you git push, it spins up, reads a YAML instruction file, runs your tests, builds the Docker image, and sends it to your production server via SSH or APIs.
Syntax
The instructions to build a steel box are written in a Dockerfile.
1# 1. Start with a base Linux environment that has Python
2FROM python:3.9-slim
3
4# 2. Copy my local code into the box
5COPY . /app
6
7# 3. Set the working directory inside the box
8WORKDIR /app
9
10# 4. Install dependencies
11RUN pip install -r requirements.txt
12
13# 5. Tell the box what to do when it turns on
14CMD ["python", "server.py"]Visual Explanation
Tiny Example
Building and running the box locally.
1# Build the image from the Dockerfile
2docker build -t my_app_image .
3
4# Run the image as a live container on port 8080
5docker run -p 8080:80 my_app_imageCommon Mistakes
Putting Passwords in the Dockerfile
Why it fails: If you write ENV DB_PASS="secret123" in your Dockerfile, that password is permanently baked into the image. Anyone who downloads the image can extract the password.
The Fix: Never bake secrets into the box. Pass them into the box from the outside at the exact moment you turn it on: docker run -e DB_PASS="secret123" my_image.
Debugging
When an app crashes on a cloud server, you don't have a UI or an IDE.
You must read the raw server logs. If using Docker, you SSH into the server and type: docker logs [container_id]. This prints out every print() statement and Stack Trace (Chapter 10) your code generated inside the box.
Mini Project
Time: 30 minutes.
Goal: Your First Pipeline.
Create a GitHub repo with a simple Python script and a Unit Test. Create a folder called .github/workflows and add a file called test.yml. Use GitHub's documentation to write a basic workflow that triggers on push, sets up Python, and runs pytest. Push your code. Watch the green checkmark appear next to your commit on GitHub!
💡 See One Approach (Mini Project)
This is one valid solution — yours may differ.
# Dockerfile for a Python app — annotated line by line
DOCKERFILE = """
# Base image: official Python 3.12 slim (small, secure)
FROM python:3.12-slim
# Set working directory inside the container
WORKDIR /app
# Copy dependency file FIRST (Docker layer cache optimization)
# If requirements.txt hasn't changed, this layer is cached → faster builds
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy rest of application code
COPY . .
# Expose the port the app runs on
EXPOSE 8000
# Run the app
CMD ["python", "main.py"]
"""
# Explain each instruction
lines = [
("FROM python:3.12-slim", "Base image — Python 3.12, minimal OS footprint (~50MB)"),
("WORKDIR /app", "All subsequent commands run from /app inside container"),
("COPY requirements.txt .", "Copy deps file — enables Docker build cache optimization"),
("RUN pip install ...", "Install Python packages INTO the image layer"),
("COPY . .", "Copy app code (after deps — so code changes don't bust dep cache)"),
("EXPOSE 8000", "Documentation: tells Docker which port the app uses"),
("CMD [...]", "Default command to run when container starts"),
]
print("=== Dockerfile Line-by-Line ===
")
for instruction, explanation in lines:
print(f" {instruction:<40} ← {explanation}")
print("""
=== Build & Run commands ===
docker build -t my-app:1.0 .
docker run -p 8000:8000 my-app:1.0
docker ps # List running containers
docker stop <container-id> # Stop a container
""")
Bigger Project
Time: 2 hours.
Goal: Containerize and Deploy.
Take a small web app (like a Flask or Node.js API). Write a Dockerfile for it. Install Docker Desktop and verify it builds and runs locally. Then, sign up for a free tier on a PaaS (Platform as a Service) like Render, Heroku, or Fly.io. Connect your GitHub repo to it. Push your code, and watch the platform automatically build your Docker container and give you a live .com URL!
💡 See One Approach (Bigger Project)
This is one valid solution — yours may differ.
# CI/CD pipeline simulation — what happens on every git push
import time
def step(name, duration=0.1):
"""Simulates a CI/CD pipeline step."""
print(f" ▶ {name}...", end="", flush=True)
time.sleep(duration)
print(" ✅")
return True
def fail_step(name, reason):
print(f" ▶ {name}... ❌ FAILED")
print(f" Reason: {reason}")
return False
class CICDPipeline:
def __init__(self, branch, commit_sha):
self.branch = branch
self.commit = commit_sha[:8]
self.passed = True
def run(self):
print(f"
{'='*55}")
print(f" 🚀 CI/CD Pipeline")
print(f" Branch: {self.branch} | Commit: {self.commit}")
print(f"{'='*55}")
stages = [
("📦 Source", self._source_stage),
("🔨 Build", self._build_stage),
("🧪 Test", self._test_stage),
("🔒 Scan", self._scan_stage),
("🚢 Deploy", self._deploy_stage),
]
for stage_name, stage_fn in stages:
print(f"
{stage_name}")
if not stage_fn():
self.passed = False
print(f"
❌ Pipeline FAILED at: {stage_name}")
print(f" 📩 Notification sent to team Slack channel.")
return
print(f"
🎉 Pipeline PASSED — {self.commit} is live!")
def _source_stage(self):
step("Checkout code from Git")
step("Install dependencies (pip/npm)")
return True
def _build_stage(self):
step("Compile / transpile code")
step("Build Docker image")
step("Tag image as {self.commit}")
return True
def _test_stage(self):
step("Run unit tests (342 tests)")
step("Run integration tests (87 tests)")
step("Calculate code coverage (94.2%)")
# Simulate a coverage check
coverage = 94.2
if coverage < 80:
return fail_step("Coverage gate", f"{coverage}% < 80% minimum")
step(f"Coverage gate: {coverage}% ✓")
return True
def _scan_stage(self):
step("Lint check (ruff / eslint)")
step("Security scan (bandit / snyk)")
step("Dependency vulnerability scan")
return True
def _deploy_stage(self):
if self.branch == "main":
step("Push Docker image to registry")
step("Deploy to STAGING environment")
step("Run smoke tests on staging")
step("Blue/green deploy to PRODUCTION")
step("Health check production endpoint")
elif self.branch.startswith("feature/"):
step("Deploy to PREVIEW environment")
print(f" 📎 Preview URL: https://preview-{self.commit}.app.example.com")
else:
step("No deployment (branch: not main or feature)")
return True
# Simulate two pipeline runs
CICDPipeline(branch="feature/user-auth", commit_sha="abc123def456").run()
CICDPipeline(branch="main", commit_sha="9f8e7d6c5b4a").run()
Production Usage
Netflix, Amazon, and Google don't just run 1 Docker container. They run hundreds of thousands of them.
To manage this massive fleet, they use Kubernetes. Kubernetes is a master AI that monitors the containers. If a server rack catches on fire and 1,000 containers die, Kubernetes instantly boots up 1,000 exact clones on a different server rack. The user never notices a thing.
Best Practices
- Stateless Containers: A container should be ephemeral (easily destroyed and recreated). Never save uploaded user profile pictures inside the container's hard drive. If the container crashes, the photo is lost forever. Save files to external cloud storage (like AWS S3) and data to external Databases.
- Zero Downtime Deployments: A good CI/CD pipeline starts the new V2 container, waits for it to report "Healthy", routes user traffic to it, and then destroys the old V1 container. The user experiences zero interruption.
Interview Questions
🟢 Easy:What is the difference between Continuous Integration (CI) and Continuous Deployment (CD)?
🔍 Reveal Answer
🟡 Medium:Why use Docker instead of just installing Python directly on the server?
🔍 Reveal Answer
🔴 Hard:Explain a Blue/Green Deployment strategy.
🔍 Reveal Answer
Revision Sheet
- Docker: The universal steel shipping container for code.
- CI/CD: The automated robot assembly line (Test -> Build -> Deploy).
- Logs: Your only window into what a remote server is doing.
- The Final Goal: From a blank text file on Chapter 1, to a highly-available, globally-distributed, automated software system.
Connections
- The End of Volume 1: You have journeyed from the absolute basics of Variables (State) in RAM, through Loops and Data Structures, to Architecture, Testing, and finally Global Deployment. You now possess the foundational mental models to build anything.