Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*/node_modules
webpack-stats.dev.json
webpack-stats.prod.json
.venv
.git
.pytest_cache
.ruff_cache
10 changes: 0 additions & 10 deletions .env-example

This file was deleted.

36 changes: 36 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Django settings module
DJANGO_SETTINGS_MODULE=config.settings.development
# For production on EC2, start from `.env.production.example` instead.

# Secret key (only for local development — generate a new one for production)
SECRET_KEY=no7bxov1^uh=ksbp-xyw=#%4pn@01naitpdfj=-3*kao-3w93a

# Database — matches docker-compose.yml defaults
DATABASE_URL=postgres://tigerpath:tigerpath@localhost:5432/tigerpath
# Redis cache URL (set this when running `make deps-up`)
# REDIS_URL=redis://localhost:6379/1
# Optional Redis memory caps for Docker Redis
# REDIS_MAXMEMORY=256mb
# REDIS_MAXMEMORY_POLICY=allkeys-lru
# Local-memory cache safeguards (used when REDIS_URL is not set)
# CACHE_MAX_ENTRIES=2000
# CACHE_CULL_FREQUENCY=3

# Allowed hosts
ALLOWED_HOSTS=0.0.0.0,127.0.0.1,localhost

# Vite dev mode (set to False in production)
DJANGO_VITE_DEV_MODE=True

# Disable output buffering for easier debugging
PYTHONUNBUFFERED=1

# Princeton MobileApp API (required for course scraping / make seed-courses)
# CONSUMER_KEY=<consumer_key>
# CONSUMER_SECRET=<consumer_secret>
# Optional override if Princeton changes API path/version again
# MOBILEAPP_BASE_URL=https://api.princeton.edu:443/mobile-app/1.0.6

# TigerBook API (optional — used for onboarding auto-fill)
# TIGERBOOK_USERNAME=<netid>
# TIGERBOOK_API_KEY=<api_key>
20 changes: 20 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Django production settings
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_VITE_DEV_MODE=False
SECRET_KEY=replace-with-a-strong-random-secret
ALLOWED_HOSTS=tigerpath.io,www.tigerpath.io
CSRF_TRUSTED_ORIGINS=https://tigerpath.io,https://www.tigerpath.io

# External PostgreSQL (RDS) URL
# Example:
# DATABASE_URL=postgres://username:password@your-rds-endpoint:5432/tigerpath
DATABASE_URL=

# Local Redis (Docker service in docker-compose.prod.yml)
REDIS_URL=redis://redis:6379/1
REDIS_MAXMEMORY=128mb
REDIS_MAXMEMORY_POLICY=allkeys-lru

# Optional runtime settings
PYTHONUNBUFFERED=1
CACHE_DEFAULT_TIMEOUT=300
73 changes: 73 additions & 0 deletions .github/workflows/cd-ec2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CD (EC2)

on:
workflow_run:
workflows: ["CI"]
types:
- completed
branches:
- main
workflow_dispatch:

concurrency:
group: deploy-production-ec2
cancel-in-progress: false

jobs:
deploy:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
environment: production

steps:
- name: Validate required secrets
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_USER: ${{ secrets.EC2_USER }}
EC2_SSH_PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
EC2_DEPLOY_PATH: ${{ secrets.EC2_DEPLOY_PATH }}
run: |
set -euo pipefail
for name in EC2_HOST EC2_USER EC2_SSH_PRIVATE_KEY EC2_DEPLOY_PATH; do
if [ -z "${!name}" ]; then
echo "Missing required secret: $name"
exit 1
fi
done

- name: Configure SSH
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_PORT: ${{ secrets.EC2_PORT }}
EC2_SSH_PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
run: |
set -euo pipefail
PORT="${EC2_PORT:-22}"
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
printf "%s\n" "$EC2_SSH_PRIVATE_KEY" > "$HOME/.ssh/id_deploy"
chmod 600 "$HOME/.ssh/id_deploy"
ssh-keyscan -p "$PORT" -H "$EC2_HOST" >> "$HOME/.ssh/known_hosts"
chmod 644 "$HOME/.ssh/known_hosts"

- name: Deploy to EC2
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_PORT: ${{ secrets.EC2_PORT }}
EC2_USER: ${{ secrets.EC2_USER }}
EC2_DEPLOY_PATH: ${{ secrets.EC2_DEPLOY_PATH }}
run: |
set -euo pipefail
PORT="${EC2_PORT:-22}"
ssh -i "$HOME/.ssh/id_deploy" -p "$PORT" -o StrictHostKeyChecking=yes "${EC2_USER}@${EC2_HOST}" "bash -s" <<EOF
set -euo pipefail
cd "${EC2_DEPLOY_PATH}"

git fetch --prune origin
git checkout main
git pull --ff-only origin main

docker compose -f docker-compose.prod.yml up -d --build redis web
docker compose -f docker-compose.prod.yml exec -T web python manage.py migrate --noinput
docker compose -f docker-compose.prod.yml ps
EOF
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI

on:
pull_request:
push:
branches:
- main

jobs:
test-and-build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: tigerpath
POSTGRES_USER: tigerpath
POSTGRES_PASSWORD: tigerpath
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U tigerpath"
--health-interval=5s
--health-timeout=5s
--health-retries=10

env:
DATABASE_URL: postgres://tigerpath:tigerpath@localhost:5432/tigerpath
DJANGO_SETTINGS_MODULE: config.settings.development
DJANGO_VITE_DEV_MODE: "False"
PYTHONUNBUFFERED: "1"

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run Django migrations
run: python manage.py migrate --noinput

- name: Django system checks
run: python manage.py check

- name: Run Python tests
run: pytest

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.8"

- name: Install frontend dependencies
run: cd frontend && bun install --frozen-lockfile

- name: Build frontend
run: cd frontend && bun run build
171 changes: 171 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Deployment (EC2 + Nginx + Docker + RDS)

This guide deploys TigerPath with:
- Django/Gunicorn in Docker (`web`)
- Redis in Docker on the EC2 host (`redis`)
- PostgreSQL on external/shared RDS via `DATABASE_URL`
- Nginx on EC2 as reverse proxy + TLS termination

## 1. Provision EC2

- Ubuntu 22.04/24.04 recommended
- Security Group:
- `22/tcp` from your IP (or secure admin source)
- `80/tcp` from `0.0.0.0/0`
- `443/tcp` from `0.0.0.0/0`
- Ensure EC2 can reach your RDS endpoint on `5432`

## 2. Install Docker + Compose Plugin + Nginx

```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg nginx

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git

sudo usermod -aG docker $USER
newgrp docker
```

## 3. Clone App on EC2

Use a stable path (used by CD workflow later):

```bash
mkdir -p ~/apps
cd ~/apps
git clone https://github.com/TigerAppsOrg/TigerPath.git tigerpath
cd tigerpath
```

## 4. Create Production Env File

```bash
cp .env.production.example .env
```

Edit `.env` with real values:
- `SECRET_KEY`
- `ALLOWED_HOSTS`
- `CSRF_TRUSTED_ORIGINS`
- `DATABASE_URL` (RDS URL)
- optional Redis/cache tuning variables

Notes:
- `DATABASE_URL` must point to your shared RDS instance.
- Redis is local containerized service (`redis://redis:6379/1`) from `docker-compose.prod.yml`.

## 5. Start Production Containers

```bash
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml exec -T web python manage.py migrate
docker compose -f docker-compose.prod.yml ps
```

App listens on `127.0.0.1:8000` on host (intentionally not public).

## 6. Configure Nginx Reverse Proxy

Create `/etc/nginx/sites-available/tigerpath`:

```nginx
server {
listen 80;
server_name your-domain.com www.your-domain.com;

client_max_body_size 20m;

location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
}
}
```

Enable site:

```bash
sudo ln -s /etc/nginx/sites-available/tigerpath /etc/nginx/sites-enabled/tigerpath
sudo nginx -t
sudo systemctl reload nginx
```

## 7. Enable HTTPS (Let’s Encrypt)

```bash
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
```

Test renewal:

```bash
sudo certbot renew --dry-run
```

## 8. Operational Commands

```bash
# update app code
git pull --ff-only origin main

# rebuild/restart app
docker compose -f docker-compose.prod.yml up -d --build

# run migrations
docker compose -f docker-compose.prod.yml exec -T web python manage.py migrate

# logs
docker compose -f docker-compose.prod.yml logs -f web
docker compose -f docker-compose.prod.yml logs -f redis
```

## 9. GitHub Actions CI/CD

Two workflows are included:
- `.github/workflows/ci.yml` (test/build on PRs + pushes)
- `.github/workflows/cd-ec2.yml` (deploy to EC2 after CI succeeds on `main`, or manual run)

### Required GitHub Repository Secrets

- `EC2_HOST` (public DNS or IP)
- `EC2_USER` (SSH user, e.g. `ubuntu`)
- `EC2_SSH_PRIVATE_KEY` (private key matching the instance authorized key)
- `EC2_DEPLOY_PATH` (absolute path to repo on EC2, e.g. `/home/ubuntu/apps/tigerpath`)

Optional:
- `EC2_PORT` (defaults to `22`)

### Required EC2-side setup for CD

- Repo already cloned at `EC2_DEPLOY_PATH`
- `.env` already created/configured on EC2
- User can run Docker (`docker` group)

CD workflow executes on EC2:
1. `git fetch/pull` latest `main`
2. `docker compose -f docker-compose.prod.yml up -d --build redis web`
3. `python manage.py migrate` inside `web`
4. basic health output via `docker compose ps`

## 10. Architecture Recap

- Postgres: external RDS (`DATABASE_URL` in `.env`)
- Redis: local Docker container on EC2 (`redis` service)
- Django app: Docker `web`, proxied by Nginx
Loading