mirror of
https://github.com/karpathy/nanochat.git
synced 2026-05-07 08:19:52 +00:00
feat(deploy): add dual-mode deploy switch (EC2 monolith + EKS)
- deploy.sh: single script to switch between EC2 and EKS modes - ec2: docker-compose with ECR images + nginx SSL reverse proxy - eks: terraform apply + helm install (for demos/grading) - eks-down: terraform destroy (stop costs) - docker-compose.prod.yml: ECR image overrides + nginx service - nginx/nginx.conf: reverse proxy with SSL, SSE streaming support - deploy-ec2.yml: auto-deploy to EC2 after images are built - Remove old single-server deploy.yml Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9095cf01a8
commit
b766dcf703
70
.github/workflows/deploy-ec2.yml
vendored
Normal file
70
.github/workflows/deploy-ec2.yml
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
name: Deploy to EC2 (Monolith)
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger from GitHub UI
|
||||
workflow_run: # Auto-trigger after images are built
|
||||
workflows: ["Build & Push Dev Images"]
|
||||
types: [completed]
|
||||
branches: [master, main]
|
||||
|
||||
concurrency:
|
||||
group: deploy-ec2
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
||||
aws-region: ${{ vars.AWS_REGION || 'us-west-2' }}
|
||||
|
||||
- name: Get ECR login password
|
||||
id: ecr
|
||||
run: |
|
||||
echo "password=$(aws ecr get-login-password --region ${{ vars.AWS_REGION || 'us-west-2' }})" >> $GITHUB_OUTPUT
|
||||
echo "registry=${{ secrets.AWS_ACCOUNT_ID || '883107058766' }}.dkr.ecr.${{ vars.AWS_REGION || 'us-west-2' }}.amazonaws.com" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Deploy to EC2
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.EC2_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.EC2_SSH_KEY }}
|
||||
script: |
|
||||
set -e
|
||||
cd /home/ubuntu
|
||||
|
||||
# Login to ECR
|
||||
echo "${{ steps.ecr.outputs.password }}" | \
|
||||
docker login --username AWS --password-stdin ${{ steps.ecr.outputs.registry }}
|
||||
|
||||
# Clone or update repo
|
||||
if [ -d samosachaat ]; then
|
||||
cd samosachaat
|
||||
git fetch origin master
|
||||
git reset --hard origin/master
|
||||
else
|
||||
git clone https://github.com/manmohan659/nanochat.git samosachaat
|
||||
cd samosachaat
|
||||
fi
|
||||
|
||||
# Set image source
|
||||
export ECR_REGISTRY=${{ steps.ecr.outputs.registry }}
|
||||
export IMAGE_TAG=dev-latest
|
||||
|
||||
# Pull and deploy
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Run migrations (wait for postgres)
|
||||
sleep 8
|
||||
docker compose exec -T chat-api alembic upgrade head 2>/dev/null || true
|
||||
|
||||
echo "Deploy complete!"
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
||||
29
.github/workflows/deploy.yml
vendored
29
.github/workflows/deploy.yml
vendored
|
|
@ -1,29 +0,0 @@
|
|||
name: Deploy samosaChaat to EC2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'nanochat/**'
|
||||
- 'scripts/chat_web.py'
|
||||
- 'scripts/chat_cli.py'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to EC2
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.EC2_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.EC2_SSH_KEY }}
|
||||
script: |
|
||||
cd /home/ubuntu/nanochat
|
||||
git fetch origin master
|
||||
git reset --hard origin/master
|
||||
sudo systemctl restart samosachaat.service
|
||||
echo "Deploy complete - samosaChaat restarted"
|
||||
264
deploy.sh
Executable file
264
deploy.sh
Executable file
|
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
###############################################################################
|
||||
# samosaChaat Deploy Switch
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy.sh ec2 Deploy monolith to EC2 via docker-compose
|
||||
# ./deploy.sh ec2-down Stop services on EC2
|
||||
# ./deploy.sh eks Provision EKS + deploy via Helm (demo/grading)
|
||||
# ./deploy.sh eks-down Tear down EKS (save $$$)
|
||||
# ./deploy.sh status Show what's currently running
|
||||
###############################################################################
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
AWS_ACCOUNT="883107058766"
|
||||
AWS_REGION="us-west-2"
|
||||
ECR_REGISTRY="${AWS_ACCOUNT}.dkr.ecr.${AWS_REGION}.amazonaws.com"
|
||||
EC2_HOST="52.10.243.118"
|
||||
EC2_USER="ubuntu"
|
||||
EC2_KEY="$HOME/.ssh/samosachaat.pem" # adjust if your key is elsewhere
|
||||
DOMAIN="samosachaat.art"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[samosaChaat]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[samosaChaat]${NC} $1"; }
|
||||
err() { echo -e "${RED}[samosaChaat]${NC} $1" >&2; }
|
||||
|
||||
#─── EC2 MONOLITH ─────────────────────────────────────────────────────────────
|
||||
|
||||
ec2_deploy() {
|
||||
log "Deploying to EC2 monolith at ${EC2_HOST}..."
|
||||
|
||||
# 1. Login to ECR locally to get credentials
|
||||
log "Logging into ECR..."
|
||||
aws ecr get-login-password --region ${AWS_REGION} | \
|
||||
ssh -i "${EC2_KEY}" -o StrictHostKeyChecking=no ${EC2_USER}@${EC2_HOST} \
|
||||
"docker login --username AWS --password-stdin ${ECR_REGISTRY}" 2>/dev/null
|
||||
|
||||
# 2. Sync repo to EC2
|
||||
log "Syncing code to EC2..."
|
||||
ssh -i "${EC2_KEY}" ${EC2_USER}@${EC2_HOST} bash -s << 'REMOTE_SCRIPT'
|
||||
set -e
|
||||
cd /home/ubuntu
|
||||
|
||||
# Clone or update repo
|
||||
if [ -d samosachaat ]; then
|
||||
cd samosachaat
|
||||
git fetch origin master
|
||||
git reset --hard origin/master
|
||||
else
|
||||
git clone https://github.com/manmohan659/nanochat.git samosachaat
|
||||
cd samosachaat
|
||||
fi
|
||||
|
||||
# Ensure .env exists
|
||||
if [ ! -f .env ]; then
|
||||
cp .env.example .env
|
||||
echo "⚠️ Created .env from template — edit it with real values!"
|
||||
fi
|
||||
REMOTE_SCRIPT
|
||||
|
||||
# 3. Copy .env from local if it exists
|
||||
if [ -f "${SCRIPT_DIR}/.env" ]; then
|
||||
log "Syncing .env to EC2..."
|
||||
scp -i "${EC2_KEY}" "${SCRIPT_DIR}/.env" ${EC2_USER}@${EC2_HOST}:/home/ubuntu/samosachaat/.env
|
||||
fi
|
||||
|
||||
# 4. Pull images and start services
|
||||
log "Pulling images and starting services..."
|
||||
ssh -i "${EC2_KEY}" ${EC2_USER}@${EC2_HOST} bash -s << REMOTE_DEPLOY
|
||||
set -e
|
||||
cd /home/ubuntu/samosachaat
|
||||
|
||||
# Set ECR registry in environment
|
||||
export ECR_REGISTRY=${ECR_REGISTRY}
|
||||
export IMAGE_TAG=dev-latest
|
||||
|
||||
# Pull latest images
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
|
||||
# Start everything
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Run DB migrations
|
||||
echo "Running database migrations..."
|
||||
sleep 5 # wait for postgres to be ready
|
||||
docker compose exec -T chat-api alembic upgrade head 2>/dev/null || \
|
||||
echo "Migrations skipped (may need manual .env setup)"
|
||||
|
||||
echo ""
|
||||
docker compose ps
|
||||
REMOTE_DEPLOY
|
||||
|
||||
# 5. Setup SSL if not already done
|
||||
log "Checking SSL..."
|
||||
ssh -i "${EC2_KEY}" ${EC2_USER}@${EC2_HOST} bash -s << 'SSL_CHECK'
|
||||
if [ ! -d /etc/letsencrypt/live/samosachaat.art ]; then
|
||||
echo "Setting up SSL with certbot..."
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq certbot > /dev/null 2>&1
|
||||
sudo certbot certonly --standalone --non-interactive \
|
||||
--agree-tos -m manmohan659@gmail.com \
|
||||
-d samosachaat.art -d www.samosachaat.art \
|
||||
--pre-hook "docker compose -f /home/ubuntu/samosachaat/docker-compose.yml -f /home/ubuntu/samosachaat/docker-compose.prod.yml stop nginx" \
|
||||
--post-hook "docker compose -f /home/ubuntu/samosachaat/docker-compose.yml -f /home/ubuntu/samosachaat/docker-compose.prod.yml start nginx"
|
||||
else
|
||||
echo "SSL already configured."
|
||||
fi
|
||||
SSL_CHECK
|
||||
|
||||
echo ""
|
||||
log "EC2 deploy complete!"
|
||||
log " App: https://${DOMAIN}"
|
||||
log " Grafana: https://${DOMAIN}/grafana/"
|
||||
log " EC2: ${EC2_HOST}"
|
||||
}
|
||||
|
||||
ec2_down() {
|
||||
log "Stopping services on EC2..."
|
||||
ssh -i "${EC2_KEY}" ${EC2_USER}@${EC2_HOST} \
|
||||
"cd /home/ubuntu/samosachaat && docker compose -f docker-compose.yml -f docker-compose.prod.yml down"
|
||||
log "EC2 services stopped."
|
||||
}
|
||||
|
||||
#─── EKS CLUSTER ──────────────────────────────────────────────────────────────
|
||||
|
||||
eks_deploy() {
|
||||
local ENV="${1:-dev}"
|
||||
log "Provisioning EKS cluster (${ENV})... This takes ~15-20 minutes."
|
||||
|
||||
cd "${SCRIPT_DIR}/terraform/environments/${ENV}"
|
||||
|
||||
# Init & apply Terraform
|
||||
log "Running terraform init..."
|
||||
terraform init
|
||||
|
||||
log "Running terraform apply..."
|
||||
terraform apply -auto-approve
|
||||
|
||||
# Get cluster info
|
||||
local CLUSTER_NAME=$(terraform output -raw eks_cluster_name 2>/dev/null || echo "samosachaat-${ENV}")
|
||||
log "Configuring kubectl for ${CLUSTER_NAME}..."
|
||||
aws eks update-kubeconfig --name "${CLUSTER_NAME}" --region ${AWS_REGION}
|
||||
|
||||
# Install ALB Ingress Controller
|
||||
log "Installing ALB Ingress Controller..."
|
||||
helm repo add eks https://aws.github.io/eks-charts 2>/dev/null || true
|
||||
helm repo update
|
||||
helm upgrade --install aws-load-balancer-controller eks/aws-load-balancer-controller \
|
||||
-n kube-system \
|
||||
--set clusterName="${CLUSTER_NAME}" \
|
||||
--set serviceAccount.create=true \
|
||||
--set serviceAccount.name=aws-load-balancer-controller \
|
||||
--wait --timeout 5m 2>/dev/null || warn "ALB controller may need IRSA setup"
|
||||
|
||||
# Deploy observability stack
|
||||
log "Deploying observability stack..."
|
||||
helm dependency build "${SCRIPT_DIR}/helm/observability" 2>/dev/null || true
|
||||
helm upgrade --install observability "${SCRIPT_DIR}/helm/observability" \
|
||||
--namespace monitoring --create-namespace \
|
||||
--wait --timeout 10m 2>/dev/null || warn "Observability deploy needs review"
|
||||
|
||||
# Deploy samosaChaat
|
||||
local VALUES_FILE="${SCRIPT_DIR}/helm/samosachaat/values-${ENV}.yaml"
|
||||
log "Deploying samosaChaat to EKS..."
|
||||
helm upgrade --install samosachaat "${SCRIPT_DIR}/helm/samosachaat" \
|
||||
-f "${VALUES_FILE}" \
|
||||
--set global.imageRegistry="${ECR_REGISTRY}" \
|
||||
--set global.imageTag="dev-latest" \
|
||||
--set ingress.acmCertArn="$(terraform output -raw acm_certificate_arn 2>/dev/null || echo '')" \
|
||||
--namespace samosachaat --create-namespace \
|
||||
--wait --timeout 10m
|
||||
|
||||
echo ""
|
||||
log "EKS deploy complete!"
|
||||
log " Cluster: ${CLUSTER_NAME}"
|
||||
kubectl get pods -n samosachaat
|
||||
echo ""
|
||||
kubectl get ingress -n samosachaat 2>/dev/null || true
|
||||
}
|
||||
|
||||
eks_down() {
|
||||
local ENV="${1:-dev}"
|
||||
warn "Tearing down EKS cluster (${ENV})... This saves ~\$0.10/hr + node costs."
|
||||
|
||||
cd "${SCRIPT_DIR}/terraform/environments/${ENV}"
|
||||
|
||||
# Remove Helm releases first (cleans up ALB, etc.)
|
||||
local CLUSTER_NAME=$(terraform output -raw eks_cluster_name 2>/dev/null || echo "samosachaat-${ENV}")
|
||||
aws eks update-kubeconfig --name "${CLUSTER_NAME}" --region ${AWS_REGION} 2>/dev/null || true
|
||||
|
||||
log "Removing Helm releases..."
|
||||
helm uninstall samosachaat -n samosachaat 2>/dev/null || true
|
||||
helm uninstall observability -n monitoring 2>/dev/null || true
|
||||
helm uninstall aws-load-balancer-controller -n kube-system 2>/dev/null || true
|
||||
|
||||
# Destroy infrastructure
|
||||
log "Running terraform destroy..."
|
||||
terraform destroy -auto-approve
|
||||
|
||||
log "EKS cluster destroyed. Costs stopped."
|
||||
}
|
||||
|
||||
#─── STATUS ───────────────────────────────────────────────────────────────────
|
||||
|
||||
show_status() {
|
||||
echo ""
|
||||
log "=== samosaChaat Deployment Status ==="
|
||||
echo ""
|
||||
|
||||
# Check EC2
|
||||
echo "EC2 Monolith (${EC2_HOST}):"
|
||||
if ssh -i "${EC2_KEY}" -o ConnectTimeout=5 ${EC2_USER}@${EC2_HOST} \
|
||||
"docker compose -f /home/ubuntu/samosachaat/docker-compose.yml -f /home/ubuntu/samosachaat/docker-compose.prod.yml ps --format 'table {{.Name}}\t{{.Status}}'" 2>/dev/null; then
|
||||
echo ""
|
||||
else
|
||||
echo " Not running or unreachable."
|
||||
fi
|
||||
|
||||
# Check EKS
|
||||
echo "EKS Cluster:"
|
||||
if kubectl get nodes 2>/dev/null; then
|
||||
echo ""
|
||||
kubectl get pods -n samosachaat 2>/dev/null || echo " No samosachaat namespace."
|
||||
else
|
||||
echo " No EKS cluster configured."
|
||||
fi
|
||||
|
||||
# Check ECR images
|
||||
echo ""
|
||||
echo "ECR Images (latest):"
|
||||
for svc in frontend auth chat-api inference; do
|
||||
TAG=$(aws ecr describe-images --repository-name samosachaat/${svc} --region ${AWS_REGION} \
|
||||
--query 'sort_by(imageDetails,&imagePushedAt)[-1].imageTags[0]' --output text 2>/dev/null || echo "none")
|
||||
echo " samosachaat/${svc}: ${TAG}"
|
||||
done
|
||||
}
|
||||
|
||||
#─── MAIN ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
case "${1:-help}" in
|
||||
ec2) ec2_deploy ;;
|
||||
ec2-down) ec2_down ;;
|
||||
eks) eks_deploy "${2:-dev}" ;;
|
||||
eks-down) eks_down "${2:-dev}" ;;
|
||||
status) show_status ;;
|
||||
*)
|
||||
echo "samosaChaat Deploy Switch"
|
||||
echo ""
|
||||
echo "Usage: ./deploy.sh <mode>"
|
||||
echo ""
|
||||
echo "Modes:"
|
||||
echo " ec2 Deploy monolith to EC2 (cheap, always-on)"
|
||||
echo " ec2-down Stop EC2 services"
|
||||
echo " eks [env] Provision EKS + deploy (demo/grading) [dev|uat|prod]"
|
||||
echo " eks-down Tear down EKS (save \$\$\$)"
|
||||
echo " status Show what's running"
|
||||
;;
|
||||
esac
|
||||
36
docker-compose.prod.yml
Normal file
36
docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
## Production override for EC2 monolith deployment.
|
||||
## Usage: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
##
|
||||
## This pulls pre-built images from ECR instead of building locally,
|
||||
## and adds nginx as reverse proxy with SSL.
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build: !reset null
|
||||
image: ${ECR_REGISTRY:-883107058766.dkr.ecr.us-west-2.amazonaws.com}/samosachaat/frontend:${IMAGE_TAG:-dev-latest}
|
||||
|
||||
auth:
|
||||
build: !reset null
|
||||
image: ${ECR_REGISTRY:-883107058766.dkr.ecr.us-west-2.amazonaws.com}/samosachaat/auth:${IMAGE_TAG:-dev-latest}
|
||||
|
||||
chat-api:
|
||||
build: !reset null
|
||||
image: ${ECR_REGISTRY:-883107058766.dkr.ecr.us-west-2.amazonaws.com}/samosachaat/chat-api:${IMAGE_TAG:-dev-latest}
|
||||
|
||||
inference:
|
||||
build: !reset null
|
||||
image: ${ECR_REGISTRY:-883107058766.dkr.ecr.us-west-2.amazonaws.com}/samosachaat/inference:${IMAGE_TAG:-dev-latest}
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
depends_on:
|
||||
- frontend
|
||||
- auth
|
||||
- chat-api
|
||||
75
nginx/nginx.conf
Normal file
75
nginx/nginx.conf
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
|
||||
|
||||
# Redirect HTTP → HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name samosachaat.art www.samosachaat.art;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name samosachaat.art www.samosachaat.art;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/samosachaat.art/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/samosachaat.art/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 10M;
|
||||
|
||||
# Auth service
|
||||
location /api/auth/ {
|
||||
limit_req zone=api burst=10 nodelay;
|
||||
proxy_pass http://auth:8001/auth/;
|
||||
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;
|
||||
}
|
||||
|
||||
# Chat API — SSE streaming needs special handling
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
proxy_pass http://chat-api:8002/api/;
|
||||
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;
|
||||
|
||||
# SSE support
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_set_header Connection '';
|
||||
chunked_transfer_encoding off;
|
||||
}
|
||||
|
||||
# Frontend (Next.js)
|
||||
location / {
|
||||
proxy_pass http://frontend:3000;
|
||||
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;
|
||||
|
||||
# WebSocket support (Next.js HMR in dev, general)
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Grafana (optional, if monitoring is running)
|
||||
location /grafana/ {
|
||||
proxy_pass http://grafana:3000/;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user