Module 8: Capstone Project — Deployed AI System
8. Final Project: Deployed Production AI System
Project description
This is the integrative project for the whole guide — not just Module 8, but all 8 complete modules. You'll integrate everything you built and deliver two artifacts: an AI system deployed in real production (with a URL accessible from the internet) and professional operational documentation. It's not an exercise — it's a real deployment. The result is the most portfolio-worthy artifact of the whole guide.
Why it matters: This project proves you don't just know how to deploy — you know how to decide how to deploy, document your decisions, validate that it works, and operate the system when something fails. It's exactly what an employer wants to see: an engineer who can take a system from zero to production with professional judgment.
Project goal
Produce a Deployed Production AI System that includes:
- An AI system deployed on a real platform, accessible from the internet
- A CI/CD pipeline that deploys automatically from git push
- Health checks and smoke tests that validate inference post-deploy
- A decision matrix v_final with justification of strategy and platform
- An operational runbook with procedures for common incidents
- A performance baseline with latency, cost, and error rate targets
- A deployment guide that another engineer can follow to replicate the setup
Module recap
Before you start, make sure you have the artifacts from each capsule:
| Capsule | Artifact | You use it for |
|---|---|---|
| 02 | Integration diagram | Understand how the pieces connect |
| 03 | GitHub Actions workflow | Automate test → build → deploy → validate |
| 04 | Smoke test script | Validate inference post-deploy |
| 05 | Runbook template | Document incident procedures |
| 06 | Decision matrix v_final | Justify the strategy and platform |
| 07 | Performance baseline | Establish reference metrics |
If you're missing any, go back to the corresponding capsule and complete it. The project integrates ALL of these artifacts.
Deliverable Specifications
Deliverable 1: Deployed System (50%)
MANDATORY REQUIREMENTS:
├── ✅ Public URL accessible from any browser
│ Example: https://your-app.railway.app
│
├── ✅ Health check endpoint
│ GET /health → {"status": "healthy", "version": "1.0.0"}
│
├── ✅ Readiness check with dependency verification
│ GET /health/ready → {"status": "ready", "checks": {...}}
│
├── ✅ Working AI inference endpoint
│ POST /api/inference → LLM response
│
├── ✅ CI/CD: git push to main → automatic deploy
│ GitHub Actions workflow with test + build + deploy + validate
│
├── ✅ Environment variables configured (not hardcoded)
│ OPENAI_API_KEY, ENVIRONMENT, etc. in platform secrets
│
└── ✅ Working Docker image
Multi-stage Dockerfile, .dockerignore configured
Deliverable 2: Operational Documentation (50%)
MANDATORY REQUIREMENTS:
├── ✅ Decision Matrix v_final (docs/decision-matrix.md)
│ Strategy + platform + justification + trade-offs
│
├── ✅ Operational Runbook (docs/runbook.md)
│ At least 4 procedures with diagnosis and resolution
│
├── ✅ Performance Baseline (docs/performance-baseline.md)
│ Latency, costs, error rate, uptime with targets
│
├── ✅ Deployment Guide (docs/deployment-guide.md)
│ Instructions to replicate the setup from scratch
│
└── ✅ Post-deploy validation report
Result of smoke tests against production
Case Study (if you don't have your own AI app)
DocuSearch AI — The same case from the whole guide
If you don't have your own AI app, use the DocuSearch AI case you've been developing. Here's the minimum code needed:
# src/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, field_validator
from openai import AsyncOpenAI
from src.config import get_settings
import time
app = FastAPI(title="DocuSearch AI", version="1.0.0")
settings = get_settings()
client = AsyncOpenAI(api_key=settings.openai_api_key)
class InferenceRequest(BaseModel):
prompt: str
@field_validator("prompt")
@classmethod
def prompt_not_empty(cls, v):
if not v or not v.strip():
raise ValueError("Prompt cannot be empty")
return v.strip()
class InferenceResponse(BaseModel):
response: str
model: str
latency_ms: int
@app.get("/health")
async def health():
return {
"status": "healthy",
"version": "1.0.0",
"environment": settings.environment,
}
@app.get("/health/ready")
async def readiness():
checks = {}
try:
await client.models.list()
checks["openai"] = {"status": "connected"}
except Exception as e:
checks["openai"] = {"status": "error", "detail": str(e)[:100]}
all_ok = all(c.get("status") == "connected" for c in checks.values())
return {"status": "ready" if all_ok else "degraded", "checks": checks}
@app.post("/api/inference", response_model=InferenceResponse)
async def inference(request: InferenceRequest):
start = time.time()
try:
completion = await client.chat.completions.create(
model=settings.openai_model,
messages=[
{"role": "system", "content": "You are a helpful documentation assistant. Answer concisely."},
{"role": "user", "content": request.prompt},
],
max_tokens=500,
temperature=0.7,
)
elapsed_ms = int((time.time() - start) * 1000)
return InferenceResponse(
response=completion.choices[0].message.content,
model=settings.openai_model,
latency_ms=elapsed_ms,
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Inference failed: {str(e)[:200]}")
# src/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
environment: str = "development"
openai_api_key: str = ""
openai_model: str = "gpt-4o-mini"
debug: bool = False
version: str = "1.0.0"
class Config:
env_file = ".env"
@lru_cache()
def get_settings() -> Settings:
return Settings()
# Dockerfile
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
COPY src/ ./src/
FROM python:3.11-slim AS runtime
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /root/.local /root/.local
COPY --from=builder /app/src ./src/
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
# requirements.txt
fastapi>=0.109.0
uvicorn>=0.27.0
openai>=1.12.0
pydantic>=2.6.0
pydantic-settings>=2.1.0
Step by Step: The Deployment
Step 1: Verify that everything works locally (15 min)
# Build Docker image
docker build -t docusearch-ai:latest .
# Run locally
docker run -d --name local-test \
-p 8000:8000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e ENVIRONMENT=development \
docusearch-ai:latest
# Verify health
curl http://localhost:8000/health
# {"status":"healthy","version":"1.0.0","environment":"development"}
# Verify readiness
curl http://localhost:8000/health/ready
# {"status":"ready","checks":{"openai":{"status":"connected"}}}
# Verify inference
curl -X POST http://localhost:8000/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "What is Docker?"}'
# {"response":"Docker is a platform for...","model":"gpt-4o-mini","latency_ms":1856}
# Cleanup
docker stop local-test && docker rm local-test
If something fails here, do NOT continue. Fix the local problem before trying to deploy.
Step 2: Configure the platform (10-15 min)
Railway:
# Install the CLI
npm install -g @railway/cli
# Login and create a project
railway login
railway init
# Configure environment variables
railway variables set OPENAI_API_KEY=sk-...
railway variables set ENVIRONMENT=production
# Deploy
railway up
Render:
1. render.com → New → Web Service
2. Connect GitHub repo
3. Settings:
- Environment: Docker
- Plan: Free
- Health Check Path: /health
4. Environment Variables:
- OPENAI_API_KEY: sk-...
- ENVIRONMENT: production
5. Deploy
Fly.io:
# Install the CLI
curl -L https://fly.io/install.sh | sh
# Login and create the app
fly auth login
fly launch --name docusearch-ai
# Configure secrets
fly secrets set OPENAI_API_KEY=sk-...
fly secrets set ENVIRONMENT=production
# Deploy
fly deploy
Step 3: Verify the deployment (5 min)
# Replace with your real URL
export PRODUCTION_URL="https://your-app.railway.app"
# Health check
curl $PRODUCTION_URL/health
# Readiness
curl $PRODUCTION_URL/health/ready
# Inference
curl -X POST $PRODUCTION_URL/api/inference \
-H "Content-Type: application/json" \
-d '{"prompt": "What is deployment?"}'
# Complete smoke tests
python scripts/smoke_test.py $PRODUCTION_URL
Step 4: Configure CI/CD (15 min)
# .github/workflows/deploy.yml
name: Deploy AI System
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: 'pip'
- run: pip install -r requirements.txt && pip install pytest httpx
- run: pytest tests/ -v --tb=short
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ENVIRONMENT: test
deploy:
if: github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
timeout-minutes: 10
environment: production
steps:
- uses: actions/checkout@v4
# ADAPT TO YOUR PLATFORM:
# Railway:
- run: npm install -g @railway/cli
- run: railway up --detach --service ${{ vars.RAILWAY_SERVICE_ID }}
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
validate:
needs: deploy
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Wait for deployment
run: sleep 60
- name: Run smoke tests
run: python scripts/smoke_test.py "${{ vars.PRODUCTION_URL }}"
Configure in GitHub:
- Secrets:
OPENAI_API_KEY,RAILWAY_TOKEN(or your platform's) - Variables:
PRODUCTION_URL,RAILWAY_SERVICE_ID
Step 5: Verify CI/CD end-to-end (10 min)
# Make a minor change and push
echo "# Updated $(date)" >> README.md
git add -A
git commit -m "test: verify CI/CD pipeline"
git push origin main
# Go to GitHub → Actions → watch the pipeline
# It should pass: test ✅ → deploy ✅ → validate ✅
Step 6: Configure monitoring (5 min)
UptimeRobot:
1. New Monitor → HTTP(s)
2. URL: https://your-app.railway.app/health
3. Interval: 5 minutes
4. Alert contacts: your email
Step 7: Create documentation (30-45 min)
Create the 4 documents in the docs/ folder:
docs/decision-matrix.md — Your decision matrix v_final (capsule 06) docs/runbook.md — Your operational runbook (capsule 05) docs/performance-baseline.md — Your performance baselines (capsule 07) docs/deployment-guide.md — See template below
Template: Deployment Guide
# Deployment Guide — [System Name]
## Prerequisites
- Docker 24.0+
- Python 3.11+
- GitHub account
- [Your platform] account
- OpenAI API key
## Step 1: Clone and Setup
git clone https://github.com/[your-user]/[your-repo].git
cd [your-repo]
cp .env.example .env
# Edit .env with your API keys
## Step 2: Run Locally
docker build -t [app-name]:latest .
docker run -d --name local \
-p 8000:8000 \
--env-file .env \
[app-name]:latest
curl http://localhost:8000/health
## Step 3: Deploy to [Platform]
[Platform-specific steps]
## Step 4: Configure CI/CD
1. Add secrets to GitHub: [list]
2. Add variables to GitHub: [list]
3. Push to main to trigger deploy
## Step 5: Verify
python scripts/smoke_test.py [production-url]
## Step 6: Monitor
- UptimeRobot: [URL]
- OpenAI Usage: platform.openai.com
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| OPENAI_API_KEY | Yes | OpenAI API key |
| ENVIRONMENT | Yes | development/staging/production |
| [Others] | ... | ... |
## Troubleshooting
- [Common issue 1]: [Solution]
- [Common issue 2]: [Solution]
Final Project Structure
your-ai-project/
├── .github/
│ └── workflows/
│ ├── deploy.yml # CI/CD pipeline
│ └── rollback.yml # Rollback workflow (optional)
├── src/
│ ├── __init__.py
│ ├── main.py # FastAPI app
│ └── config.py # Settings with Pydantic
├── tests/
│ ├── __init__.py
│ └── test_health.py # Basic tests
├── scripts/
│ ├── smoke_test.py # Smoke tests
│ ├── validate-deploy.sh # Bash validation
│ ├── measure_latency.py # Latency measurement
│ └── verify_secrets.py # Env var verification
├── docs/
│ ├── decision-matrix.md # Decision matrix v_final
│ ├── runbook.md # Operational runbook
│ ├── performance-baseline.md # Performance baselines
│ └── deployment-guide.md # Deployment guide
├── Dockerfile # Multi-stage build
├── .dockerignore # Image exclusions
├── docker-compose.yml # Local development
├── requirements.txt # Python dependencies
├── .env.example # Env var template
└── README.md # Project overview
Completeness Checklist
Deployed System
- Accessible public URL:
https://____________ -
GET /healthreturns 200 with status "healthy" -
GET /health/readyverifies dependencies (OpenAI) -
POST /api/inferenceprocesses a prompt and returns an LLM response - Environment variables configured on the platform (not hardcoded)
- Docker image works locally with
docker build+docker run
CI/CD Pipeline
-
git push maintriggers the pipeline automatically - The
testjob runs pytest successfully - The
deployjob deploys to the chosen platform - The
validatejob runs smoke tests against production - Complete pipeline in < 10 minutes
- Secrets configured in GitHub (API keys, platform tokens)
Documentation
-
docs/decision-matrix.mdwith weighted criteria, evaluation, and justification -
docs/runbook.mdwith at least 4 incident procedures -
docs/performance-baseline.mdwith latency, costs, error rate, uptime targets -
docs/deployment-guide.mdthat another engineer can follow from scratch - Working smoke test script (
scripts/smoke_test.py) - README.md updated with a project overview
Monitoring
- UptimeRobot (or an alternative) configured with a health check every 5 min
- Downtime alerts configured (email or Slack)
- OpenAI spending alerts configured
Quality
- The system works after 24+ hours without intervention
- A git push with a minor change triggers a successful deploy
- The smoke tests pass consistently (>95% success rate)
- The documentation is self-sufficient (someone external understands it)
- The runbook was tested with at least one simulated incident
Evaluation Criteria
Rubric
| Criterion | Weight | Excellent (5) | Good (3) | Insufficient (1) |
|---|---|---|---|---|
| Functional deploy | 25% | Public URL, health checks, working inference | Working URL but missing a check | Not deployed or not accessible |
| CI/CD | 20% | Complete pipeline with validate | Pipeline with test and deploy | No CI/CD or manual |
| Decision matrix | 15% | v_final with real data and validation | Matrix with criteria but no real data | Incomplete or generic matrix |
| Runbook | 15% | 4+ tested procedures | 3 basic procedures | <3 procedures or untested |
| Performance baseline | 10% | 4 metrics with targets and alerts | 2-3 metrics documented | No baselines or no targets |
| Deployment guide | 10% | Another engineer replicates the setup | Partial instructions | No guide or incomplete |
| Monitoring | 5% | UptimeRobot + alerts configured | Basic monitoring | No monitoring |
Delivery levels
BASIC LEVEL (pass):
├── System deployed with a working URL
├── Health check that responds 200
├── Working inference
├── Decision matrix with justification
└── CI/CD with at least test + deploy
ADVANCED LEVEL (distinguished):
├── Everything basic +
├── Automated post-deploy smoke tests
├── Runbook with 4+ tested procedures
├── Performance baseline with 4 metrics
├── Complete deployment guide
├── External monitoring configured
└── Documented and tested rollback strategy
EXCEPTIONAL LEVEL (portfolio-worthy):
├── Everything advanced +
├── Pipeline with staging → production
├── Post-mortem of a simulated incident
├── Sensitivity analysis in the decision matrix
├── Caching implemented for frequent responses
└── Documentation that looks like a professional team's
Final Verification: Complete Validation Script
All-in-one script to verify the project
# scripts/verify_project.py
"""
Complete verification of the final project.
Run: python scripts/verify_project.py https://your-app.railway.app
"""
import sys
import os
import json
import time
import urllib.request
import urllib.error
def check(name: str, condition: bool, detail: str = ""):
icon = "PASS" if condition else "FAIL"
msg = f" [{icon}] {name}"
if detail:
msg += f" — {detail}"
print(msg)
return condition
def verify_deployment(base_url: str) -> dict:
"""Verifies all the requirements of the deployed system."""
results = {"passed": 0, "failed": 0, "tests": []}
print(f"\n{'='*60}")
print(f"PROJECT VERIFICATION: {base_url}")
print(f"{'='*60}")
# 1. Health check
print("\n--- System Health ---")
try:
req = urllib.request.Request(f"{base_url}/health")
resp = urllib.request.urlopen(req, timeout=15)
data = json.loads(resp.read())
ok = check("Health endpoint", resp.status == 200, f"status={data.get('status')}")
ok2 = check("Version present", "version" in data, f"v={data.get('version')}")
except Exception as e:
ok = check("Health endpoint", False, str(e)[:80])
ok2 = False
# 2. Readiness check
try:
req = urllib.request.Request(f"{base_url}/health/ready")
resp = urllib.request.urlopen(req, timeout=15)
data = json.loads(resp.read())
ok3 = check("Readiness endpoint", resp.status == 200, f"status={data.get('status')}")
ok4 = check("Dependency checks", "checks" in data, f"checks={list(data.get('checks',{}).keys())}")
except Exception as e:
ok3 = check("Readiness endpoint", False, str(e)[:80])
ok4 = False
# 3. Inference
print("\n--- AI Inference ---")
try:
payload = json.dumps({"prompt": "What is 2+2? Answer briefly."}).encode()
req = urllib.request.Request(
f"{base_url}/api/inference",
data=payload,
headers={"Content-Type": "application/json"},
)
start = time.time()
resp = urllib.request.urlopen(req, timeout=30)
latency = (time.time() - start) * 1000
data = json.loads(resp.read())
ok5 = check("Inference endpoint", resp.status == 200, f"{latency:.0f}ms")
ok6 = check("Response has content", len(data.get("response", "")) > 0)
ok7 = check("Latency < 10s", latency < 10000, f"{latency:.0f}ms")
except Exception as e:
ok5 = check("Inference endpoint", False, str(e)[:80])
ok6 = ok7 = False
# 4. Error handling
print("\n--- Error Handling ---")
try:
payload = json.dumps({"prompt": ""}).encode()
req = urllib.request.Request(
f"{base_url}/api/inference",
data=payload,
headers={"Content-Type": "application/json"},
)
resp = urllib.request.urlopen(req, timeout=10)
ok8 = check("Empty prompt rejected", False, f"Expected 422, got {resp.status}")
except urllib.error.HTTPError as e:
ok8 = check("Empty prompt rejected", e.code == 422, f"status={e.code}")
except Exception as e:
ok8 = check("Empty prompt rejected", False, str(e)[:80])
# 5. Documentation files
print("\n--- Documentation ---")
doc_files = [
"docs/decision-matrix.md",
"docs/runbook.md",
"docs/performance-baseline.md",
"docs/deployment-guide.md",
]
for doc in doc_files:
exists = os.path.exists(doc)
check(f"File: {doc}", exists)
# 6. CI/CD
print("\n--- CI/CD ---")
ci_file = ".github/workflows/deploy.yml"
check(f"File: {ci_file}", os.path.exists(ci_file))
# 7. Scripts
print("\n--- Scripts ---")
scripts = ["scripts/smoke_test.py"]
for script in scripts:
check(f"File: {script}", os.path.exists(script))
print(f"\n{'='*60}")
print("Verification complete.")
print(f"{'='*60}\n")
if __name__ == "__main__":
url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000"
verify_deployment(url)
Execution
# Verify everything at once
python scripts/verify_project.py https://your-app.railway.app
# Expected output:
# ============================================================
# PROJECT VERIFICATION: https://your-app.railway.app
# ============================================================
#
# --- System Health ---
# [PASS] Health endpoint — status=healthy
# [PASS] Version present — v=1.0.0
# [PASS] Readiness endpoint — status=ready
# [PASS] Dependency checks — checks=['openai']
#
# --- AI Inference ---
# [PASS] Inference endpoint — 2340ms
# [PASS] Response has content
# [PASS] Latency < 10s — 2340ms
#
# --- Error Handling ---
# [PASS] Empty prompt rejected — status=422
#
# --- Documentation ---
# [PASS] File: docs/decision-matrix.md
# [PASS] File: docs/runbook.md
# [PASS] File: docs/performance-baseline.md
# [PASS] File: docs/deployment-guide.md
#
# --- CI/CD ---
# [PASS] File: .github/workflows/deploy.yml
#
# --- Scripts ---
# [PASS] File: scripts/smoke_test.py
#
# ============================================================
# Verification complete.
# ============================================================
Project Troubleshooting
"I can't deploy because my app needs an additional service (Redis, DB)"
Solution: For the project, simplify the architecture. Use only FastAPI + OpenAI API (no Redis or DB). The complexity is in the deployment integration, not in the app. If you insist on a DB, Railway and Render let you add additional services (PostgreSQL, Redis) from the dashboard.
"The deploy works but validation fails intermittently"
Solution:
# Increase wait times and retries
- name: Wait for deployment
run: sleep 90 # Give more time, especially on free tier
# Add a retry to the smoke test
- name: Run smoke tests with retry
run: |
for i in 1 2 3; do
python scripts/smoke_test.py "$URL" && exit 0
echo "Attempt $i failed, retrying in 30s..."
sleep 30
done
exit 1
"My OpenAI API key works locally but not in production"
Solution:
# Verify that the key is configured correctly on the platform
# It shouldn't have spaces, line breaks, or quotes
# Railway
railway variables | grep OPENAI
# Fly.io
fly secrets list
# Direct test of the key
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer sk-your-key-here" | head -c 100
"The pipeline takes too long and sometimes times out"
Solution: Optimize the pipeline:
# Use pip cache
- uses: actions/setup-python@v5
with:
cache: 'pip'
# Parallelize test and build if they're independent
# Reduce the number of tests in CI (only the critical path)
# Use timeout-minutes on each job
"How do I do the project without spending money?"
Solution: All the recommended platforms have a free tier:
Railway: $5 credit/month free (more than enough for a project)
Render: 750 hours/month of free instances
Fly.io: 3 free shared VMs
AWS Lambda: 1M requests/month free
OpenAI: Use gpt-4o-mini ($0.15/1M input tokens) —
500 requests/day × 30 days = 15K requests/month ≈ $1.35/month
Final Result
On completing this project you'll have:
✅ DEPLOYED SYSTEM
URL: https://your-app.railway.app (or your platform)
Health: /health → healthy
Inference: /api/inference → LLM response
CI/CD: git push → automatic deploy
✅ PROFESSIONAL DOCUMENTATION
Decision matrix: justification with real data
Runbook: 4+ incident procedures
Baselines: latency, costs, error rate, uptime
Deploy guide: replicable instructions
✅ MONITORING
UptimeRobot: health check every 5 min
Alerts: email if the service goes down
Cost alerts: OpenAI spending limits
Portfolio value
This project demonstrates:
- Decision judgment: Decision matrix with documented trade-offs
- Technical execution: Docker + CI/CD + deployment + validation
- Production mindset: Runbook, baselines, monitoring
- Communication: Documentation another engineer can use
It's the most complete artifact you can show in an infrastructure or AI engineering interview.
Connection with the Guide
What's next?
With your AI system deployed in production:
Guide #18 — Monitoring & Observability for AI: Your system is in production. Now you need to see what happens inside it: inference metrics, structured logs, distributed tracing, intelligent alerts, and dashboards. The transition is natural: "You have an AI system in production → now you need to observe it."
What you carry from this guide to #18:
- A working AI system in production (this project)
- Performance baselines (capsule 07)
- Health checks and smoke tests (capsule 04)
- A runbook for incidents (capsule 05)
What guide #18 adds:
- Structured logging with inference context
- Custom metrics (token usage, latency per model, cache hit rate)
- Distributed tracing (request → preprocessing → LLM → postprocessing)
- Anomaly-based alerts (not just static thresholds)
- Dashboards for technical and non-technical stakeholders
Final Note
You've just put your first AI system into production — this is real.
It's not a tutorial that ends with "and that's how you'd deploy it." It's a real URL that anyone with internet can access. It's a pipeline that deploys automatically when you push. It's documentation another engineer can follow to operate the system.
This is what separates a developer who experiments from one who delivers in production. The experience of seeing your AI system responding on the internet, with professional documentation, monitoring configured, and incident procedures ready — no tutorial gives you that experience.
What you built here is the foundation for everything that comes next: monitoring, observability, scaling, multi-region. But the most important thing is that you already have something in production. And that changes everything.
Summary
- You deployed an AI system to production with an accessible public URL, health check, and a working inference endpoint.
- You configured a CI/CD pipeline (GitHub Actions) that deploys automatically from git push to main.
- You implemented health checks and readiness checks that validate dependencies (OpenAI) post-deploy.
- You documented the decision with a decision matrix v_final that justifies the strategy and platform.
- You created an operational runbook with concrete procedures for common incidents.
- You defined a performance baseline with latency, cost, error rate, and uptime targets.
Project Resources
- Architecture Decision Records — Format for documenting decisions
- The Twelve-Factor App — Principles of production-ready apps
- Google SRE Book — Reference for production operations
- Render Documentation — Docs for deployment on Render
- Railway Documentation — Docs for deployment on Railway
- Fly.io Documentation — Docs for deployment on Fly.io
- GitHub Actions Documentation — CI/CD with GitHub Actions
- UptimeRobot — Free uptime monitoring
- OpenAI API Reference — OpenAI API reference
- FastAPI Deployment — Official FastAPI deployment documentation