Module 7: Alternative Platforms (Render, Railway, Fly.io)
8. Project: Deploy to Platform of Choice
Project description
This is the integrative project for Module 7. You'll take everything you learned — Render, Railway, Fly.io, comparison, CI/CD, decision matrix — and apply it to a real deployment. The result is your AI app deployed in production on the platform your decision matrix recommends, with automated CI/CD, comparative documentation, and the completed decision matrix v2.
It's not a theoretical exercise. Your app will be online, accessible by public URL, deploying automatically on every push. And you'll have a decision document that justifies why you chose that platform and not another — with real data from having tested them all.
Why it matters: This project is the bridge between "I know the platforms" and "I have an AI app in production." It's what you show when someone asks "where did you deploy your project?" — not just the URL, but the documented reasoning behind the choice. And it's the direct foundation of Module 8, where this deployment becomes part of your integrative AI system.
Project goal
Produce three deliverables:
- Deployed AI app: Your app running on the chosen platform, with a public URL, health check, and CI/CD
- Comparative documentation: A comparison of your experience across the three platforms vs local (M2) vs serverless (M3)
- Decision Matrix v2: The M1 matrix extended with platform criteria and real data
Module recap
Before you start, make sure you master these concepts:
| Capsule | Key concept | You use it for |
|---|---|---|
| 02 | Render: deploy, render.yaml, databases | Experience on platform 1 |
| 03 | Railway: CLI, add-ons, preview environments | Experience on platform 2 |
| 04 | Fly.io: flyctl, multi-region, volumes | Experience on platform 3 |
| 05 | Comparison: pricing, limits, features | Data for your matrix |
| 06 | CI/CD: GitHub Actions + each platform | Automate the deploy |
| 07 | Decision Matrix v2: framework, scenarios | Choose and justify the platform |
Case Study (if you don't have your own app)
DocuSearch AI — continued
If you don't have your own app, use DocuSearch AI from capsule 01 and the previous modules:
project_data = {
"name": "DocuSearch AI",
"type": "RAG (Retrieval Augmented Generation)",
"stage": "MVP → Early growth",
"team": "2 developers",
"technical": {
"framework": "FastAPI",
"llm": "GPT-4o-mini via OpenAI API",
"database": "PostgreSQL (inference logs)",
"cache": "Redis (response cache)",
"container": "Docker (working Dockerfile)",
"ci_cd": "GitHub Actions (M7 capsule 06 pipeline)",
},
"constraints": {
"budget": "$30-50/month for infrastructure",
"latency": "<3 seconds for a response",
"streaming": "Nice-to-have, not required now",
"users": "150 current, 500 target at 6 months",
"availability": "Business hours, not 24/7 critical",
},
"deployment_history": {
"M2": "Local Docker Compose — works, not externally accessible",
"M3": "Lambda — works for simple endpoints, limited by cold start",
"M7_render": "Deployed in capsule 02 — works, 30s timeout is a concern",
"M7_railway": "Deployed in capsule 03 — works, better DX",
"M7_flyio": "Deployed in capsule 04 — works, more setup but edge",
},
}
Deliverable Specifications
Deliverable 1: Deployed AI App
Minimum requirements
Your deployed app must have:
✅ Accessible public URL (HTTPS)
✅ A /health endpoint that returns status
✅ At least one AI inference endpoint (/ask, /search, /chat, etc.)
✅ Environment variables configured (API keys as secrets)
✅ CI/CD: push to main → tests → automatic deploy
✅ Health check configured on the platform
Optional requirements (recommended)
Bonus if your app has:
⚠️ A database (PostgreSQL) for logging/metadata
⚠️ Cache (Redis) for frequent responses
⚠️ Custom domain
⚠️ Staging + production environments
⚠️ Basic monitoring/alerts
Deliverable 2: Comparative Documentation
A deployment-comparison.md file with:
# Deployment Comparison: DocuSearch AI
## Executive summary
[2-3 paragraphs: which platform you chose, why, and how it compares
with the alternatives you tested]
## Platforms tested
### Local (Docker Compose — M2)
- **Setup time:** [real time]
- **Deploy time:** [real time]
- **Cost:** $0 (your laptop)
- **Pros:** [real experience]
- **Cons:** [real experience]
- **When to use it:** [your criteria]
### Serverless (Lambda — M3)
- **Setup time:** [real time]
- **Deploy time:** [real time]
- **Estimated cost:** [calculation]
- **Pros:** [real experience]
- **Cons:** [real experience]
- **When to use it:** [your criteria]
### Render (M7)
- **Setup time:** [real time]
- **Deploy time:** [real time]
- **Cost:** [real or estimated]
- **Pros:** [real experience]
- **Cons:** [real experience]
### Railway (M7)
- **Setup time:** [real time]
- **Deploy time:** [real time]
- **Cost:** [real or estimated]
- **Pros:** [real experience]
- **Cons:** [real experience]
### Fly.io (M7)
- **Setup time:** [real time]
- **Deploy time:** [real time]
- **Cost:** [real or estimated]
- **Pros:** [real experience]
- **Cons:** [real experience]
## Comparison table
| Dimension | Local | Lambda | Render | Railway | Fly.io |
|-----------|-------|--------|--------|---------|--------|
| Setup | | | | | |
| Deploy | | | | | |
| Cost/month | | | | | |
| DX | | | | | |
| AI limitations | | | | | |
## Conclusion
[Which one you chose and why. When you'd reconsider.]
Deliverable 3: Decision Matrix v2
A decision-matrix-v2.md file:
# Decision Matrix v2: [Your Project]
## Context
[Project data: stage, team, budget, users, technical stack]
## Evolution from M1
Matrix v1 (M1): Category = Managed
Matrix v2 (M7): Platform = [your choice]
## Criteria and Weights
| # | Criterion | Weight | Justification |
|---|----------|------|---------------|
| 1 | [criterion] | [weight] | [why this weight] |
| ... | ... | ... | ... |
| **Total** | | **100** | |
## Evaluation (1-5)
| Criterion (Weight) | AWS | Render | Railway | Fly.io | Notes |
|-----------------|-----|--------|---------|--------|-------|
| [criterion] (weight) | [1-5] | [1-5] | [1-5] | [1-5] | [real data] |
| ... | ... | ... | ... | ... | ... |
## Weighted Scores
| Criterion | AWS | Render | Railway | Fly.io |
|----------|-----|--------|---------|--------|
| [criterion] | [w×s] | [w×s] | [w×s] | [w×s] |
| ... | ... | ... | ... | ... |
| **TOTAL** | **[X]** | **[X]** | **[X]** | **[X]** |
| **%** | **X%** | **X%** | **X%** | **X%** |
## Recommendation
### Chosen platform: [name]
**Score:** [total] of [maximum] ([percentage]%)
### Justification
[3-5 paragraphs with real data from your experience in capsules 02-06]
### Discarded options
| Option | Score | Reason for discarding |
|--------|-------|--------------------|
| ... | ... | ... |
## Sensitivity Analysis
[Result of varying the top 3 criteria — is the recommendation robust?]
## Re-evaluation Conditions
- [ ] [trigger 1 with metric]
- [ ] [trigger 2 with metric]
- [ ] [trigger 3 with metric]
- Scheduled review: [date — 3 months]
## Migration Path
If [trigger], migrate to [platform]:
1. [step 1]
2. [step 2]
3. [step 3]
Guided Step-by-Step
Step 1: Run your Decision Matrix v2 (20 min)
Before deploying, decide where. Use the capsule 07 framework:
# Run your matrix with your real criteria and weights
# The result tells you which platform to deploy on
my_criteria = {
# Adjust for YOUR case
"Monthly cost": 20,
"Developer experience": 15,
"Request timeout": 15,
"WebSocket/streaming": 15,
"Integrated databases": 10,
"Available RAM": 10,
"Cold start": 10,
"CI/CD integration": 5,
}
# Calculate scores with the data from capsule 07
# Result: your recommended platform
Step 2: Prepare your app for production (15 min)
# Verify that your app has everything it needs
# 1. Health check endpoint
@app.get("/health")
def health():
return {
"status": "healthy",
"version": "1.0.0",
"platform": os.environ.get("PLATFORM", "unknown"),
}
# 2. Environment variables (not hardcoded)
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
DATABASE_URL = os.environ.get("DATABASE_URL")
REDIS_URL = os.environ.get("REDIS_URL")
# 3. .env.example (for documentation)
# OPENAI_API_KEY=sk-your-key-here
# PLATFORM=railway
# DATABASE_URL=postgresql://...
# REDIS_URL=redis://...
# 4. Working Dockerfile
# (you already have it from capsule 01)
# 5. Basic tests
# (you already have them from capsule 06)
# Verify that everything works locally
docker build -t docusearch-ai .
docker run -p 8000:8000 \
-e OPENAI_API_KEY=sk-test \
-e PLATFORM=local \
docusearch-ai
curl http://localhost:8000/health
Step 3: Deploy on your chosen platform (15 min)
Follow the instructions from the corresponding capsule (02, 03, or 04):
# === If you chose Railway ===
railway init
railway add # PostgreSQL
railway add # Redis
railway variables set OPENAI_API_KEY=sk-prod-xxx
railway variables set PLATFORM=railway
railway up
railway domain
# URL: https://docusearch-ai-production.up.railway.app
# === If you chose Render ===
# Dashboard → New → Web Service → Connect GitHub → Deploy
# Add environment variables
# URL: https://docusearch-ai.onrender.com
# === If you chose Fly.io ===
flyctl launch --name docusearch-ai
flyctl secrets set OPENAI_API_KEY=sk-prod-xxx
flyctl deploy
# URL: https://docusearch-ai.fly.dev
Step 4: Verify the deployment (5 min)
# Define your URL
APP_URL="https://your-app.your-platform.com"
# Health check
curl $APP_URL/health
# {"status":"healthy","version":"1.0.0","platform":"railway"}
# Inference
curl -X POST $APP_URL/ask \
-H "Content-Type: application/json" \
-d '{"question": "What is deployment?", "max_tokens": 200}'
# If you have PostgreSQL, verify logging
curl $APP_URL/health # should log to the DB
# If you have Redis, verify caching
# A second request to the same question should be faster
time curl -X POST $APP_URL/ask \
-H "Content-Type: application/json" \
-d '{"question": "Cache test", "max_tokens": 50}'
Step 5: Configure CI/CD (15 min)
Use the capsule 06 workflow adapted to your platform:
# Create the workflow
mkdir -p .github/workflows
# .github/workflows/deploy.yml
name: Test and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- run: pip install -r app/requirements.txt pytest httpx
- run: pytest tests/ -v
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
# Uncomment your platform's section:
# --- Railway ---
- name: Deploy to Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
npm install -g @railway/cli
railway up --detach
# --- Render ---
# - name: Deploy to Render
# run: |
# curl -X POST \
# "https://api.render.com/v1/services/${{ secrets.RENDER_SERVICE_ID }}/deploys" \
# -H "Authorization: Bearer ${{ secrets.RENDER_API_KEY }}"
# --- Fly.io ---
# - uses: superfly/flyctl-actions/setup-flyctl@master
# - name: Deploy to Fly.io
# env:
# FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
# run: flyctl deploy --remote-only
- name: Health check
run: |
sleep 60
curl -f ${{ secrets.APP_URL }}/health
# Commit and push — the pipeline runs automatically
git add .github/workflows/deploy.yml
git commit -m "Add CI/CD pipeline for production deployment"
git push origin main
Step 6: Document the experience (20 min)
Create the deployment-comparison.md and decision-matrix-v2.md files using the templates from the previous section. Fill them with REAL data from your experience:
# Measure real data
# 1. Deploy time (look at the GitHub Actions logs)
# 2. Latency (run the capsule 05 benchmark)
# 3. Cost (look at your platform's billing dashboard)
# 4. Problems encountered (review your notes/troubleshooting)
# collect_metrics.py — Collect data automatically
import requests
import time
import json
import statistics
APP_URL = "https://your-app.your-platform.com"
print(f"Collecting metrics from {APP_URL}...\n")
# Measure health check latency
print("=== Health Check Latency ===")
latencies = []
for i in range(10):
start = time.time()
resp = requests.get(f"{APP_URL}/health", timeout=30)
elapsed = (time.time() - start) * 1000
latencies.append(elapsed)
print(f" Request {i+1}: {elapsed:.0f}ms (status: {resp.status_code})")
time.sleep(1)
print(f" Mean: {statistics.mean(latencies):.0f}ms")
print(f" Median: {statistics.median(latencies):.0f}ms")
print(f" P95: {sorted(latencies)[9]:.0f}ms")
# Measure inference latency
print("\n=== Inference Latency ===")
inference_latencies = []
for i in range(5):
start = time.time()
resp = requests.post(
f"{APP_URL}/ask",
json={"question": "Benchmark test", "max_tokens": 50},
timeout=60,
)
elapsed = (time.time() - start) * 1000
inference_latencies.append(elapsed)
print(f" Request {i+1}: {elapsed:.0f}ms (status: {resp.status_code})")
time.sleep(2)
print(f" Mean: {statistics.mean(inference_latencies):.0f}ms")
# Compile report
report = {
"platform": "railway",
"url": APP_URL,
"date": "2026-03-08",
"health_check": {
"avg_ms": round(statistics.mean(latencies), 1),
"median_ms": round(statistics.median(latencies), 1),
"p95_ms": round(sorted(latencies)[9], 1),
"min_ms": round(min(latencies), 1),
"max_ms": round(max(latencies), 1),
},
"inference": {
"avg_ms": round(statistics.mean(inference_latencies), 1),
"median_ms": round(statistics.median(inference_latencies), 1),
"samples": len(inference_latencies),
},
"ci_cd": "GitHub Actions → Railway",
"status": "healthy",
}
print(f"\n=== Full Report ===")
print(json.dumps(report, indent=2))
with open("deployment-metrics.json", "w") as f:
json.dump(report, f, indent=2)
print("\nSaved to deployment-metrics.json")
Step 7: Write comparative documentation (15 min)
With the collected data, create the comparison document:
# generate_comparison.py — Generate a comparison template with real data
import json
with open("deployment-metrics.json") as f:
metrics = json.load(f)
comparison_template = f"""# Deployment Comparison: DocuSearch AI
## Executive Summary
I deployed DocuSearch AI on **{metrics['platform']}** after evaluating
Render, Railway and Fly.io in the Module 7 capsules. The decision was
based on my decision matrix v2, where {metrics['platform']} got the
highest score for my use case (MVP, team of 2, limited budget,
need for integrated PostgreSQL + Redis).
Production URL: {metrics['url']}
CI/CD: {metrics['ci_cd']}
## Production Metrics
| Metric | Value |
|---------|-------|
| Health check avg | {metrics['health_check']['avg_ms']:.0f}ms |
| Health check p95 | {metrics['health_check']['p95_ms']:.0f}ms |
| Inference avg | {metrics['inference']['avg_ms']:.0f}ms |
| Status | {metrics['status']} |
## Comparison of Tested Platforms
| Dimension | Local (M2) | Lambda (M3) | Render (M7) | Railway (M7) | Fly.io (M7) |
|-----------|-----------|-------------|-------------|--------------|-------------|
| Setup time | [your data] | [your data] | [your data] | [your data] | [your data] |
| Deploy time | N/A | [your data] | [your data] | [your data] | [your data] |
| Cost/month | $0 | [your data] | [your data] | [your data] | [your data] |
| DX (1-5) | [your data] | [your data] | [your data] | [your data] | [your data] |
| Main limitation | Not accessible | Cold start | 30s timeout | Variable pricing | More setup |
## Conclusion
[Your justification with real data]
"""
with open("deployment-comparison.md", "w") as f:
f.write(comparison_template)
print("Generated: deployment-comparison.md")
print("Fill in the [your data] fields with your real measurements.")
Step 8: Complete decision matrix v2 (15 min)
# generate_matrix.py — Generate decision matrix v2 with calculations
import json
criteria = {
"Monthly cost": 20,
"Developer experience": 15,
"Request timeout": 15,
"WebSocket/streaming": 15,
"Integrated databases": 10,
"Available RAM": 10,
"Cold start": 10,
"CI/CD integration": 5,
}
evaluations = {
"AWS": {"Monthly cost": 3, "Developer experience": 2, "Request timeout": 5,
"WebSocket/streaming": 3, "Integrated databases": 4, "Available RAM": 4,
"Cold start": 2, "CI/CD integration": 4},
"Render": {"Monthly cost": 4, "Developer experience": 3, "Request timeout": 2,
"WebSocket/streaming": 3, "Integrated databases": 3, "Available RAM": 3,
"Cold start": 2, "CI/CD integration": 3},
"Railway": {"Monthly cost": 3, "Developer experience": 5, "Request timeout": 4,
"WebSocket/streaming": 4, "Integrated databases": 5, "Available RAM": 4,
"Cold start": 3, "CI/CD integration": 4},
"Fly.io": {"Monthly cost": 5, "Developer experience": 3, "Request timeout": 4,
"WebSocket/streaming": 5, "Integrated databases": 3, "Available RAM": 3,
"Cold start": 4, "CI/CD integration": 4},
}
max_possible = sum(criteria.values()) * 5
print(f"=== Decision Matrix v2 ===")
print(f"Max possible score: {max_possible}\n")
totals = {}
for platform in evaluations:
total = sum(
criteria[c] * evaluations[platform][c]
for c in criteria
)
totals[platform] = total
for platform, score in sorted(totals.items(), key=lambda x: -x[1]):
pct = score / max_possible * 100
print(f" {platform:<12} {score:>4}/{max_possible} ({pct:.0f}%)")
winner = max(totals, key=totals.get)
print(f"\nRecommendation: {winner}")
Step 9: Verify completeness (10 min)
Run the checklist before considering the project finished.
Completeness Checklist
Deliverable 1: Deployed App
- Accessible public URL (HTTPS)
-
/healthreturns 200 with status "healthy" - At least one AI inference endpoint works
- Environment variables configured as secrets (not hardcoded)
- CI/CD configured: push to main → tests → deploy
- Health check configured on the platform
- The app is running (not sleeping/stopped)
Deliverable 2: Comparative Documentation
- Executive summary (2-3 paragraphs)
- At least 3 platforms documented with real data
- Comparison table with setup time, deploy time, cost, DX
- Comparison with local (M2) and serverless (M3)
- Conclusion with justification
Deliverable 3: Decision Matrix v2
- Project context with specific data
- 7-10 criteria with weights summing to 100
- Each weight has a justification
- 1-5 evaluation for all 4 platforms (AWS, Render, Railway, Fly.io)
- Weighted scores calculated
- Recommendation with justification
- Discarded options with a reason
- Re-evaluation conditions with metrics
- Migration path documented
- Scheduled review date
Quality
- The matrix data comes from real experience (not from the documentation)
- The evaluations (1-5) have concrete justification (not "it's good")
- The comparison document includes problems encountered (not just the positives)
- The migration path is realistic (concrete steps, not "migrate to AWS")
- You could defend the choice in a 5-minute conversation
Project-Specific Troubleshooting
"My matrix says one platform but I'm already comfortable with another"
That's valid. The matrix is a decision tool, not an obligation. If the score difference is <10%, team familiarity is a legitimate factor. Document it: "Railway score 380 vs Fly.io 360. I chose Railway because the team already knows it and the difference is marginal."
"I don't have money for the paid plan — can I use the free tier?"
Yes, but document the limitations. If you use Render Free, document that the 30s cold start would affect real production. If you use Railway Trial, document that the $5 of credit runs out in a week. The project works with the free tier — documenting the limitations is part of the learning.
"My app has no tests — can I skip CI?"
Not ideally, but you can add minimal tests:
# tests/test_health.py — the most basic test possible
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_health():
response = client.get("/health")
assert response.status_code == 200
One test is better than none. A CI/CD pipeline with at least one test is part of the deliverable.
"The deploy works but the health check fails intermittently"
Common causes:
- Free tier sleep: The app sleeps and the health check finds it asleep. Solution: upgrade or ping more frequently.
- Memory limit: The app consumes more RAM than available and gets killed by OOM. Solution: upgrade RAM or optimize usage.
- Slow startup: The app takes time to start and the health check is too early. Solution: increase grace_period.
# Debug: verify manually
curl -v https://your-app.platform.com/health
# Look at the HTTP status code and response time
"I want to include a database but my free tier doesn't support it"
Options:
- Use the Supabase free tier as external PostgreSQL (500 MB free)
- Use the Upstash free tier as external Redis (10K commands/day free)
- Document that you would use PostgreSQL but the free tier doesn't include it — this is real data for your matrix
# main.py — handle an optional DB
DATABASE_URL = os.environ.get("DATABASE_URL")
if DATABASE_URL:
init_db()
log_to_db = True
else:
log_to_db = False
# Log to a file or stdout as a fallback
Deployment Validation
Complete validation script
Run this script to verify that your deployment meets all requirements before submitting:
# validate_deployment.py
import requests
import time
import sys
import json
APP_URL = sys.argv[1] if len(sys.argv) > 1 else "https://your-app.platform.com"
RESULTS = {"passed": 0, "failed": 0, "tests": []}
def test(name: str, condition: bool, detail: str = ""):
status = "PASS" if condition else "FAIL"
RESULTS["passed" if condition else "failed"] += 1
RESULTS["tests"].append({"name": name, "status": status, "detail": detail})
icon = "✅" if condition else "❌"
print(f" {icon} {name}")
if detail and not condition:
print(f" → {detail}")
print(f"=== Validating deployment: {APP_URL} ===\n")
# Test 1: Health check
print("[Endpoints]")
try:
r = requests.get(f"{APP_URL}/health", timeout=10)
test("Health check responds", r.status_code == 200, f"Status: {r.status_code}")
data = r.json()
test("Health includes 'status' field", "status" in data)
test("Health includes 'version' field", "version" in data)
test("Health includes 'platform' field", "platform" in data)
test("Platform is not 'local'", data.get("platform") != "local",
f"Platform: {data.get('platform')}")
except requests.exceptions.RequestException as e:
test("Health check responds", False, str(e))
# Test 2: HTTPS
print("\n[Security]")
test("URL uses HTTPS", APP_URL.startswith("https://"))
# Test 3: Inference endpoint
print("\n[AI Inference]")
try:
r = requests.post(
f"{APP_URL}/ask",
json={"question": "Validation test", "max_tokens": 20},
timeout=60,
)
test("Inference endpoint responds", r.status_code == 200,
f"Status: {r.status_code}")
if r.status_code == 200:
data = r.json()
test("Response includes 'answer' field", "answer" in data)
test("Response includes 'model' field", "model" in data)
except requests.exceptions.RequestException as e:
test("Inference endpoint responds", False, str(e))
# Test 4: Latency
print("\n[Performance]")
start = time.time()
try:
r = requests.get(f"{APP_URL}/health", timeout=10)
latency = (time.time() - start) * 1000
test(f"Health latency < 1000ms", latency < 1000, f"{latency:.0f}ms")
except Exception:
test("Health latency < 1000ms", False, "Request failed")
# Test 5: Consistent responses
print("\n[Reliability]")
success_count = 0
for i in range(5):
try:
r = requests.get(f"{APP_URL}/health", timeout=10)
if r.status_code == 200:
success_count += 1
except Exception:
pass
time.sleep(0.5)
test(f"5/5 health checks pass", success_count == 5,
f"{success_count}/5 passed")
# Summary
print(f"\n{'='*50}")
print(f"Result: {RESULTS['passed']} passed, {RESULTS['failed']} failed")
if RESULTS["failed"] == 0:
print("🎉 All tests passed — deployment validated")
else:
print("⚠️ There are failing tests — review before submitting")
sys.exit(1)
# Run validation
python validate_deployment.py https://your-app.your-platform.com
# Expected output:
# === Validating deployment: https://docusearch-ai-production.up.railway.app ===
#
# [Endpoints]
# ✅ Health check responds
# ✅ Health includes 'status' field
# ✅ Health includes 'version' field
# ✅ Health includes 'platform' field
# ✅ Platform is not 'local'
#
# [Security]
# ✅ URL uses HTTPS
#
# [AI Inference]
# ✅ Inference endpoint responds
# ✅ Response includes 'answer' field
# ✅ Response includes 'model' field
#
# [Performance]
# ✅ Health latency < 1000ms
#
# [Reliability]
# ✅ 5/5 health checks pass
#
# ==================================================
# Result: 11 passed, 0 failed
# 🎉 All tests passed — deployment validated
Verify CI/CD
# Verify that the GitHub Actions pipeline works
# 1. Make a minor change (add a comment in main.py)
# 2. Push to main
# 3. Go to GitHub → Actions → verify that the workflow runs
# 4. Verify that the deploy completes and the health check passes
# From the terminal:
git log --oneline -5
# Verify that the latest commits have ✓ (Actions passed)
# Via the GitHub CLI:
gh run list --limit 5
# Verify that the latest runs are "completed" with "success"
Functional Result
On completing this project, you'll have:
deployment-cloud-guide/module-07/
├── app/
│ ├── main.py # Your AI app
│ ├── Dockerfile # Container ready for production
│ ├── requirements.txt
│ └── tests/
│ └── test_app.py # Basic tests
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline
├── [render.yaml | railway.toml | fly.toml] # Platform config
├── deployment-comparison.md # Experience comparison
├── decision-matrix-v2.md # Complete matrix v2
└── deployment-metrics.json # Measured data (optional)
Public URL: https://your-app.your-platform.com
├── GET /health → {"status": "healthy", ...}
├── POST /ask → {"answer": "...", "model": "...", ...}
└── CI/CD: push → test → deploy → health check ✅
This project is:
- Portfolio-worthy: An AI app deployed in production with CI/CD and decision documentation
- Reusable: The decision matrix framework applies to any future project
- Extensible: It integrates directly into Module 8 (Integrative Project)
- Defensible: Any engineer can review your decision matrix and understand your reasoning
Connection with the Guide
What's next?
With your app deployed and your decision matrix v2 completed:
Module 8: Integrative Project — Deployed AI System
M8 integrates EVERYTHING you've learned:
Module 2: Docker Compose → containerization
+
Module 3: Lambda → serverless
+
Module 4: LocalStack → cost-free development
+
Module 5: AWS → cloud services
+
Module 6: Migration → migration patterns
+
Module 7: Platforms → the platform you chose here
↓
Module 8: AI system deployed, validated, documented
The platform you chose in this module can be the production platform in M8. If your decision matrix says Railway, M8 uses Railway. The documentation you wrote here (comparison + matrix) gets incorporated into M8.
Decision Matrix v1 (M1) → base framework
↓
Decision Matrix v2 (M7) → extended with platforms
↓
Decision Matrix v_final (M8) → applied and verified with a real system
Your next step is M8: take your AI app, integrate the components from the whole guide (Docker + CI/CD + cloud services + chosen platform), and produce a deployed, validated and documented production system.
Project Resources
- Architecture Decision Records (ADR) — Format for documenting decisions
- Render Documentation — If you chose Render
- Railway Documentation — If you chose Railway
- Fly.io Documentation — If you chose Fly.io
- GitHub Actions — CI/CD reference
- Supabase Free Tier — Free PostgreSQL as an alternative