Skip to content

Commit b803e45

Browse files
Update
1 parent 8e9f4c7 commit b803e45

9 files changed

Lines changed: 271 additions & 106 deletions

File tree

.env.example

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
# Supabase
2-
SUPABASE_URL=http://localhost:54321
3-
SUPABASE_KEY=your-anon-key
4-
SUPABASE_DB_URL=postgresql://postgres:postgres@localhost:54322/postgres
1+
# Local development environment variables
2+
# Copy this to .env and configure for your local setup
53

6-
# Redis
4+
# Supabase (local instance via `supabase start`)
5+
SUPABASE_URL=http://127.0.0.1:54321
6+
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
7+
SUPABASE_DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
8+
9+
# Redis (local instance via docker-compose)
710
REDIS_URL=redis://localhost:6379/0
811

9-
# Celery
12+
# Celery (uses Redis above)
1013
CELERY_BROKER_URL=redis://localhost:6379/0
1114
CELERY_RESULT_BACKEND=redis://localhost:6379/1
1215

@@ -17,3 +20,6 @@ STORAGE_BUCKET=datasets
1720
API_TITLE=PolicyEngine API
1821
API_VERSION=0.1.0
1922
DEBUG=true
23+
24+
# Logfire (get token from https://logfire.pydantic.dev)
25+
LOGFIRE_TOKEN=your-logfire-token-here

.github/workflows/deploy.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,33 @@ jobs:
2323
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
2424
aws-region: ${{ vars.AWS_REGION }}
2525

26+
- name: Setup Terraform
27+
uses: hashicorp/setup-terraform@v3
28+
with:
29+
terraform_version: 1.6.0
30+
31+
- name: Terraform init
32+
working-directory: ./terraform
33+
run: terraform init
34+
35+
- name: Terraform plan
36+
working-directory: ./terraform
37+
env:
38+
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
39+
TF_VAR_supabase_key: ${{ secrets.SUPABASE_KEY }}
40+
TF_VAR_supabase_db_url: ${{ secrets.SUPABASE_DB_URL }}
41+
TF_VAR_logfire_token: ${{ secrets.LOGFIRE_TOKEN }}
42+
run: terraform plan
43+
44+
- name: Terraform apply
45+
working-directory: ./terraform
46+
env:
47+
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
48+
TF_VAR_supabase_key: ${{ secrets.SUPABASE_KEY }}
49+
TF_VAR_supabase_db_url: ${{ secrets.SUPABASE_DB_URL }}
50+
TF_VAR_logfire_token: ${{ secrets.LOGFIRE_TOKEN }}
51+
run: terraform apply -auto-approve
52+
2653
- name: Login to Amazon ECR
2754
id: login-ecr
2855
uses: aws-actions/amazon-ecr-login@v2

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ htmlcov/
4949
data/
5050
*.h5
5151
*.db
52+
.env.prod

DEPLOYMENT.md

Lines changed: 124 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,117 +3,87 @@
33
## Prerequisites
44

55
1. AWS account with appropriate permissions
6-
2. Terraform installed locally
6+
2. AWS CLI installed and configured
77
3. Supabase project set up
88
4. Redis instance (recommend Upstash for serverless Redis)
9-
5. GitHub repository with the main branch
9+
5. GitHub repository (PolicyEngine/policyengine-api-v2-alpha)
1010

11-
## Step 1: Set up AWS credentials for Terraform
11+
## Step 1: Create Terraform state bucket
1212

13-
Install and configure AWS CLI with SSO (no long-term keys):
13+
The S3 bucket stores Terraform state and enables automated deployments:
1414

1515
```bash
16-
aws configure sso
16+
make create-state-bucket
1717
```
1818

19-
Follow the prompts to authenticate via your browser.
19+
This creates `policyengine-api-v2-terraform-state` with versioning enabled.
2020

21-
## Step 2: Deploy infrastructure with Terraform
21+
**Note**: Only needs to be done once.
2222

23-
1. Create `terraform/terraform.tfvars`:
23+
## Step 2: Set up GitHub OIDC
2424

25-
```hcl
26-
aws_region = "us-east-1"
27-
project_name = "policyengine-api-v2-alpha"
28-
supabase_url = "https://your-project.supabase.co"
29-
supabase_key = "your-anon-key"
30-
supabase_db_url = "postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres"
31-
redis_url = "redis://default:[password]@your-redis.upstash.io:6379"
32-
logfire_token = "pylf_v1_us_..."
33-
storage_bucket = "datasets"
34-
api_cpu = "512"
35-
api_memory = "1024"
36-
api_desired_count = 1
37-
worker_cpu = "1024"
38-
worker_memory = "2048"
39-
worker_desired_count = 1
40-
```
41-
42-
2. Deploy:
25+
1. Get your AWS account ID:
4326

4427
```bash
45-
cd terraform
46-
terraform init
47-
terraform plan
48-
terraform apply
28+
aws sts get-caller-identity --query Account --output text
4929
```
5030

51-
3. Save the outputs:
31+
2. Create OIDC provider (if not already done):
5232

5333
```bash
54-
terraform output
34+
aws iam create-open-id-connect-provider \
35+
--url https://token.actions.githubusercontent.com \
36+
--client-id-list sts.amazonaws.com
5537
```
5638

57-
You'll get:
58-
- `ecr_repository_url` - where to push Docker images
59-
- `load_balancer_url` - your API endpoint
60-
- `ecs_cluster_name` - cluster name for GitHub Actions
61-
- `api_service_name` - API service name
62-
- `worker_service_name` - worker service name
63-
64-
## Step 3: Set up GitHub OIDC (no access keys needed)
65-
66-
1. Get your AWS account ID:
67-
68-
```bash
69-
aws sts get-caller-identity --query Account --output text
70-
```
71-
72-
2. In AWS Console, go to IAM → Identity providers → Add provider:
73-
- Provider type: OpenID Connect
74-
- Provider URL: `https://token.actions.githubusercontent.com`
75-
- Audience: `sts.amazonaws.com`
76-
- Click "Add provider"
77-
7839
3. Create IAM role for GitHub Actions:
79-
- IAM → Roles → Create role
40+
- AWS Console → IAM → Roles → Create role
8041
- Trusted entity type: Web identity
8142
- Identity provider: `token.actions.githubusercontent.com`
8243
- Audience: `sts.amazonaws.com`
83-
- GitHub organization: your-username-or-org
44+
- GitHub organization: `PolicyEngine`
8445
- GitHub repository: `policyengine-api-v2-alpha`
8546
- GitHub branch: `main`
8647
- Click Next
8748

8849
4. Attach these policies:
8950
- `AmazonECS_FullAccess`
9051
- `AmazonEC2ContainerRegistryPowerUser`
52+
- `IAMFullAccess`
53+
- `AmazonVPCFullAccess`
54+
- `CloudWatchLogsFullAccess`
55+
- `TerraformStateAccess` (custom policy created earlier)
9156

9257
5. Name the role: `GitHubActionsDeployRole`
9358

94-
6. After creation, copy the role ARN (looks like `arn:aws:iam::123456789012:role/GitHubActionsDeployRole`)
59+
6. Copy the role ARN: `arn:aws:iam::YOUR_ACCOUNT_ID:role/GitHubActionsDeployRole`
9560

96-
## Step 4: Configure GitHub secrets and variables
61+
## Step 3: Configure GitHub secrets and variables
9762

98-
Go to your GitHub repository → Settings → Secrets and variables → Actions
63+
Go to repo Settings → Secrets and variables → Actions
9964

100-
**Add this secret** (Secrets tab):
65+
**Add these secrets** (Secrets tab):
10166

10267
```
10368
AWS_ROLE_ARN=arn:aws:iam::YOUR_ACCOUNT_ID:role/GitHubActionsDeployRole
69+
SUPABASE_URL=https://your-project.supabase.co
70+
SUPABASE_KEY=your-anon-key
71+
SUPABASE_DB_URL=postgresql://postgres:[password]@db.[project].supabase.co:5432/postgres
72+
REDIS_URL=redis://default:[password]@your-redis.upstash.io:6379
73+
LOGFIRE_TOKEN=pylf_v1_us_...
10474
```
10575

10676
**Add these variables** (Variables tab):
10777

10878
```
109-
AWS_REGION=us-east-1
79+
AWS_REGION=eu-north-1
11080
ECR_REPOSITORY_NAME=policyengine-api-v2-alpha
11181
ECS_CLUSTER_NAME=policyengine-api-v2-cluster
11282
ECS_API_SERVICE_NAME=policyengine-api-v2-api
11383
ECS_WORKER_SERVICE_NAME=policyengine-api-v2-worker
11484
```
11585

116-
## Step 5: Deploy from GitHub
86+
## Step 4: Deploy
11787

11888
Push to the main branch:
11989

@@ -123,39 +93,81 @@ git push origin main
12393
```
12494

12595
GitHub Actions will automatically:
126-
1. Build the Docker image
127-
2. Push to ECR
128-
3. Update ECS services
129-
4. Wait for deployment to stabilise
96+
1. Set up Terraform
97+
2. Run `terraform init`
98+
3. Run `terraform plan`
99+
4. Run `terraform apply` (creates all infrastructure)
100+
5. Build Docker image
101+
6. Push to ECR
102+
7. Update ECS services
103+
8. Wait for deployment to stabilise
104+
105+
**First deployment**: Takes ~10 minutes as it creates VPC, load balancer, ECS cluster, etc.
106+
107+
**Subsequent deployments**: ~3-5 minutes (only updates Docker images and ECS tasks)
108+
109+
## Step 5: Verify deployment
110+
111+
After deployment completes:
112+
113+
1. **Get API endpoint**:
114+
```bash
115+
cd terraform
116+
terraform output load_balancer_url
117+
```
118+
119+
2. **Check health**:
120+
```bash
121+
curl http://YOUR-ALB-URL/health
122+
```
123+
124+
3. **View logs**:
125+
- AWS Console → CloudWatch → Log groups → `/ecs/policyengine-api-v2`
130126

131-
**Note**: After running Terraform, the ECS services will initially fail to start (no Docker image exists yet). Once you push to `main` and GitHub Actions completes, the services will automatically recover and start successfully.
127+
4. **Monitor services**:
128+
- AWS Console → ECS → Clusters → policyengine-api-v2-cluster
129+
130+
## Local deployment (optional)
131+
132+
Deploy infrastructure manually from your machine:
133+
134+
```bash
135+
make deploy-local
136+
```
137+
138+
This runs Terraform with variables from your `.env` file and prompts for confirmation before applying.
132139

133140
## Monitoring
134141

135-
- View logs: AWS Console → CloudWatch → Log groups → `/ecs/policyengine-api-v2`
136-
- View services: AWS Console → ECS → Clusters → policyengine-api-v2-cluster
137-
- API endpoint: Check Terraform output for `load_balancer_url`
142+
- **Logs**: CloudWatch → `/ecs/policyengine-api-v2`
143+
- **Services**: ECS → policyengine-api-v2-cluster
144+
- **Metrics**: CloudWatch → ECS metrics
145+
- **Logfire**: https://logfire-us.pydantic.dev/nikhilwoodruff/api-v2
138146

139147
## Updating the deployment
140148

141-
Any push to the `main` branch will trigger a new deployment automatically.
149+
Any push to `main` automatically:
150+
1. Updates infrastructure if Terraform files changed
151+
2. Builds new Docker image
152+
3. Deploys to ECS
142153

143-
## Cost estimates (us-east-1)
154+
## Cost estimates (eu-north-1)
144155

145-
- VPC/networking: Free (within free tier)
146-
- Application Load Balancer: ~$16/month
147-
- ECS Fargate API (0.5 vCPU, 1GB): ~$15/month
148-
- ECS Fargate Worker (1 vCPU, 2GB): ~$30/month
149-
- CloudWatch Logs: ~$1/month
156+
- VPC/networking: Free
157+
- Application Load Balancer: ~€14/month
158+
- ECS Fargate API (0.5 vCPU, 1GB): ~€13/month
159+
- ECS Fargate Worker (1 vCPU, 2GB): ~€26/month
160+
- CloudWatch Logs: ~€1/month
161+
- S3 (Terraform state): ~€0.10/month
150162
- Data transfer: Variable
151163

152-
**Total: ~$62/month** (plus data transfer and Redis costs)
164+
**Total: ~€54/month** (plus data transfer and Redis costs)
153165

154166
## Troubleshooting
155167

156168
### GitHub Actions can't assume role
157169

158-
Check the trust policy on your IAM role includes:
170+
Trust policy must match your repo exactly:
159171
```json
160172
{
161173
"Version": "2012-10-17",
@@ -168,28 +180,53 @@ Check the trust policy on your IAM role includes:
168180
"Action": "sts:AssumeRoleWithWebIdentity",
169181
"Condition": {
170182
"StringEquals": {
171-
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
172-
"token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/policyengine-api-v2-alpha:ref:refs/heads/main"
183+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
184+
},
185+
"StringLike": {
186+
"token.actions.githubusercontent.com:sub": "repo:PolicyEngine/policyengine-api-v2-alpha:*"
173187
}
174188
}
175189
}
176190
]
177191
}
178192
```
179193

194+
**Note**: `PolicyEngine` is case-sensitive (capital P and E)
195+
196+
### Terraform state locking errors
197+
198+
If deployment fails midway, you might see "state is locked". Wait 2 minutes for the lock to expire, or manually unlock:
199+
200+
```bash
201+
# DON'T run this unless you're sure no other process is running Terraform
202+
aws s3 rm s3://policyengine-api-v2-terraform-state/.terraform.tflock
203+
```
204+
180205
### ECS tasks not starting
181206

182207
Check CloudWatch logs for errors. Common issues:
183-
- Environment variables not set correctly
208+
- Environment variables not set in GitHub secrets
184209
- Supabase/Redis connection issues
185210
- Image build failures
186211

187-
### Deployment timeout
212+
### High costs
213+
214+
Reduce task resources in `terraform/variables.tf`:
215+
```hcl
216+
api_cpu = "256" # Lower from 512
217+
api_memory = "512" # Lower from 1024
218+
worker_desired_count = 0 # Disable worker when not needed
219+
```
188220

189-
Increase health check grace period in task definition if app takes long to start.
221+
Then push to trigger redeployment.
190222

191-
### High costs
223+
## Destroying infrastructure
224+
225+
To tear everything down:
226+
227+
```bash
228+
cd terraform
229+
./deploy.sh destroy
230+
```
192231

193-
- Reduce task CPU/memory in `terraform.tfvars`
194-
- Set `api_desired_count = 0` and `worker_desired_count = 0` when not in use
195-
- Use AWS Cost Explorer to identify expensive resources
232+
**Warning**: This deletes all resources and data. The Terraform state bucket is preserved for recovery.

0 commit comments

Comments
 (0)