YouTube Stats Tracker API
Build a REST API with FastAPI and package it into a Docker container.
Introduction
30 Second Summary
Every YouTube creator refreshes their video page to watch view counts climb. What if you could build your own backend tool that tracks those numbers over time and serves the data through a professional web API?
In this project, you will build a REST API that records YouTube video statistics, then package it inside a Docker container so it can run on any machine.
What You'll Build
A FastAPI web application with four endpoints for tracking YouTube video stats, containerized with Docker for portable deployment.
By the end of this project, you'll have:
- A working REST API that accepts YouTube video IDs, stores view and like snapshots, and returns stats history.
- A Dockerfile that packages the Python app into a portable Linux container you could share with anyone.
- A running Docker container serving the API on localhost with interactive Swagger UI documentation.
- Secret Mission: Connect the API to the real YouTube Data API to auto-fetch live stats instead of manual entry.
Are there any prerequisites?
This project assumes intermediate Python knowledge and basic comfort with the command line. You should already have VS Code and Python 3.13 installed on your machine.
Install Docker Desktop and Set Up the Project
To build and run your YouTube Stats Tracker API, you need two things: a way to write and serve the API (FastAPI), and a way to package it into a portable container (Docker).
In this step, you will install Docker Desktop, create your project folder, set up a Python virtual environment, and scaffold the files your API needs.
In this step, get ready to:
- Install Docker Desktop and verify it is working.
- Create a project folder with a Python virtual environment and install FastAPI.
- Create the project file structure.
Install Docker Desktop with WSL 2 backend
Docker Desktop lets you run Linux containers on your local machine. Your API will eventually run inside one of these containers.
- Open your browser and navigate to the Docker Desktop download page.
- Click Download for Windows to download Docker Desktop Installer.exe.
- Double-click Docker Desktop Installer.exe to run the installer.
- When asked about installation type, choose per-user installation (no admin rights required).
- On the Configuration page, ensure Use WSL 2 instead of Hyper-V is selected.
- After installation completes, press the Windows key, type Docker Desktop, and press Enter to launch it.
- Accept the license agreement when prompted.
What is Docker Desktop?
Docker Desktop is a desktop application that lets you build and run containers on your machine. It uses WSL 2 (Windows Subsystem for Linux) to run a real Linux kernel on Windows, which is how your container will run a Linux-based environment.
Docker Desktop is free for personal use, education, and small businesses.
Wait for Docker Desktop to finish starting up (the whale icon in your system tray stops animating).
- In VS Code, open a terminal by pressing Ctrl+` (backtick key, above Tab).
- Verify Docker is installed by running this command:
docker --version
✔️ I see a version number
Docker is installed and ready. You should see output like Docker version 27.x.x or similar. Move on to the next verification below.
ⓧ Command not found
Docker Desktop may not have finished installing, or the terminal needs to be restarted.
- Close and reopen your terminal.
- Make sure Docker Desktop is running (check the system tray for the whale icon).
- If it still fails, restart your computer and launch Docker Desktop again.
- Confirm Docker can run containers by running:
docker run hello-world
You should see a message from Docker confirming that your installation is working correctly.
Docker run failed?
Make sure Docker Desktop has fully started. The whale icon in the system tray should be steady (not animating). If you see a message about WSL, ensure WSL 2 is installed on your system by running wsl --version in PowerShell.
Help me troubleshoot Docker Desktop not starting
Create the project folder and Python virtual environment
A virtual environment keeps your project's Python packages isolated from the rest of your system. This prevents version conflicts between projects.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir yt-stats-tracker
cd yt-stats-tracker
- Create a Python virtual environment by running:
python -m venv venv
- Activate the virtual environment by running:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
- Install FastAPI and its standard dependencies by running:
pip install "fastapi[standard]"
What is FastAPI?
FastAPI is a modern Python web framework for building APIs. It automatically validates incoming data using Pydantic and generates interactive documentation (Swagger UI) for free.
- Verify FastAPI installed correctly by running:
python -c "import fastapi; print(fastapi.__version__)"
You should see 0.136.3 (or a similar version number) printed in the terminal.
Seeing a ModuleNotFoundError?
Make sure your virtual environment is active. You should see (venv) at the start of your prompt. If not, run the activation command again.
Help me fix my FastAPI installation
Create the project file structure
Your project needs an app directory to hold the API code, plus a requirements.txt file that pins your dependencies for Docker to use later.
- Create the app directory by running:
mkdir app
- Create the two Python files inside the app folder by running:
macOS
touch app/__init__.py app/main.py
Windows
New-Item -ItemType File -Path "app/__init__.py", "app/main.py"
What is __init__.py?
The __init__.py file tells Python that the app folder is a package (a collection of modules that can be imported). It can be empty.
- Create the requirements.txt file with the pinned FastAPI version by running:
macOS
echo "fastapi[standard]==0.136.3" > requirements.txt
Windows
Set-Content requirements.txt "fastapi[standard]==0.136.3"
Why pin the version?
Pinning fastapi[standard]==0.136.3 ensures that when Docker builds your container later, it installs the exact same version you developed with. This prevents surprise breakages from newer releases.
- Confirm all your files were created by running:
ls
You should see app, requirements.txt, and venv listed in the output.
- Verify the files inside your app folder by running:
ls app
You should see __init__.py and main.py listed.
Missing files?
Double-check that you ran the file creation commands from inside the yt-stats-tracker folder. Run pwd (macOS) or Get-Location (Windows) to confirm your current directory ends with yt-stats-tracker.
Help me find my missing project files
✔️ Awesome, I've got everything!
Great. Your project structure is ready for the next step.
ⓧ I'd like to double check the full code
Here is what requirements.txt should contain:
fastapi[standard]==0.136.3
The files app/__init__.py and app/main.py should both be empty at this point.
Your environment is locked and loaded. Next up, you will build the actual FastAPI application with endpoints that track YouTube video stats.
Build the FastAPI Application
Your project folder is set up and FastAPI is installed. Now it's time to build the actual API that tracks YouTube video statistics over time.
You'll define the shape of data your API accepts and returns, set up a SQLite database to store snapshots, and wire up four endpoints that handle everything from health checks to retrieving a video's full stats history.
In this step, get ready to:
- Define Pydantic data models for request and response validation.
- Create a SQLite database layer that initializes on app startup.
- Implement four API endpoints for health checks, video tracking, and snapshot history.
Create the data models with Pydantic
Before writing any endpoint logic, you need to define the shape of data flowing in and out of your API. Pydantic models handle this by validating incoming requests and structuring outgoing responses automatically.
- Open app/main.py in VS Code and add the following imports and model definitions:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from contextlib import asynccontextmanager
from datetime import datetime, timezone
import sqlite3
import os
# --- Data Models ---
class VideoCreate(BaseModel):
video_id: str
title: str | None = None
class SnapshotCreate(BaseModel):
views: int
likes: int
class SnapshotResponse(BaseModel):
video_id: str
views: int
likes: int
recorded_at: str
class VideoResponse(BaseModel):
video_id: str
title: str | None
added_at: str
What does this code do?
The imports bring in everything you'll need for the full application: FastAPI itself, Pydantic for validation, SQLite for storage, and datetime utilities for timestamps.
- VideoCreate defines what the API accepts when you register a video to track. The title field is optional.
- SnapshotCreate defines what the API accepts when you record a stats snapshot (view count and like count).
- SnapshotResponse and VideoResponse define what the API sends back. FastAPI uses these to validate and serialize outgoing data.
- Save app/main.py.
- You won't see a change yet. The next substep builds the database layer that stores and retrieves this data.
Create the SQLite database layer
Your models define the shape of data, but you need somewhere to actually store it. Python's built-in sqlite3 module gives you a lightweight database with zero setup.
- In app/main.py, add the following database layer below your model definitions:
# --- Database ---
DB_PATH = os.environ.get("DB_PATH", "tracker.db")
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_db()
conn.execute("""
CREATE TABLE IF NOT EXISTS videos (
video_id TEXT PRIMARY KEY,
title TEXT,
added_at TEXT NOT NULL
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id TEXT NOT NULL,
views INTEGER NOT NULL,
likes INTEGER NOT NULL,
recorded_at TEXT NOT NULL,
FOREIGN KEY (video_id) REFERENCES videos(video_id)
)
""")
conn.commit()
conn.close()
# --- App Lifespan ---
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
yield
What does this code do?
- DB_PATH reads the database file location from an environment variable. It falls back to tracker.db in the current directory if the variable is not set.
- get_db() opens a connection to the SQLite database. Setting row_factory = sqlite3.Row lets you access columns by name instead of index.
- init_db() creates two tables: videos (stores registered video IDs) and snapshots (stores view/like counts with timestamps).
- The lifespan function is an async context manager that runs init_db() once when the app starts. This ensures your tables exist before any request arrives.
Why SQLite instead of a bigger database?
SQLite is built into Python's standard library. There's nothing extra to install or configure, and it stores everything in a single file. For learning how APIs interact with databases, it removes all the setup noise so you can focus on the SQL and the endpoint logic.
- Save app/main.py.
- You won't see a change yet. The database file gets created when the app starts, which requires endpoints first.
Implement the four API endpoints
With the data models and database in place, you can now create the REST API endpoints that tie everything together. Each endpoint handles one specific operation on your video tracker.
- In app/main.py, add the FastAPI app instance and health check endpoint below the lifespan function:
# --- FastAPI App ---
app = FastAPI(
title="YouTube Stats Tracker",
description="Track view and like counts for YouTube videos over time.",
version="1.0.0",
lifespan=lifespan,
)
# --- Endpoints ---
@app.get("/")
def health_check():
return {"name": "YouTube Stats Tracker API", "status": "running"}
What does this code do?
- app = FastAPI(...) creates the application instance. The lifespan=lifespan parameter hooks up the database initialization you wrote earlier.
- The @app.get("/") decorator creates a GET endpoint at the root URL. It returns a simple JSON object confirming the API is running.
- Save app/main.py.
- You won't see a change yet. The next chunk adds the video registration endpoint.
- Add the POST /videos endpoint below the health check:
@app.post("/videos", response_model=VideoResponse, status_code=201)
def add_video(video: VideoCreate):
conn = get_db()
# Check if the video is already being tracked
existing = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video.video_id,)
).fetchone()
if existing:
conn.close()
raise HTTPException(status_code=409, detail="Video already tracked")
# Insert the new video with a UTC timestamp
added_at = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT INTO videos (video_id, title, added_at) VALUES (?, ?, ?)",
(video.video_id, video.title, added_at),
)
conn.commit()
conn.close()
return VideoResponse(video_id=video.video_id, title=video.title, added_at=added_at)
What does this code do?
- The @app.post("/videos") decorator creates a POST endpoint. FastAPI automatically validates the request body against your VideoCreate model.
- The function first checks if the video ID already exists. If it does, it raises a 409 Conflict error to prevent duplicates.
- response_model=VideoResponse tells FastAPI to validate and serialize the response using that model. status_code=201 returns a "Created" HTTP status instead of the default 200.
- Save app/main.py.
- You won't see a change yet. The final two endpoints complete the snapshot tracking functionality.
- Add the snapshot recording and history retrieval endpoints below add_video:
@app.post("/videos/{video_id}/snapshots", response_model=SnapshotResponse, status_code=201)
def record_snapshot(video_id: str, snapshot: SnapshotCreate):
conn = get_db()
# Verify the video exists before recording a snapshot
video = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video_id,)
).fetchone()
if not video:
conn.close()
raise HTTPException(status_code=404, detail="Video not found. Add it first.")
# Record the snapshot with a UTC timestamp
recorded_at = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT INTO snapshots (video_id, views, likes, recorded_at) VALUES (?, ?, ?, ?)",
(video_id, snapshot.views, snapshot.likes, recorded_at),
)
conn.commit()
conn.close()
return SnapshotResponse(
video_id=video_id,
views=snapshot.views,
likes=snapshot.likes,
recorded_at=recorded_at,
)
@app.get("/videos/{video_id}/history", response_model=list[SnapshotResponse])
def get_history(video_id: str):
conn = get_db()
# Verify the video exists before querying history
video = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video_id,)
).fetchone()
if not video:
conn.close()
raise HTTPException(status_code=404, detail="Video not found")
# Retrieve all snapshots sorted by time
rows = conn.execute(
"SELECT video_id, views, likes, recorded_at FROM snapshots WHERE video_id = ? ORDER BY recorded_at",
(video_id,),
).fetchall()
conn.close()
return [SnapshotResponse(**dict(row)) for row in rows]
What do these endpoints do?
- POST /videos/{video_id}/snapshots records a view/like count at the current moment. The {video_id} in the URL is a path parameter that FastAPI extracts automatically.
- GET /videos/{video_id}/history retrieves every snapshot ever recorded for a video, sorted from oldest to newest. This is what makes it a "tracker" rather than a single reading.
- Both endpoints first verify the video exists in the database. If it doesn't, they return a 404 error with a helpful message.
- Save app/main.py.
- Start the development server by running this command in your terminal:
fastapi dev app/main.py
You should see output indicating the server is running at http://127.0.0.1:8000.
- Open your browser and navigate to http://127.0.0.1:8000/docs.
- You should see FastAPI's interactive Swagger UI with all four of your endpoints listed.
What is Swagger UI?
FastAPI automatically generates interactive API documentation from your code. The Swagger UI at /docs lets you try every endpoint directly in the browser without writing a single line of client code.
Server not starting?
- Check that your virtual environment is still active. You should see (venv) at the start of your terminal prompt. If not, run venv\Scripts\activate again.
- Make sure you're running the command from the yt-stats-tracker folder (the one containing the app/ directory).
- If you see an import error, double check that your app/main.py has all six imports at the top of the file.
Help me debug why my FastAPI server won't start.
✔️ Awesome, I've got everything!
Great. Double check you've saved app/main.py and that the Swagger UI shows all four endpoints.
ⓧ I'd like to double check the full code
Here's the complete app/main.py file. Compare yours against it:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from contextlib import asynccontextmanager
from datetime import datetime, timezone
import sqlite3
import os
# --- Data Models ---
class VideoCreate(BaseModel):
video_id: str
title: str | None = None
class SnapshotCreate(BaseModel):
views: int
likes: int
class SnapshotResponse(BaseModel):
video_id: str
views: int
likes: int
recorded_at: str
class VideoResponse(BaseModel):
video_id: str
title: str | None
added_at: str
# --- Database ---
DB_PATH = os.environ.get("DB_PATH", "tracker.db")
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_db()
conn.execute("""
CREATE TABLE IF NOT EXISTS videos (
video_id TEXT PRIMARY KEY,
title TEXT,
added_at TEXT NOT NULL
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
video_id TEXT NOT NULL,
views INTEGER NOT NULL,
likes INTEGER NOT NULL,
recorded_at TEXT NOT NULL,
FOREIGN KEY (video_id) REFERENCES videos(video_id)
)
""")
conn.commit()
conn.close()
# --- App Lifespan ---
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
yield
# --- FastAPI App ---
app = FastAPI(
title="YouTube Stats Tracker",
description="Track view and like counts for YouTube videos over time.",
version="1.0.0",
lifespan=lifespan,
)
# --- Endpoints ---
@app.get("/")
def health_check():
return {"name": "YouTube Stats Tracker API", "status": "running"}
@app.post("/videos", response_model=VideoResponse, status_code=201)
def add_video(video: VideoCreate):
conn = get_db()
existing = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video.video_id,)
).fetchone()
if existing:
conn.close()
raise HTTPException(status_code=409, detail="Video already tracked")
added_at = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT INTO videos (video_id, title, added_at) VALUES (?, ?, ?)",
(video.video_id, video.title, added_at),
)
conn.commit()
conn.close()
return VideoResponse(video_id=video.video_id, title=video.title, added_at=added_at)
@app.post("/videos/{video_id}/snapshots", response_model=SnapshotResponse, status_code=201)
def record_snapshot(video_id: str, snapshot: SnapshotCreate):
conn = get_db()
video = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video_id,)
).fetchone()
if not video:
conn.close()
raise HTTPException(status_code=404, detail="Video not found. Add it first.")
recorded_at = datetime.now(timezone.utc).isoformat()
conn.execute(
"INSERT INTO snapshots (video_id, views, likes, recorded_at) VALUES (?, ?, ?, ?)",
(video_id, snapshot.views, snapshot.likes, recorded_at),
)
conn.commit()
conn.close()
return SnapshotResponse(
video_id=video_id,
views=snapshot.views,
likes=snapshot.likes,
recorded_at=recorded_at,
)
@app.get("/videos/{video_id}/history", response_model=list[SnapshotResponse])
def get_history(video_id: str):
conn = get_db()
video = conn.execute(
"SELECT video_id FROM videos WHERE video_id = ?", (video_id,)
).fetchone()
if not video:
conn.close()
raise HTTPException(status_code=404, detail="Video not found")
rows = conn.execute(
"SELECT video_id, views, likes, recorded_at FROM snapshots WHERE video_id = ? ORDER BY recorded_at",
(video_id,),
).fetchall()
conn.close()
return [SnapshotResponse(**dict(row)) for row in rows]
Your API is live and the documentation looks great. Next up, you'll exercise each endpoint through the Swagger UI to see the full request-response cycle in action.
Test the API Locally
Your API code is complete with models, database logic, and four endpoints ready to serve requests.
Now it's time to see the API in action. FastAPI auto-generates interactive documentation called Swagger UI that lets you test every endpoint directly from your browser.
In this step, get ready to:
- Start the FastAPI development server and open Swagger UI.
- Test all four endpoints with real data through the interactive docs.
- Confirm the API stores and retrieves video stats correctly.
Run the API in development mode
The fastapi dev command starts your server with auto-reload enabled, meaning any code changes you save are picked up immediately.
- Start the development server by running this command:
fastapi dev app/main.py
You should see output in your terminal showing the server is running at http://127.0.0.1:8000.
- Open your browser and navigate to http://127.0.0.1:8000/docs to open the Swagger UI.
You should see an interactive page titled "YouTube Stats Tracker" with all four endpoints listed.
What is Swagger UI?
Swagger UI is an interactive API documentation tool that FastAPI generates automatically from your code. It reads your route decorators, Pydantic models, and type hints to build a page where you can send real requests and see real responses.
No extra configuration needed. Every FastAPI app gets this for free at the /docs path.
Don't see the Swagger UI page?
Make sure your virtual environment is still active. You should see (venv) at the start of your terminal prompt.
Check that the terminal shows no error messages. If you see a "ModuleNotFoundError", re-run pip install "fastapi[standard]" with your virtual environment active.
help me debug why my FastAPI server isn't starting
Exercise the endpoints through Swagger UI
Swagger UI lets you send real HTTP requests without needing a separate tool like Postman or curl. You'll test each endpoint in the order a real user would call them.
Add a video to track
- In the Swagger UI, click on the POST /videos row to expand it.
- Click the Try it out button on the right.
- Replace the contents of the request body with this JSON:
{
"video_id": "dQw4w9WgXcQ",
"title": "Never Gonna Give You Up"
}
- Click Execute.
You should see a 201 response code in the "Server response" section, with a JSON body containing your video_id, title, and an added_at timestamp.
What does the 201 status code mean?
HTTP status code 201 means "Created". It tells the client that the server successfully created a new resource. This is more specific than a generic 200 ("OK"), which just means the request succeeded.
Record a stats snapshot
- Click on the POST /videos/{video_id}/snapshots row to expand it.
- Click Try it out.
- In the video_id path parameter field, enter dQw4w9WgXcQ.
- Replace the request body with this JSON:
{
"views": 1500000,
"likes": 50000
}
- Click Execute.
You should see another 201 response with the video_id, views, likes, and a recorded_at timestamp.
Retrieve the video history
- Click on the GET /videos/{video_id}/history row to expand it.
- Click Try it out.
- In the video_id field, enter dQw4w9WgXcQ.
- Click Execute.
You should see a 200 response containing a JSON array with the snapshot you just recorded, including the correct video_id, views, likes, and recorded_at values.
Verify the health check
- Click on the GET / row to expand it.
- Click Try it out, then click Execute.
You should see a 200 response with {"name": "YouTube Stats Tracker API", "status": "running"}.
Why test in this order?
You added a video first, then recorded a snapshot for that video, then retrieved history. This mirrors how a real client would use the API. If you tried to record a snapshot before adding the video, the API would return a 404 error.
Getting a 404 or 409 error?
A 404 on the snapshots or history endpoint means the video hasn't been added yet. Go back and run POST /videos first.
A 409 on POST /videos means you've already added that video ID. Try a different video_id value or continue to the snapshot step.
help me troubleshoot errors in my FastAPI Swagger UI responses
Stop the development server
Now that you've confirmed everything works, stop the server so the port is free for the next step.
- Switch back to your terminal and press Ctrl+C to stop the FastAPI server.
Your API is tested and working. Next up, you'll package it into a Docker container so it runs in an isolated Linux environment on your Windows machine.
Containerize with Docker
Your API works perfectly on your machine. But what happens when you want to share it with a teammate or deploy it to a server? They would need the exact same Python version, the same packages, and the same operating system configuration.
Docker solves this by packaging your app, its dependencies, and its runtime environment into a single portable unit called a container. In this step, you will write a Dockerfile, build a Docker image, and run the API inside a Linux container on your Windows machine.
In this step, get ready to:
- Write a Dockerfile that describes how to package the app.
- Build a Docker image from the Dockerfile.
- Run the image as a container and verify the API works inside Linux.
Create the Dockerfile
A Dockerfile is a text file that tells Docker how to assemble your application into an image. Each line is an instruction that creates a new layer in the image.
- In VS Code, create a new file called Dockerfile in the root of your yt-stats-tracker folder (at the same level as requirements.txt).
- Add the following content to your Dockerfile:
FROM python:3.13-slim
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
EXPOSE 8000
CMD ["fastapi", "run", "app/main.py", "--port", "8000"]
What does this Dockerfile do?
- FROM python:3.13-slim starts from a lightweight Debian-based image with Python 3.13 pre-installed. The slim tag strips out unnecessary files to keep the image small.
- WORKDIR /code sets the working directory inside the container. All subsequent commands run from here.
- COPY ./requirements.txt and RUN pip install are intentionally placed before copying the app code. Docker caches each layer. If your code changes but your dependencies do not, Docker reuses the cached dependency layer instead of reinstalling everything.
- COPY ./app /code/app brings your application source code into the container.
- EXPOSE 8000 documents which port the container listens on.
- CMD defines the command that runs when the container starts. It launches FastAPI in production mode on port 8000.
- Save Dockerfile.
- Confirm the file exists by running:
ls Dockerfile
You should see Dockerfile in the output.
Don't see the file?
Make sure you created the file in the project root (yt-stats-tracker), not inside the app/ subfolder. The file must be named exactly Dockerfile with a capital D and no file extension.
help me fix my Dockerfile location
✔️ Awesome, I've got everything!
Great. Double check you have saved your Dockerfile before moving on.
ⓧ I'd like to double check the full code
Here is the complete Dockerfile:
FROM python:3.13-slim
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
EXPOSE 8000
CMD ["fastapi", "run", "app/main.py", "--port", "8000"]
Build the Docker image
A Docker image is the built snapshot of your Dockerfile instructions. Think of the Dockerfile as the recipe and the image as the finished dish ready to be served.
- Make sure Docker Desktop is running. You can check by looking for the Docker whale icon in your system tray.
- Build the image by running this command from your yt-stats-tracker folder:
docker build -t yt-stats-tracker .
What does this command do?
- docker build reads the Dockerfile in the current directory and executes each instruction as a layer.
- -t yt-stats-tracker tags (names) the image so you can reference it later.
- . tells Docker to use the current directory as the build context (the files Docker can access).
You will see output showing each step being executed. Docker downloads the python:3.13-slim base image, installs your dependencies, and copies your code.
- Verify the image was created by running:
docker images
You should see yt-stats-tracker listed with a latest tag.
Build failed with an error?
- If you see a connection error, make sure Docker Desktop is running. The Docker daemon must be active for build commands to work.
- If you see a file-not-found error for requirements.txt, make sure you are running the command from the yt-stats-tracker folder (the one containing Dockerfile).
- If the pip install step fails, double-check that requirements.txt contains exactly fastapi[standard]==0.136.3.
Help me fix my Docker build error
Run the container and test
Now that the image is built, you can run it as a container. The container is a live, running instance of the image.
- Start the container by running:
docker run -d -p 8000:8000 --name yt-tracker yt-stats-tracker
What do these flags mean?
- -d runs the container in detached mode (in the background) so you get your terminal back.
- -p 8000:8000 maps port 8000 on your machine to port 8000 inside the container. Without this, you could not reach the API from your browser.
- --name yt-tracker gives the container a human-readable name.
- yt-stats-tracker is the image to run (the one you just built).
- Open your browser and navigate to http://localhost:8000/docs.
- Test the same endpoints you used earlier: add a video, record a snapshot, and retrieve the history.
The API works exactly the same as before. The difference is that it is now running inside a Linux container, not directly on your Windows machine.
- Prove the container is running Linux by running:
docker exec yt-tracker cat /etc/os-release
You should see output showing a Debian-based Linux distribution with fields like ID=debian and a PRETTY_NAME value containing the Debian version name.
Why does this matter?
You are running a full Linux environment inside your Windows machine through Docker and WSL 2. Anyone with Docker installed can pull this same image and run the exact same API, regardless of their operating system. This is the power of containerization.
Can't reach the API at localhost:8000?
- If you stopped the local dev server earlier but still get a port conflict, check if another process is using port 8000. Run docker ps to confirm your container is running.
- If docker ps shows nothing, the container may have exited. Run docker logs yt-tracker to see what went wrong.
- If you see a "name already in use" error when running docker run, remove the old container first with docker rm yt-tracker and try again.
Help me get my Docker container running
Secret mission
Connect to the Real YouTube Data API
Your API works, but you have to type in view and like counts manually. What if it could fetch live stats from YouTube automatically? In this secret mission, you'll connect your tracker to the real YouTube Data API v3 and pull live data with a single API call.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally using Docker Desktop, so there are no ongoing costs. If you completed the Secret Mission, you also created a Google Cloud project (the YouTube Data API v3 free tier has no charges for the usage in this project).
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Docker container yt-tracker (running locally, no cost).
- Docker image yt-stats-tracker (stored locally, no cost).
- SQLite database inside the container (ephemeral, deleted with the container).
- Project folder yt-stats-tracker on your Desktop.
- Google Cloud project with YouTube Data API v3 enabled (Secret Mission only, no cost).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with the API.
- Leave Docker Desktop running and the container active.
- The API will be available at http://localhost:8000 whenever Docker Desktop is running.
- Your project folder and virtual environment remain on your Desktop.
- Docker Desktop stays installed on your machine.
Pause - I'll come back to this later
Stop the running container to free up system resources. You can restart it anytime without losing your data.
- Stop the container by running:
docker stop yt-tracker
- When you want to use the API again, restart it by running:
docker start yt-tracker
- The database inside the container is preserved between stops and starts.
- You can also quit Docker Desktop from the system tray to free more memory.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each item below to clean up completely.
- Stop and remove the Docker container by running:
docker stop yt-tracker
docker rm yt-tracker
- Remove the Docker image by running:
docker rmi yt-stats-tracker
- Delete the project folder from your Desktop by running:
cd ~/Desktop
rm -rf yt-stats-tracker
On Windows?
If you are using Command Prompt instead of Git Bash or WSL, delete the folder with this command instead:
cd %USERPROFILE%\Desktop
rmdir /s /q yt-stats-tracker
- To uninstall Docker Desktop, open Settings on your Windows machine.
- Select Apps, then Apps & features.
- Find Docker Desktop in the list and click Uninstall.
If you completed the Secret Mission and want to delete your Google Cloud project:
- Go to the IAM & Admin Settings page in the Google Cloud Console.
- Select the project you created for the YouTube Data API.
- Click Shut down.
- Enter the project ID when prompted and click Shut down anyway.
- The project enters a 30-day recovery period before permanent deletion.
Nice Work!
Nice Work!
You just built a production-style REST API from scratch, packaged it into a portable Linux container, and ran it on your local machine. That is real-world DevOps in action.
Here's what you accomplished:
- Built a REST API from scratch using FastAPI with Pydantic data validation and auto-generated Swagger documentation.
- Wrote a Dockerfile that packages a Python application into a portable Linux container and ran it on Windows using WSL 2.
- Stored and retrieved time-series data using SQLite inside a Docker container with port mapping to serve requests on localhost.
- Secret Mission: Connected to the real YouTube Data API v3 and passed secrets securely via environment variables.
Ready to quiz yourself?