DEV Community

Ajit Kumar
Ajit Kumar

Posted on

From Zero to Cached: Building a High-Performance Housing Portal with Django, Next.js, and Redis- Part 7 - Deployment

Part 7: Deployment — From localhost:3000 to production.yoursite.com

In Part 1, we containerized everything. In Part 2, we built the database. In Part 3, we cached the API. In Part 4, we optimized queries. In Part 5, we built the UI. In Part 6, we integrated the CDN. Today, we deploy to production.


If you're jumping in here, you need the full context. Part 1 through Part 6 built a complete, optimized housing portal that runs on localhost. If you're continuing from Part 6, you have a working app with images, caching, and all the features — but only you can access it.

Today we make it public. By the end of this post, you'll understand deployment architectures, choose the right hosting strategy for your needs, and have a live site accessible from anywhere. We'll cover four complete deployment paths — from completely free (for demos) to enterprise-grade AWS infrastructure.


The Deployment Challenge

Deploying a full-stack application isn't like deploying a simple website. We have:

  • A Next.js frontend — needs Node.js, build process, edge caching
  • A Django backend — needs Python, Gunicorn, environment variables
  • A PostgreSQL database — needs persistence, backups, connection pooling
  • A Redis cache — needs memory, persistence configuration
  • A Cloudinary CDN — already deployed, just needs credentials
  • Static files — CSS, JS, images that need to be served efficiently
  • Environment variables — different for dev, staging, production
  • SSL certificates — HTTPS is mandatory for production
  • Domain names — custom domains or free subdomains

There isn't one "right" way to deploy all of this. The right choice depends on your budget, traffic, technical comfort, and requirements.

This post shows you four complete deployment architectures and helps you choose:


Part A: The Architecture Decision Tree

Before we deploy anything, we need to understand the options.

The Four Deployment Paths

Path 0: The Free Tier (Demo/Portfolio)

  • Cost: $0/month
  • Complexity: Low-Medium
  • Best for: This blog series, demos, portfolio projects, MVPs
  • Tradeoff: Cold starts (30s delay after inactivity), usage limits

Path 1: Railway Only (Simple)

  • Cost: ~$20/month
  • Complexity: Low
  • Best for: Side projects, MVPs, small apps
  • Tradeoff: Higher cost at scale, less control

Path 2: Vercel + Railway (Optimal)

  • Cost: ~$15-35/month
  • Complexity: Medium
  • Best for: Production apps, startups, SaaS
  • Tradeoff: More moving parts to manage

Path 3: AWS DIY (Enterprise)

  • Cost: ~$90+/month
  • Complexity: High
  • Best for: Enterprise, compliance needs, learning DevOps
  • Tradeoff: Requires infrastructure knowledge

Decision Criteria

Choose Path 0 (Free Tier) if:

  • ✅ Following this blog series and don't want to spend money
  • ✅ Building a portfolio project or demo
  • ✅ Testing an MVP with < 10 daily users
  • ✅ Cold starts (30s first load) are acceptable
  • ✅ You plan to upgrade when you get traction

Choose Path 1 (Railway Only) if:

  • ✅ Want the simplest deployment possible
  • ✅ Have a small budget ($20/month)
  • ✅ Don't want to manage infrastructure
  • ✅ Need automatic SSL and deployments
  • ✅ Okay with vendor lock-in

Choose Path 2 (Vercel + Railway) if:

  • ✅ Need production-grade performance
  • ✅ Want global edge caching for frontend
  • ✅ Can manage multiple services
  • ✅ Need 99.9% uptime
  • ✅ This is a real product with users

Choose Path 3 (AWS DIY) if:

  • ✅ Need full control of infrastructure
  • ✅ Have compliance requirements (HIPAA, SOC2)
  • ✅ Want to learn DevOps
  • ✅ Have high traffic (100k+ requests/day)
  • ✅ Can manage servers

📊 [Architecture Decision Tree]

Architecture Decision Tree

Architecture Patterns Comparison

Let's visualize each architecture before we dive into implementation.

Pattern 1: The Monolith (Railway Only)

Everything runs on one platform. Simple but less optimized.

User → Railway (Frontend + Backend + DB + Redis)
Enter fullscreen mode Exit fullscreen mode

📊 [Monolith Architecture]

Monolith Architecture

Pattern 2: The Modern Split (Vercel + Railway)

Frontend on edge (Vercel), backend on PaaS (Railway). Optimal performance.

User → Vercel Edge (Next.js) → Railway (Django + DB + Redis)
Enter fullscreen mode Exit fullscreen mode

📊 [Modern Split Architecture]

Modern Split Architecture

Pattern 3: The Cloud DIY (AWS)

Self-managed infrastructure. Maximum control, maximum complexity.

User → CloudFront → ALB → EC2 (Django) → RDS (PostgreSQL)
                                        → ElastiCache (Redis)
Enter fullscreen mode Exit fullscreen mode

📊 [AWS Architecture]

AWS Architect


Cost vs Complexity Matrix

Deployment Path Monthly Cost Setup Time Maintenance Performance Scalability
Path 0: Free Tier $0 2 hours None 3/5 ⭐ Limited
Path 1: Railway $20 1 hour Minimal 4/5 ⭐ Medium
Path 2: Vercel+Railway $15-35 3 hours Low 5/5 ⭐ High
Path 3: AWS DIY $90+ 8 hours High 5/5 ⭐ Very High

For this tutorial series, Path 0 (Free Tier) is perfect for learning. When you're ready to launch, Path 2 (Vercel + Railway) is the sweet spot.


Part B: Production Readiness — Preparing the Code

Before we deploy anywhere, we need to prepare our code for production. The settings that work in development are not safe for production.

Step 1: Create Production Django Settings

We'll use a split settings pattern: settings/base.py, settings/development.py, settings/production.py.

Create backend/core/settings/ directory:

mkdir -p backend/core/settings
Enter fullscreen mode Exit fullscreen mode

Move existing settings to base:

mv backend/core/settings.py backend/core/settings/base.py
Enter fullscreen mode Exit fullscreen mode

Create backend/core/settings/__init__.py:

"""
core/settings/__init__.py

Import the correct settings based on DJANGO_ENV environment variable.
Defaults to development if not set.
"""

import os

env = os.environ.get('DJANGO_ENV', 'development')

if env == 'production':
    from .production import *
elif env == 'staging':
    from .staging import *
else:
    from .development import *
Enter fullscreen mode Exit fullscreen mode

Create backend/core/settings/development.py:

"""
core/settings/development.py

Development-specific settings.
Imports from base and overrides as needed.
"""

from .base import *

DEBUG = True

ALLOWED_HOSTS = ['*']

# Use console email backend in development
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# Show SQL queries in debug toolbar
DEBUG_TOOLBAR_CONFIG = {
    'SHOW_TOOLBAR_CALLBACK': lambda request: DEBUG,
}
Enter fullscreen mode Exit fullscreen mode

Create backend/core/settings/production.py:

"""
core/settings/production.py

Production settings with security hardening.
"""

from .base import *
import os

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

# Must be set in environment variables
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',')

# Security settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Use a strong secret key from environment
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
    raise ValueError('SECRET_KEY environment variable must be set in production')

# Database connection pooling
DATABASES['default']['CONN_MAX_AGE'] = 600  # 10 minutes

# Static files (WhiteNoise)
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# CORS settings for production
CORS_ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '').split(',')

# Logging configuration
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console'],
        'level': 'INFO',
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}
Enter fullscreen mode Exit fullscreen mode

Install WhiteNoise for static file serving:

cd backend
pip install whitenoise
pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Next.js Environment Files

Create frontend/.env.production:

# Production environment variables
# Update these with your actual production URLs

NEXT_PUBLIC_API_URL=https://your-backend.railway.app
# or
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Create frontend/.env.local.example:

# Example environment variables
# Copy this to .env.local and fill in your values

NEXT_PUBLIC_API_URL=http://localhost:8000
Enter fullscreen mode Exit fullscreen mode

Update frontend/lib/api.ts to use environment variables:

/**
 * lib/api.ts
 * 
 * Updated to use environment variables for production.
 */

const getBaseURL = () => {
  // In production, use the environment variable
  if (process.env.NEXT_PUBLIC_API_URL) {
    return process.env.NEXT_PUBLIC_API_URL;
  }

  // Development: differentiate server vs client
  if (typeof window === 'undefined') {
    return 'http://backend:8000';
  }
  return 'http://localhost:8000';
};

export const API_BASE_URL = getBaseURL();

// ... rest of the file stays the same
Enter fullscreen mode Exit fullscreen mode

Step 3: Update Dependencies

Create backend/requirements/base.txt:

Django==5.0.1
djangorestframework==3.14.0
django-cors-headers==4.3.1
django-filter==23.5
psycopg2-binary==2.9.9
redis==5.0.1
django-redis==5.4.0
cloudinary==1.37.0
django-cloudinary-storage==0.3.0
gunicorn==21.2.0
whitenoise==6.6.0
Enter fullscreen mode Exit fullscreen mode

Create backend/requirements/production.txt:

-r base.txt

# Production-specific packages
sentry-sdk==1.39.2
Enter fullscreen mode Exit fullscreen mode

Create backend/requirements/development.txt:

-r base.txt

# Development-specific packages
django-debug-toolbar==4.2.0
Enter fullscreen mode Exit fullscreen mode

Update backend/requirements.txt:

-r requirements/production.txt
Enter fullscreen mode Exit fullscreen mode

Step 4: Create .gitignore

Ensure these are in .gitignore:

# Environment variables
.env
.env.local
.env.production
.env.staging

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/

# Django
*.log
db.sqlite3
staticfiles/
mediafiles/

# Next.js
.next/
out/
node_modules/

# IDEs
.vscode/
.idea/
*.swp
*.swo
Enter fullscreen mode Exit fullscreen mode

Step 5: Generate Production Secrets

Generate a secure SECRET_KEY for Django:

python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
Enter fullscreen mode Exit fullscreen mode

Save this — you'll need it in environment variables.


Part C: Path 0 — The Free Tier Deployment (RECOMMENDED FOR THIS TUTORIAL)

This is the perfect path for following this blog series without spending money. We'll use 100% free tiers.

What we're deploying:

  • Frontend: Vercel (free tier)
  • Backend: Render.com (free tier)
  • Database: Neon.tech (free PostgreSQL, 0.5GB)
  • Redis: Upstash (free tier, 10k requests/day)
  • Images: Cloudinary (already set up, free tier)

Total cost: $0/month

Tradeoffs to accept:

  • Backend sleeps after 15 minutes of inactivity (30-second cold start on first request)
  • Database limited to 0.5GB (~50,000 property records)
  • Redis limited to 10,000 requests per day
  • Free subdomains only (no custom domain)

This is perfect for demos, portfolios, and learning deployments.

Step 1: Deploy Database (Neon.tech)

  1. Go to neon.tech
  2. Sign up with GitHub (no credit card required)
  3. Click "Create a project"
  4. Name: housing-portal-db
  5. Region: Choose closest to you (us-east-1, eu-central-1, etc.)
  6. Click "Create project"

You'll get a connection string like:

postgres://username:password@ep-long-string.us-east-1.aws.neon.tech/neondb?sslmode=require
Enter fullscreen mode Exit fullscreen mode

Save this — you'll need it for the backend.

Neon automatically handles:

  • ✅ SSL connections
  • ✅ Automatic backups
  • ✅ Connection pooling
  • ✅ Scales to zero (no usage = no resources used)

Step 2: Deploy Redis (Upstash)

  1. Go to upstash.com
  2. Sign up with GitHub
  3. Click "Create Database"
  4. Name: housing-portal-redis
  5. Type: Regional
  6. Region: Choose same as Neon (for lower latency)
  7. Eviction: No eviction
  8. Click "Create"

You'll get a connection string like:

redis://default:password@us1-host.upstash.io:port
Enter fullscreen mode Exit fullscreen mode

Or REST URL (alternative if Redis protocol doesn't work):

https://us1-host.upstash.io
Enter fullscreen mode Exit fullscreen mode

Save this connection string.

Step 3: Deploy Backend (Render.com)

  1. Go to render.com
  2. Sign up with GitHub
  3. Click "New +" → "Web Service"
  4. Connect your GitHub repository
  5. Select the housing-caching-demo repository
  6. Configure:

Settings:

  • Name: housing-portal-backend
  • Region: Same as database
  • Branch: main
  • Root Directory: backend
  • Runtime: Python 3
  • Build Command: pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate
  • Start Command: gunicorn core.wsgi:application --bind 0.0.0.0:$PORT
  • Instance Type: Free

Environment Variables:
Click "Add Environment Variable" for each:

DJANGO_ENV=production
SECRET_KEY=<your-generated-secret-key>
DEBUG=False
ALLOWED_HOSTS=<will-fill-after-deploy>.onrender.com
CSRF_TRUSTED_ORIGINS=https://<will-fill-after-deploy>.onrender.com
DATABASE_URL=<neon-postgres-url>
REDIS_URL=<upstash-redis-url>
CLOUDINARY_CLOUD_NAME=<your-cloudinary-name>
CLOUDINARY_API_KEY=<your-cloudinary-key>
CLOUDINARY_API_SECRET=<your-cloudinary-secret>
CORS_ALLOWED_ORIGINS=https://your-app.vercel.app
Enter fullscreen mode Exit fullscreen mode

Note: You'll update ALLOWED_HOSTS and CORS_ALLOWED_ORIGINS after you know the actual URLs.

  1. Click "Create Web Service"

Render will:

  • Build your app (takes 2-3 minutes)
  • Run migrations
  • Start the server

You'll get a URL like: https://housing-portal-backend.onrender.com

Test it:

curl https://housing-portal-backend.onrender.com/api/properties/cached/
Enter fullscreen mode Exit fullscreen mode

If you get JSON back, it works! If you get a 403 or CORS error, that's expected — we haven't configured CORS yet.

Now go back and update environment variables:

ALLOWED_HOSTS=housing-portal-backend.onrender.com
CSRF_TRUSTED_ORIGINS=https://housing-portal-backend.onrender.com
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy Frontend (Vercel)

  1. Go to vercel.com
  2. Sign up with GitHub
  3. Click "Add New..." → "Project"
  4. Import your housing-caching-demo repository
  5. Configure:

Settings:

  • Framework Preset: Next.js
  • Root Directory: frontend
  • Build Command: npm run build (default)
  • Output Directory: .next (default)

Environment Variables:
Click "Add" for each environment:

NEXT_PUBLIC_API_URL=https://housing-portal-backend.onrender.com
Enter fullscreen mode Exit fullscreen mode
  1. Click "Deploy"

Vercel will:

  • Install dependencies
  • Build the Next.js app
  • Deploy to edge network globally

You'll get a URL like: https://housing-portal-abc123.vercel.app

Test it:
Open that URL in your browser. You should see your housing portal!

Step 5: Connect Frontend to Backend (CORS)

Now update the backend CORS settings to allow requests from Vercel:

Go back to Render.com → your backend service → Environment → Edit CORS_ALLOWED_ORIGINS:

CORS_ALLOWED_ORIGINS=https://housing-portal-abc123.vercel.app
Enter fullscreen mode Exit fullscreen mode

Click "Save Changes" → Render will redeploy.

Test again:
Refresh your Vercel URL. The property listings should load.

Step 6: Understanding Cold Starts

The First Request:
Since Render's free tier sleeps after 15 minutes of inactivity, the first request will:

  1. Wake up the server (15-30 seconds)
  2. Connect to database
  3. Return response

Subsequent Requests:
Instant (as long as you keep using it within 15 minutes).

How to explain this to users:
Add a note in your README or on the site:

"This is a demo deployed on free hosting. The first load may take 30 seconds as the server wakes up. Subsequent loads are instant."

Step 7: Verifying the Deployment

Check each component:

  1. Frontend: https://your-app.vercel.app — should load the homepage
  2. Backend API: https://your-backend.onrender.com/api/properties/cached/ — should return JSON
  3. Database: Properties should display (proves DB connection works)
  4. Redis: Navigate between pages quickly (proves cache works)
  5. Images: Property cards should show images (proves Cloudinary works)

Open DevTools and check:

  • Network tab → No CORS errors
  • No console errors
  • Images load from res.cloudinary.com

📊 [Exercise / Showcase Free Tier Deployment - Live Site]

Instructions: Capture a screenshot of your deployed site on Vercel showing property listings loading successfully.


Step 8: Free Tier Limits and Monitoring

Neon.tech:

  • 0.5GB storage (check usage in dashboard)
  • Unlimited queries
  • Auto-scales to zero (saves resources when not in use)

Upstash:

  • 10,000 requests per day
  • Dashboard shows usage
  • Resets daily

Render.com:

  • 750 hours/month (31 days = 744 hours — enough for 1 service)
  • 512MB RAM
  • Sleeps after 15 min inactivity

Vercel:

  • Unlimited bandwidth (fair use)
  • Unlimited deployments
  • 100GB bandwidth/month (more than enough)

Cloudinary:

  • 25GB storage
  • 25GB bandwidth/month
  • Unlimited transformations

When you'll hit limits:

  • Upstash: ~400 users per day (assuming 25 requests each)
  • Neon: ~50,000 properties (way more than needed for demo)
  • Never hitting Vercel/Cloudinary limits for a demo

Step 9: Migration Path (When to Upgrade)

Free Tier is fine when:

  • < 50 daily active users
  • Demo/portfolio project
  • MVP validation
  • Cold starts are acceptable

Upgrade to Railway ($5/month) when:

  • 50-500 daily active users
  • Can't tolerate cold starts
  • Need custom domain
  • Exceed Redis 10k requests/day

Upgrade to Vercel + Railway ($15-35/month) when:

  • 500+ daily active users
  • Need 99.9% uptime
  • Global audience (Vercel edge network)
  • This is a real business

Step 10: Adding a Custom Domain (Optional)

Even on free tiers, you can add a custom domain if you have one.

Vercel:

  1. Go to your project → Settings → Domains
  2. Add your domain (e.g., housing.yourdomain.com)
  3. Update DNS:
    • Type: CNAME
    • Name: housing
    • Value: cname.vercel-dns.com
  4. Vercel automatically provisions SSL

Render:

  1. Go to your service → Settings → Custom Domains
  2. Add api.yourdomain.com
  3. Update DNS:
    • Type: CNAME
    • Name: api
    • Value: your-service.onrender.com
  4. Render automatically provisions SSL

Update environment variables:

  • Render: ALLOWED_HOSTS=api.yourdomain.com
  • Render: CSRF_TRUSTED_ORIGINS=https://api.yourdomain.com
  • Vercel: (no change needed)

Path 0 Complete! You now have a fully deployed housing portal for $0/month. This is perfect for following the tutorial, building your portfolio, or validating an MVP.


Part D: Path 1 — Railway Only (Simple)

If you need to avoid cold starts and want everything in one place, Railway is the simplest paid option.

What we're deploying:

  • Frontend: Railway
  • Backend: Railway
  • Database: Railway PostgreSQL
  • Redis: Railway Redis

Cost: ~$20/month ($5/service × 4 services)

Pros:

  • No cold starts
  • Everything in one dashboard
  • Automatic SSL
  • Easy scaling
  • Preview deployments

Step 1: Create Railway Account

  1. Go to railway.app
  2. Sign up with GitHub
  3. Click "New Project"
  4. Select "Deploy from GitHub repo"
  5. Choose housing-caching-demo

Step 2: Add Services

Railway will create one service by default. We need 4 total.

Add PostgreSQL:

  1. Click "New" → "Database" → "Add PostgreSQL"
  2. Railway provisions a database automatically
  3. Connection string appears in the service variables

Add Redis:

  1. Click "New" → "Database" → "Add Redis"
  2. Railway provisions Redis automatically

Add Backend Service:

  1. Click "New" → "GitHub Repo" → Select your repo
  2. Configure:
    • Root Directory: backend
    • Build Command: pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate
    • Start Command: gunicorn core.wsgi:application --bind 0.0.0.0:$PORT

Add Frontend Service:

  1. Click "New" → "GitHub Repo" → Select same repo
  2. Configure:
    • Root Directory: frontend
    • Build Command: npm install && npm run build
    • Start Command: npm start

Step 3: Configure Environment Variables

Backend Service:
Click on backend → Variables → Add Reference Variables:

Railway automatically creates DATABASE_URL and REDIS_URL when you add those services.

Add these manually:

DJANGO_ENV=production
SECRET_KEY=<your-secret-key>
DEBUG=False
ALLOWED_HOSTS=${{RAILWAY_PUBLIC_DOMAIN}}
CSRF_TRUSTED_ORIGINS=https://${{RAILWAY_PUBLIC_DOMAIN}}
CLOUDINARY_CLOUD_NAME=<your-value>
CLOUDINARY_API_KEY=<your-value>
CLOUDINARY_API_SECRET=<your-value>
CORS_ALLOWED_ORIGINS=https://${{frontend.RAILWAY_PUBLIC_DOMAIN}}
Enter fullscreen mode Exit fullscreen mode

Frontend Service:

NEXT_PUBLIC_API_URL=https://${{backend.RAILWAY_PUBLIC_DOMAIN}}
Enter fullscreen mode Exit fullscreen mode

The ${{}} syntax is Railway's variable reference system. It automatically resolves to the actual domain of each service.

Step 4: Generate Public Domains

Each service gets a Railway domain automatically:

  1. Click backend service → Settings → Public Networking → Generate Domain
  2. Click frontend service → Settings → Public Networking → Generate Domain

You'll get:

  • Backend: https://housing-backend-production-abc.up.railway.app
  • Frontend: https://housing-frontend-production-xyz.up.railway.app

Step 5: Deploy

Railway automatically deploys when you:

  • Push to GitHub
  • Change environment variables
  • Update settings

Watch the deployment logs in each service.

Test:
Open the frontend URL. Everything should work!

Step 6: Custom Domain (Optional)

Add custom domain to frontend:

  1. Frontend service → Settings → Custom Domains
  2. Add housing.yourdomain.com
  3. Update DNS (CNAME to Railway domain)
  4. Railway provisions SSL automatically

Add custom domain to backend:

  1. Backend service → Settings → Custom Domains
  2. Add api.yourdomain.com
  3. Update DNS
  4. Update CORS_ALLOWED_ORIGINS to include new domain

Step 7: Railway Pricing

Railway charges $5/month per service that uses resources.

Free tier:

  • $5 free credit/month
  • Shared resources
  • Good for one service

Hobby plan:

  • $5/service/month
  • Dedicated resources
  • No sleep
  • Automatic scaling

For our 4 services:

  • PostgreSQL: $5/month
  • Redis: $5/month
  • Backend: $5/month
  • Frontend: $5/month Total: $20/month

Part E: Path 2 — Vercel + Railway (RECOMMENDED FOR PRODUCTION)

This is the optimal architecture for production apps. Frontend on Vercel's edge network (global CDN), backend on Railway (simple PaaS).

What we're deploying:

  • Frontend: Vercel (edge network, 100+ locations globally)
  • Backend: Railway
  • Database: Railway PostgreSQL
  • Redis: Railway Redis or Upstash

Cost: $15-35/month

  • Vercel: Free (hobby) or $20/month (pro)
  • Railway: $15/month (3 services: backend, DB, Redis)

Why this is optimal:

  • ✅ Frontend served from edge (lowest latency globally)
  • ✅ Backend centralized (simpler to manage)
  • ✅ Best performance-to-cost ratio
  • ✅ Each component runs on its optimal platform

Step 1: Deploy Backend to Railway

Follow the same steps as Path 1 for:

  • PostgreSQL service
  • Redis service
  • Backend service

Environment variables:

DJANGO_ENV=production
SECRET_KEY=<your-secret>
ALLOWED_HOSTS=${{RAILWAY_PUBLIC_DOMAIN}}
CSRF_TRUSTED_ORIGINS=https://${{RAILWAY_PUBLIC_DOMAIN}}
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
CLOUDINARY_CLOUD_NAME=<your-value>
CLOUDINARY_API_KEY=<your-value>
CLOUDINARY_API_SECRET=<your-value>
CORS_ALLOWED_ORIGINS=https://your-app.vercel.app
Enter fullscreen mode Exit fullscreen mode

Generate domain for backend.

You'll get: https://housing-backend-production.up.railway.app

Step 2: Deploy Frontend to Vercel

  1. Go to vercel.com
  2. Import project from GitHub
  3. Configure:

    • Root Directory: frontend
    • Framework: Next.js
    • Environment Variable:
     NEXT_PUBLIC_API_URL=https://housing-backend-production.up.railway.app
    
  4. Deploy

You'll get: https://housing-portal.vercel.app

Step 3: Update CORS

Go back to Railway → Backend → Environment Variables → Update:

CORS_ALLOWED_ORIGINS=https://housing-portal.vercel.app
Enter fullscreen mode Exit fullscreen mode

Save and redeploy.

Step 4: Test the Integration

Open https://housing-portal.vercel.app:

  • First load: Served from Vercel edge (fast globally)
  • API calls: Go to Railway backend
  • Images: Served from Cloudinary CDN
  • Static files: Served from Vercel edge

Check DevTools:

  • Network tab → API calls to housing-backend-production.up.railway.app
  • Images from res.cloudinary.com
  • HTML/CSS/JS from Vercel edge

Step 5: Add Custom Domains

Frontend:

  1. Vercel → Project → Settings → Domains
  2. Add housing.yourdomain.com and www.housing.yourdomain.com
  3. Update DNS:
   CNAME  housing  cname.vercel-dns.com
   CNAME  www      cname.vercel-dns.com
Enter fullscreen mode Exit fullscreen mode
  1. Vercel provisions SSL (automatic)

Backend:

  1. Railway → Backend → Settings → Custom Domains
  2. Add api.yourdomain.com
  3. Update DNS:
   CNAME  api  housing-backend-production.up.railway.app
Enter fullscreen mode Exit fullscreen mode
  1. Railway provisions SSL (automatic)

Update environment variables:

  • Railway: ALLOWED_HOSTS=api.yourdomain.com
  • Railway: CORS_ALLOWED_ORIGINS=https://housing.yourdomain.com,https://www.housing.yourdomain.com
  • Vercel: NEXT_PUBLIC_API_URL=https://api.yourdomain.com

Step 6: Performance Optimization

Vercel Edge Caching:
Next.js automatically caches static assets on Vercel's edge network. No configuration needed.

SWR Client Caching:
Already implemented in Part 5. Works automatically.

Railway Connection Pooling:
Railway handles this automatically.

Result:

  • US user: ~50ms TTFB
  • EU user: ~80ms TTFB
  • Asia user: ~150ms TTFB

Much better than a single-region deployment!


📊 [Exercise: Vercel Deployment Dashboard]

Instructions: Capture screenshot of Vercel deployment success page showing build logs and deployment URL.



Part F: Path 3 — AWS DIY (Advanced)

This is for readers who want full control or need to learn infrastructure.

What we're deploying:

  • Frontend: Vercel (or AWS Amplify if you want 100% AWS)
  • Backend: EC2 instance with Nginx + Gunicorn
  • Database: RDS PostgreSQL
  • Redis: ElastiCache
  • Load Balancer: Application Load Balancer
  • SSL: AWS Certificate Manager
  • Static files: S3 + CloudFront (optional, or use WhiteNoise)

Cost: ~$90/month

  • EC2 t3.small: $15/month
  • RDS db.t3.micro: $15/month
  • ElastiCache cache.t3.micro: $12/month
  • ALB: $16/month
  • S3/CloudFront: $5/month
  • Data transfer: $10-20/month

Complexity: High

This section provides the architecture and key steps. Full AWS deployment would be a separate multi-part series.

AWS Architecture Overview

Internet
  ↓
Route 53 (DNS)
  ↓
CloudFront (optional, for static files)
  ↓
Application Load Balancer
  ↓
EC2 Instance (Django + Gunicorn + Nginx)
  ↓
├─ RDS PostgreSQL (VPC, private subnet)
└─ ElastiCache Redis (VPC, private subnet)
Enter fullscreen mode Exit fullscreen mode

High-Level Steps (Not Complete Tutorial)

Due to complexity, this is an overview. For full AWS deployment, refer to the Django Deployment Guide by SaaS Pegasus.

1. Provision Infrastructure:

  • Create VPC with public/private subnets
  • Launch RDS PostgreSQL in private subnet
  • Launch ElastiCache Redis in private subnet
  • Launch EC2 instance in public subnet
  • Configure security groups (allow port 80, 443, 22)

2. Set Up EC2 Instance:

SSH into instance:

ssh -i your-key.pem ubuntu@ec2-instance-ip
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

sudo apt update
sudo apt install python3-pip python3-venv nginx postgresql-client redis-tools
Enter fullscreen mode Exit fullscreen mode

Clone your repository:

git clone https://github.com/yourusername/housing-caching-demo.git
cd housing-caching-demo/backend
Enter fullscreen mode Exit fullscreen mode

Create virtual environment:

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

3. Configure Gunicorn:

Create /etc/systemd/system/gunicorn.service:


4. Configure Nginx:

Create /etc/nginx/sites-available/housing-portal:


📄 [GIST 1: nginx.conf]

Gist URL: https://gist.github.com/urwithajit9/11b25980f75649279fec917f05cb5565


5. SSL with Let's Encrypt:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Certbot automatically updates Nginx config for HTTPS.

6. Set Up Application:

Set environment variables in /home/ubuntu/.env:

DJANGO_ENV=production
SECRET_KEY=...
DATABASE_URL=postgres://user:pass@rds-endpoint:5432/housing_db
REDIS_URL=redis://elasticache-endpoint:6379
...
Enter fullscreen mode Exit fullscreen mode

Run migrations:

python manage.py migrate
python manage.py collectstatic --noinput
Enter fullscreen mode Exit fullscreen mode

Start services:

sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

7. Deploy Frontend:

Either:

  • Deploy to Vercel (same as Path 2)
  • Or deploy to AWS Amplify
  • Or serve from same EC2 with Nginx

8. Set Up Load Balancer (Optional but Recommended):

  • Create Application Load Balancer
  • Point to EC2 instance(s)
  • Configure health checks
  • Update Route 53 to point to ALB instead of EC2 directly

9. Auto Scaling (Optional):

  • Create Auto Scaling Group
  • Define scaling policies
  • ALB distributes traffic across instances

When to Use AWS DIY

Use AWS when:

  • You need full control (compliance, security requirements)
  • You're learning DevOps
  • You have high traffic (100k+ requests/day)
  • You're already on AWS ecosystem
  • You need custom infrastructure (VPN, peering, etc.)

Don't use AWS when:

  • You're building a side project (overkill)
  • You don't have DevOps experience
  • You want to focus on features, not infrastructure
  • You're a solo developer (too much to manage)

AWS Cost Optimization Tips

  1. Use Reserved Instances: Save 30-40% by committing to 1 year
  2. Right-size instances: Start with t3.micro, scale up only when needed
  3. Use S3 for static files: Cheaper than serving from EC2
  4. Enable CloudWatch alarms: Catch runaway costs early
  5. Use AWS Free Tier: RDS and EC2 have free tiers for 12 months

Part G: Domain and SSL Configuration

Regardless of which path you chose, you'll want custom domains and HTTPS.

Step 1: Register a Domain

Popular registrars:

  • Namecheap: ~$10/year for .com
  • Google Domains: ~$12/year
  • Cloudflare Registrar: At-cost pricing (~$9/year)
  • Porkbun: Budget option (~$8/year)

For this tutorial, we'll use housing-demo.com as an example.

Step 2: DNS Configuration

You need to point your domain to your deployed services.

For Vercel (Frontend):

Type: CNAME
Name: housing (or www or @)
Value: cname.vercel-dns.com
TTL: 3600
Enter fullscreen mode Exit fullscreen mode

For Railway (Backend):

Type: CNAME
Name: api
Value: your-service.up.railway.app
TTL: 3600
Enter fullscreen mode Exit fullscreen mode

For Render (Backend):

Type: CNAME
Name: api
Value: your-service.onrender.com
TTL: 3600
Enter fullscreen mode Exit fullscreen mode

For AWS (Backend with ALB):

Type: A (Alias)
Name: api
Value: your-alb.us-east-1.elb.amazonaws.com
TTL: 3600
Enter fullscreen mode Exit fullscreen mode

Step 3: SSL Certificates

Vercel: Automatic. Provisions SSL within minutes of adding domain.

Railway: Automatic. Provisions SSL via Let's Encrypt.

Render: Automatic. Provisions SSL via Let's Encrypt.

AWS: Use AWS Certificate Manager:

  1. Request certificate for api.yourdomain.com
  2. Verify via email or DNS
  3. Attach certificate to ALB

Self-managed (VPS): Use Certbot:

sudo certbot --nginx -d api.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Step 4: Force HTTPS

Django (already configured in production.py):

SECURE_SSL_REDIRECT = True
Enter fullscreen mode Exit fullscreen mode

Nginx (if using):

server {
    listen 80;
    server_name api.yourdomain.com;
    return 301 https://$server_name$request_uri;
}
Enter fullscreen mode Exit fullscreen mode

Next.js: Handled by platform (Vercel/Railway).

Step 5: DNS Propagation

After updating DNS, changes take time to propagate:

  • Minimum: 5 minutes
  • Typical: 1-2 hours
  • Maximum: 48 hours (rare)

Check propagation:

dig housing-demo.com
dig api.housing-demo.com
Enter fullscreen mode Exit fullscreen mode

Or use online tool: dnschecker.org

Step 6: Test HTTPS

curl -I https://housing-demo.com
# Should show: HTTP/2 200
# Should NOT redirect to HTTP

curl -I https://api.housing-demo.com/api/properties/cached/
# Should show: HTTP/2 200
# Should return JSON
Enter fullscreen mode Exit fullscreen mode

Part H: CI/CD Pipeline with GitHub Actions

Automate deployments so every push to main deploys automatically.

Deployment Flow

Developer pushes to GitHub
  ↓
GitHub Actions triggers
  ↓
Run tests
  ↓
Deploy to Vercel (frontend)
  ↓
Deploy to Railway/Render (backend)
  ↓
Run migrations
  ↓
Send notification (Slack/email)
Enter fullscreen mode Exit fullscreen mode

📊 [ CI/CD Pipeline Flow]

CI/CD Pipeline Flow


GitHub Actions for Frontend (Vercel)

Vercel automatically deploys on push if you connected via GitHub. No action needed!

But if you want explicit control, create .github/workflows/deploy-frontend.yml:


📄 [GIST 2: deploy-frontend.yml]

GitHub Actions workflow for deploying frontend to Vercel.
Gist URL: hhttps://gist.github.com/urwithajit9/a68c22276c30501a6b4d14a06d86df3b


GitHub Actions for Backend (Railway)

Railway also auto-deploys on push. But for explicit control:

.github/workflows/deploy-backend.yml:


📄 [GIST 3: deploy-backend.yml]

GitHub Actions workflow for deploying backend to Railway.
Gist URL: https://gist.github.com/urwithajit9/cc0026c991cf7669a0083f396b64c9a6


GitHub Actions for Backend (AWS)

For AWS, you need to SSH into the server and pull latest code:

.github/workflows/deploy-aws.yml:


📄 [GIST 4: deploy-aws.yml]

GitHub Actions workflow for deploying to AWS EC2.
Gist URL: https://gist.github.com/urwithajit9/880d644d7026a2cc1cb4bc4d03c32282


Environment-Specific Deployments

Create separate workflows for staging and production:

.github/workflows/
├── deploy-staging.yml    # Triggers on push to 'staging' branch
└── deploy-production.yml # Triggers on push to 'main' branch
Enter fullscreen mode Exit fullscreen mode

This lets you test changes in staging before production.


Part I: Monitoring and Maintenance

Production apps need monitoring to catch errors, track performance, and ensure uptime.

Step 1: Error Tracking with Sentry

Sentry captures errors from both frontend and backend.

1. Create Sentry account:

  • Go to sentry.io
  • Sign up (free tier: 5k errors/month)
  • Create a project for Django
  • Create a project for Next.js

2. Install Sentry in Django:

pip install sentry-sdk
Enter fullscreen mode Exit fullscreen mode

Add to backend/core/settings/production.py:

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn=os.environ.get('SENTRY_DSN'),
    integrations=[DjangoIntegration()],
    traces_sample_rate=0.1,  # 10% of transactions
    send_default_pii=False,
)
Enter fullscreen mode Exit fullscreen mode

Add SENTRY_DSN to environment variables.

3. Install Sentry in Next.js:

npm install @sentry/nextjs
Enter fullscreen mode Exit fullscreen mode

Run configuration wizard:

npx @sentry/wizard@latest -i nextjs
Enter fullscreen mode Exit fullscreen mode

This creates sentry.client.config.js, sentry.server.config.js, etc.

Add NEXT_PUBLIC_SENTRY_DSN to Vercel environment variables.

4. Test error tracking:

Trigger an error in Django:

# In a view
raise Exception("Test error from Django")
Enter fullscreen mode Exit fullscreen mode

Trigger an error in Next.js:

// In a component
throw new Error("Test error from Next.js")
Enter fullscreen mode Exit fullscreen mode

Check Sentry dashboard — errors should appear.


📊 [Exercise: Sentry Error Dashboard]

Instructions: Capture screenshot of Sentry dashboard showing captured errors from both Django and Next.js.


Step 2: Uptime Monitoring

Use UptimeRobot (free tier) to monitor if your site is down.

1. Create account: uptimerobot.com

2. Add monitors:

  • Frontend: https://housing-demo.com (HTTP monitor, check every 5 min)
  • Backend: https://api.housing-demo.com/api/properties/cached/ (HTTP monitor)

3. Configure alerts:

  • Email when down
  • Email when back up
  • Optional: Slack, Discord, SMS

4. Public status page (optional):
UptimeRobot can generate a public status page: https://stats.uptimerobot.com/ABC123

You can embed this on your site or share with users.


📊 [Exercise: UptimeRobot Dashboard]

Instructions: Capture screenshot showing uptime monitors for frontend and backend with 100% uptime.


Step 3: Performance Monitoring

Option 1: Vercel Analytics (Frontend)

  • Vercel → Project → Analytics
  • Automatically tracks Web Vitals (LCP, FID, CLS)
  • Free on all plans

Option 2: Railway Metrics (Backend)

  • Railway → Service → Metrics
  • Shows CPU, memory, request latency
  • Free on all plans

Option 3: New Relic or Datadog (Advanced)

  • Full APM (Application Performance Monitoring)
  • Tracks database queries, external API calls, etc.
  • Expensive ($100+/month)

For this tutorial, Vercel and Railway built-in metrics are sufficient.

Step 4: Log Aggregation

Railway/Render:

  • Built-in logs in dashboard
  • Can export to external services

AWS:

  • CloudWatch Logs (built-in)
  • Or use Papertrail, LogDNA, Datadog

Centralized logging pattern:

All services → Log aggregator (Papertrail) → Search/analyze
Enter fullscreen mode Exit fullscreen mode

This lets you search logs across all services in one place.

Step 5: Database Backups

Neon.tech:

  • Automatic daily backups (free tier)
  • Point-in-time recovery (paid tiers)

Railway:

  • Automatic daily backups
  • Can restore from dashboard

RDS:

  • Automatic daily backups (configurable)
  • Can restore to any point in time (within retention period)

Manual backup:

# Dump database
pg_dump $DATABASE_URL > backup.sql

# Restore
psql $DATABASE_URL < backup.sql
Enter fullscreen mode Exit fullscreen mode

Schedule this with cron or GitHub Actions.


Part J: Cost and Performance Comparison

Let's compare all four deployment paths side by side.

Cost Comparison

Component Path 0 (Free) Path 1 (Railway) Path 2 (Vercel+Railway) Path 3 (AWS)
Frontend Free (Vercel) $5 (Railway) Free (Vercel Hobby) $30 (EC2 or Vercel Pro)
Backend Free* (Render) $5 (Railway) $5 (Railway) $30 (EC2)
Database Free** (Neon) $5 (Railway) $5 (Railway) $15 (RDS)
Redis Free*** (Upstash) $5 (Railway) $5 (Railway or Upstash) $15 (ElastiCache)
Monitoring Free (UptimeRobot) Free (Railway) Free (Vercel Analytics) $20 (CloudWatch)
SSL Free (auto) Free (auto) Free (auto) Free (ACM)
Domain Optional $10/year Optional $10/year Optional $10/year Optional $10/year
TOTAL/month $0 $20 $15-20 $110

* Sleeps after 15min inactivity

** 0.5GB storage limit

*** 10k requests/day limit

Performance Comparison

Measured from different global locations:

Location Path 0 (Free) Path 1 (Railway) Path 2 (Vercel+Railway) Path 3 (AWS)
US East 150ms (cold start) / 80ms (warm) 50ms 30ms 40ms
US West 180ms / 100ms 80ms 40ms 120ms
Europe 250ms / 180ms 150ms 60ms 200ms
Asia 400ms / 300ms 280ms 100ms 350ms

Winner: Path 2 (Vercel + Railway) — Vercel's global edge network gives best latency worldwide.


📊 [Exercise: Actual Performance Metrics]

Instructions: Use tools like Pingdom or WebPageTest to measure actual TTFB from different locations. Record your results here.


Uptime Comparison

Deployment Path Expected Uptime SLA
Path 0 (Free) 95-98% None
Path 1 (Railway) 99%+ None (hobby) / 99.9% (pro)
Path 2 (Vercel+Railway) 99.9%+ 99.9% (Vercel Pro)
Path 3 (AWS) 99.95%+ 99.99% (with multi-AZ)

Scalability Comparison

Deployment Path Max Concurrent Users Bottleneck
Path 0 (Free) ~50 Redis rate limit (10k/day)
Path 1 (Railway) ~5,000 Database connections
Path 2 (Vercel+Railway) ~50,000 Backend capacity
Path 3 (AWS) ~500,000+ Cost 💰

Decision Matrix

Choose based on:

Stage → Deployment Path

Idea/Demo → Path 0 (Free)
MVP/Beta → Path 1 (Railway)
Launched/Growth → Path 2 (Vercel+Railway)
Enterprise/Scale → Path 3 (AWS)
Enter fullscreen mode Exit fullscreen mode

Part K: Troubleshooting Deployment

Common issues and solutions.

Issue 1: Static Files Not Loading

Symptom: CSS/JS broken, images don't load (404 errors).

Cause: Static files not collected or not served properly.

Fix:

Django:

python manage.py collectstatic --noinput
Enter fullscreen mode Exit fullscreen mode

Make sure in production settings:

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
Enter fullscreen mode Exit fullscreen mode

With WhiteNoise:

MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Enter fullscreen mode Exit fullscreen mode

Issue 2: CORS Errors

Symptom: Browser console shows:

Access to fetch at 'https://api.yourdomain.com' has been blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

Cause: Backend doesn't allow frontend domain in CORS settings.

Fix:

Check Django settings:

CORS_ALLOWED_ORIGINS = [
    'https://yourdomain.com',
    'https://www.yourdomain.com',
]
Enter fullscreen mode Exit fullscreen mode

Restart backend after changing.

Issue 3: 502 Bad Gateway

Symptom: Nginx shows 502 error.

Cause: Gunicorn not running or crashed.

Fix:

Check Gunicorn status:

sudo systemctl status gunicorn
Enter fullscreen mode Exit fullscreen mode

Check logs:

sudo journalctl -u gunicorn -n 50
Enter fullscreen mode Exit fullscreen mode

Restart:

sudo systemctl restart gunicorn
Enter fullscreen mode Exit fullscreen mode

Issue 4: Database Connection Errors

Symptom:

django.db.utils.OperationalError: could not connect to server
Enter fullscreen mode Exit fullscreen mode

Cause: Wrong DATABASE_URL or database not accessible.

Fix:

Test connection:

psql $DATABASE_URL
Enter fullscreen mode Exit fullscreen mode

Check:

  • Is DATABASE_URL correct?
  • Is database accepting connections from your server IP?
  • For RDS/Railway: Check security groups/firewall rules
  • For Neon: Check connection pooler settings

Issue 5: Environment Variables Not Loading

Symptom: Settings reference missing environment variables.

Cause: .env file not loaded or variables not set in platform.

Fix:

Railway/Render/Vercel:

  • Check dashboard → Service → Environment Variables
  • Redeploy after adding variables

AWS EC2:

  • Check /home/ubuntu/.env file exists
  • Source it in systemd service file

Local testing:

export DJANGO_ENV=production
python manage.py check --deploy
Enter fullscreen mode Exit fullscreen mode

Issue 6: SSL Certificate Errors

Symptom:

NET::ERR_CERT_AUTHORITY_INVALID
Enter fullscreen mode Exit fullscreen mode

Cause: SSL not provisioned yet or DNS not propagated.

Fix:

Wait for DNS propagation (up to 48 hours, usually < 2 hours).

Check DNS:

dig yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Force SSL renewal (Certbot):

sudo certbot renew --force-renewal
Enter fullscreen mode Exit fullscreen mode

Issue 7: Next.js Build Fails

Symptom: Deployment fails with TypeScript errors.

Cause: Type errors not caught in development.

Fix:

Test build locally:

cd frontend
npm run build
Enter fullscreen mode Exit fullscreen mode

Fix all TypeScript errors before deploying.

Temporary bypass (not recommended):

// next.config.js
module.exports = {
  typescript: {
    ignoreBuildErrors: true,  // DON'T use in production
  },
}
Enter fullscreen mode Exit fullscreen mode

Part L: The Complete Deployment Checklist

Use this checklist for every deployment.

Pre-Deployment Checklist

Code Preparation:

  • [ ] All tests passing locally
  • [ ] No TypeScript errors (npm run build)
  • [ ] No Django warnings (python manage.py check --deploy)
  • [ ] Environment variables documented
  • [ ] .gitignore includes .env* files
  • [ ] requirements.txt / package.json up to date

Settings Verification:

  • [ ] DEBUG = False in production
  • [ ] SECRET_KEY is strong and from environment
  • [ ] ALLOWED_HOSTS configured
  • [ ] CSRF_TRUSTED_ORIGINS configured
  • [ ] CORS_ALLOWED_ORIGINS configured
  • [ ] Database connection pooling enabled
  • [ ] Static files configuration (WhiteNoise or S3)

Security Checklist:

  • [ ] SSL redirect enabled (SECURE_SSL_REDIRECT = True)
  • [ ] Secure cookies (SESSION_COOKIE_SECURE = True)
  • [ ] HSTS enabled
  • [ ] No secrets in code (all in environment variables)
  • [ ] CORS properly configured (not CORS_ALLOW_ALL_ORIGINS = True)

Infrastructure Ready:

  • [ ] Domain registered (if using custom domain)
  • [ ] DNS records planned
  • [ ] Hosting accounts created
  • [ ] Database provisioned
  • [ ] Redis provisioned

Deployment Checklist

Backend Deployment:

  • [ ] Code deployed to server/platform
  • [ ] Environment variables set
  • [ ] Database migrations run
  • [ ] Static files collected
  • [ ] Gunicorn/service started
  • [ ] Health check endpoint responding

Frontend Deployment:

  • [ ] Code deployed to Vercel/platform
  • [ ] Environment variables set (NEXT_PUBLIC_API_URL)
  • [ ] Build successful
  • [ ] Preview URL accessible

Database Setup:

  • [ ] Migrations applied
  • [ ] Superuser created
  • [ ] Test data loaded (if needed)
  • [ ] Backups configured

Domain and SSL:

  • [ ] DNS records added
  • [ ] DNS propagated (check with dig)
  • [ ] SSL certificates provisioned
  • [ ] HTTPS enforced
  • [ ] HTTP redirects to HTTPS

Integration Testing:

  • [ ] Frontend loads without errors
  • [ ] API calls work (check DevTools Network tab)
  • [ ] No CORS errors
  • [ ] Images load from Cloudinary
  • [ ] Authentication works (if implemented)
  • [ ] Database queries succeed

Post-Deployment Checklist

Monitoring Setup:

  • [ ] Error tracking configured (Sentry)
  • [ ] Uptime monitoring configured (UptimeRobot)
  • [ ] Performance monitoring enabled
  • [ ] Log aggregation set up
  • [ ] Alerts configured (email/Slack)

Backups Verified:

  • [ ] Database backups enabled
  • [ ] Backup restoration tested
  • [ ] Backup schedule confirmed

Performance Verification:

  • [ ] Lighthouse score > 90
  • [ ] TTFB < 200ms
  • [ ] No console errors
  • [ ] API response times < 500ms
  • [ ] Images loading fast

Documentation:

  • [ ] Deployment process documented
  • [ ] Environment variables documented
  • [ ] Rollback procedure documented
  • [ ] Incident response plan created

Load Testing:

  • [ ] Test with expected traffic volume
  • [ ] Verify database connection pooling works
  • [ ] Verify Redis cache hit rate
  • [ ] No memory leaks under sustained load

User Acceptance:

  • [ ] Staging environment tested
  • [ ] Stakeholders approve
  • [ ] Users can access the site
  • [ ] All features work in production

What We Built — The Complete System

We entered Part 7 with a housing portal running on localhost. We exit with four complete deployment paths to choose from.

Path 0 (Free Tier):

  • Perfect for this tutorial
  • $0/month, ideal for demos and MVPs
  • Accept cold starts as a tradeoff
  • Easy migration path to paid tiers when needed

Path 1 (Railway):

  • Simplest paid deployment
  • $20/month for everything in one place
  • No infrastructure management
  • Great for side projects and small apps

Path 2 (Vercel + Railway):

  • Optimal for production
  • $15-35/month
  • Best performance globally
  • This is what you choose for a real product

Path 3 (AWS DIY):

  • Full control
  • $90+/month
  • For enterprise and compliance needs
  • Requires DevOps knowledge

📊 [Complete Production Request Flow]

Complete Production Request Flow


The Production Architecture (Path 2)

User in Tokyo
  ↓
Vercel Edge (Tokyo) — serves Next.js SSR, cached static files
  ↓
Railway Backend (US-East) — Django API
  ↓
├─ PostgreSQL (Railway) — persistent data
├─ Redis (Railway) — cache layer
└─ Cloudinary CDN (Tokyo edge) — images

Result: < 100ms TTFB globally
Enter fullscreen mode Exit fullscreen mode

Every layer is optimized. Every cache works together. The user experience is instant.

What's Different from Development

Development (localhost):

  • DEBUG = True (detailed errors)
  • SQLite or PostgreSQL on same machine
  • Redis on same machine
  • No SSL
  • No monitoring
  • Single server
  • Unlimited resources

Production (live site):

  • DEBUG = False (generic errors)
  • Managed PostgreSQL with backups
  • Managed Redis or ElastiCache
  • SSL enforced
  • Error tracking, uptime monitoring
  • Distributed across multiple servers/regions
  • Resource limits, cost constraints
  • Autoscaling
  • Security hardening

Beyond Deployment — What's Next

The series ends here, but your learning continues:

Part 8 (Bonus): Advanced Topics

  • Celery background tasks
  • WebSockets for real-time updates
  • Full-text search with Elasticsearch
  • Payment integration (Stripe)
  • Email notifications (SendGrid)
  • Admin customization

Part 9 (Bonus): Scaling Beyond

  • Kubernetes deployment
  • Microservices architecture
  • Multi-region deployment
  • Database sharding
  • Read replicas

But you don't need any of that yet. What you have now — this complete, optimized, deployed housing portal — is production-ready and can handle thousands of users.


Checkpoint: Push to GitHub

git add .
git commit -m "feat: add production settings, deployment configs, CI/CD pipeline"
git checkout -b part-7-deployment
git push origin part-7-deployment
Enter fullscreen mode Exit fullscreen mode

The repo now has seven branches:

  • part-1-setup — infrastructure
  • part-2-data — database and seed
  • part-3-caching — API and Redis
  • part-4-optimization — query optimization and signals
  • part-5-frontend — Next.js UI and SWR
  • part-6-images — Cloudinary and lazy loading
  • part-7-deployment — production deployment

View the live site:

  • Free tier: https://your-app.vercel.app
  • With domain: https://housing-demo.com

Acknowledgments and References

This deployment guide was informed by:

Special thanks to the Django, Next.js, and Railway communities for excellent documentation.


The series is complete. You've built a production-grade housing portal from zero to deployed. You understand caching at every layer. You know how to optimize databases. You can deploy to production. Most importantly, you understand the why behind every decision.

Now go build something amazing. 🚀


Series Complete:

Top comments (0)