- Python 3.11+
- Node.js 20+ (LTS) and npm
- SQLite 3.35+ (bundled with Python)
- Git
cd src
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
cp .env.example .env # then edit .env with your values
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver # starts on :8000The dev settings (core.settings.dev) use SQLite and in-memory cache by default. No external services are required.
cd pages
npm install
npm run dev # starts Vite dev server on :5173Vite proxies /api/*, /media/*, /admin/*, and /static/* to http://localhost:8000.
- Start backend:
cd src && python manage.py runserver - Start frontend:
cd pages && npm run dev - Visit
http://localhost:5173(Vite handles proxying to Django) - Admin:
http://localhost:5173/admin
| Variable | Purpose | Example |
|---|---|---|
DJANGO_SECRET_KEY |
Django cryptographic signing | Random 50+ char string |
DJANGO_ALLOWED_HOSTS |
Comma-separated allowed hostnames | api.example.com,example.com |
DB_NAME |
PostgreSQL database name | innovate_to_grow |
DB_USER |
PostgreSQL user | itg_user |
DB_PASSWORD |
PostgreSQL password | — |
DB_HOST |
PostgreSQL host | db.example.com |
DB_PORT |
PostgreSQL port | 5432 |
DJANGO_SUPERUSER_USERNAME |
Auto-created admin username | admin |
DJANGO_SUPERUSER_PASSWORD |
Auto-created admin password | — |
| Variable | Purpose | Default |
|---|---|---|
DEBUG |
Debug mode | False |
DB_ENGINE |
Database engine override | django.db.backends.postgresql |
DJANGO_SUPERUSER_EMAIL |
Admin email | "" |
RSA_KEY_PASSPHRASE |
Passphrase for RSA key encryption at rest | Blank (required in prod) |
FRONTEND_URL |
Frontend domain for admin live preview links | "" |
REDIS_URL |
Redis connection URL for caching | Falls back to FileBasedCache in-container; Redis is recommended for multi-instance deployments |
VITE_API_BASE_URL |
Backend API URL for frontend build | /api (dev proxy) |
| Variable | Purpose | Default |
|---|---|---|
AWS_STORAGE_BUCKET_NAME |
S3 bucket for static/media files | itg-static-assets |
AWS_ACCESS_KEY_ID |
IAM access key (or use instance role) | — |
AWS_SECRET_ACCESS_KEY |
IAM secret key | — |
AWS_S3_REGION_NAME |
S3 region | us-west-2 |
AWS_S3_ENDPOINT_URL |
Custom S3 endpoint (for R2 etc.) | — |
AWS_S3_CUSTOM_DOMAIN |
CDN domain for S3 | {bucket}.s3.amazonaws.com |
| Variable | Purpose | Default |
|---|---|---|
SES_AWS_ACCESS_KEY_ID |
SES IAM access key | "" |
SES_AWS_SECRET_ACCESS_KEY |
SES IAM secret key | "" |
SES_AWS_REGION |
SES region | us-west-2 |
SES_FROM_EMAIL |
SES sender email | i2g@g.ucmerced.edu |
SES_FROM_NAME |
SES sender display name | Innovate to Grow |
| Variable | Purpose |
|---|---|
GOOGLE_SHEETS_CREDENTIALS_JSON |
Service account JSON (preferred) |
GOOGLE_SHEETS_SCOPES |
OAuth scopes (default: spreadsheets.readonly) |
GOOGLE_SHEETS_API_KEY |
API key fallback (public sheets only) |
| Variable | Purpose |
|---|---|
CSRF_TRUSTED_ORIGINS |
Comma-separated trusted origins for CSRF |
CORS_ALLOWED_ORIGINS |
Comma-separated allowed CORS origins |
┌─────────────┐
Users ──────── │ CloudFront │
└──────┬──────┘
│
┌────────────┴────────────┐
│ │
┌────────▼────────┐ ┌─────────▼─────────┐
│ AWS Amplify │ │ AWS ALB │
│ (Frontend) │ │ (TLS termination) │
│ React SPA │ └─────────┬──────────┘
└─────────────────┘ │
┌────────▼────────┐
│ AWS ECS Fargate │
│ (Backend) │
│ Gunicorn + Django│
└──┬─────┬────┬───┘
│ │ │
┌────────────┘ │ └────────────┐
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ PostgreSQL │ │ Redis │ │ S3 │
│ (RDS) │ │ (ElastiCache) │ │ (static + │
│ │ │ │ │ media) │
└───────────────┘ └───────────────┘ └───────────────┘
- Docker container built from
src/Dockerfile - Image: Python 3.11-slim with gunicorn (3 workers, 120s timeout)
- Task: 256 CPU units, 512 MB memory
- Health check:
GET /health/every 30 seconds
- Static SPA built with
npm run build(TypeScript check + Vite build) - Deployed via GitHub Actions after CI passes
VITE_API_BASE_URLset at build time to point to the backend API domain
The backend Dockerfile (src/Dockerfile) is a single-stage build:
- Base image:
python:3.11-slim - System deps:
libpq-dev(PostgreSQL client library) - Python deps: installed from
src/requirements.txt - Application code: copied into
/app - Non-root user:
django:django - Entrypoint:
/app/entrypoint.sh - Default command:
gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 120
The entrypoint (src/entrypoint.sh) runs on every container start:
- Wait for database — retries PostgreSQL connection up to 30 times (2s intervals)
- Run migrations —
python manage.py migrate --noinput - Ensure superuser — creates or updates the admin user from
DJANGO_SUPERUSER_*env vars (password is always synced to support rotation) - Collect static files —
python manage.py collectstatic --noinput - Start server — executes the CMD (gunicorn)
All workflows are in .github/workflows/.
Triggers on push to main and on pull requests (when src/, pages/, aws/, or workflow files change).
| Stage | Job | What It Does |
|---|---|---|
| 1 | Code Style Check | Ruff (Python) + ESLint (TypeScript) via lint.yml |
| 2a | Django Build & Test | Install deps, migrate (SQLite), run tests, manage.py check |
| 2b | Docker Build Test | Build Docker image, export as artifact |
| 2c | Frontend Build | npm ci + npm run build |
| 3 | DB Migration Test | Load Docker image, run migrations against PostgreSQL 16 service container, verify no pending migrations |
Triggers after successful CI on main (or manual dispatch).
- Build Docker image and push to Amazon ECR (tagged with git SHA +
latest) - Render ECS task definition from
aws/task-definition.jsontemplate (substitutes env vars) - Deploy to ECS with service stability wait
- Verify deployment was not rolled back
- Smoke test: curl
/health/and verify CORS headers
Triggers after successful CI on main (or manual dispatch).
- Checkout and install Node.js 20
npm ci+npm run build(withVITE_API_BASE_URLfrom GitHub vars/secrets)- Create Amplify deployment, upload zipped dist
- Start deployment
| Service | Used For | Required In |
|---|---|---|
| PostgreSQL | Primary database | Production |
| Redis | Cache backend | Optional (falls back to LocMemCache) |
| AWS S3 | Static and media file storage | Production |
| AWS SES | Transactional email sending | Optional (alternative to Gmail) |
| Google Sheets API | Read project/event data | Optional (for sheet-powered pages) |
| Gmail API | Email sending via service account | Optional (alternative to SES) |
Configured in src/core/settings/prod.py:
SECURE_HSTS_SECONDS = 31536000(1 year) withHSTS_INCLUDE_SUBDOMAINSandHSTS_PRELOADSECURE_SSL_REDIRECT = False(ALB handles HTTP → HTTPS)SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")(trusts ALB)SESSION_COOKIE_SECURE = True,CSRF_COOKIE_SECURE = TrueX_FRAME_OPTIONS = "DENY"SECURE_SERVER_HEADER = None(hides server version)REQUIRE_ENCRYPTED_PASSWORDS = True(blocks plaintext password submission)CORS_ALLOW_CREDENTIALS = Truewith explicitCORS_ALLOWED_ORIGINS
| Command | App | Purpose |
|---|---|---|
python manage.py runserver |
— | Start dev server |
python manage.py migrate |
— | Apply database migrations |
python manage.py createsuperuser |
— | Create admin user |
python manage.py collectstatic |
— | Collect static files for production |
python manage.py resetdb --force --confirm RESET_DB |
core | Reset database (dev only) |
python manage.py seed_initial_data |
pages | Seed menu and footer data |
python manage.py cms_seed |
pages | Seed CMS pages |
python manage.py sync_news |
news | Sync news from RSS feed sources |
python manage.py sync_projects |
projects | Import projects from Google Sheets |
python manage.py test_gmail_connection |
Test Gmail API connection |