Overview
GenLayer Points is deployed on AWS using:
AWS App Runner - Backend Django application (containerized)
AWS Amplify - Frontend Svelte application
AWS RDS - PostgreSQL database
AWS Systems Manager (SSM) - Environment variable management
Architecture
Prerequisites
Install AWS CLI
# macOS
brew install awscli
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Verify installation
aws --version
Configure AWS credentials
Enter:
AWS Access Key ID
AWS Secret Access Key
Default region: us-east-1
Default output format: json
Install Docker
Required for building and pushing container images:
Database Setup
Create RDS PostgreSQL Instance
Using AWS CLI
Using AWS Console
# Create DB subnet group
aws rds create-db-subnet-group \
--db-subnet-group-name points-subnet-group \
--db-subnet-group-description "Subnet group for Points database" \
--subnet-ids subnet-xxxxxx subnet-yyyyyy
# Create security group
aws ec2 create-security-group \
--group-name points-db-sg \
--description "Security group for Points RDS" \
--vpc-id vpc-xxxxxx
# Allow PostgreSQL traffic
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxx \
--protocol tcp \
--port 5432 \
--source-group sg-xxxxxx # App Runner security group
# Create RDS instance
aws rds create-db-instance \
--db-instance-identifier points-db \
--db-instance-class db.t3.micro \
--engine postgres \
--engine-version 15.4 \
--master-username pointsadmin \
--master-user-password YourSecurePassword123! \
--allocated-storage 20 \
--db-subnet-group-name points-subnet-group \
--vpc-security-group-ids sg-xxxxxx \
--backup-retention-period 7 \
--storage-encrypted \
--publicly-accessible false
Wait for the database to become available: aws rds wait db-instance-available --db-instance-identifier points-db
Get the endpoint: aws rds describe-db-instances \
--db-instance-identifier points-db \
--query 'DBInstances[0].Endpoint.Address' \
--output text
Go to RDS in AWS Console
Click Create database
Choose Standard create
Select PostgreSQL
Choose Free tier (db.t3.micro for production)
Set DB instance identifier : points-db
Set Master username : pointsadmin
Set Master password : Create a secure password
Under Connectivity :
Don’t connect to EC2
Choose your VPC
Create new security group or select existing
Publicly accessible : No
Set Initial database name : points
Enable Automated backups (7 days retention)
Enable Encryption
Click Create database
Note the endpoint once created (e.g., points-db.xxxxxx.us-east-1.rds.amazonaws.com)
Store Database URL in Parameter Store
# Format: postgresql://username:password@endpoint:5432/database
aws ssm put-parameter \
--name "/tally/prod/database_url" \
--value "postgresql://pointsadmin:YourSecurePassword123!@points-db.xxxxxx.us-east-1.rds.amazonaws.com:5432/points" \
--type "SecureString"
VPC Connector Setup
A VPC connector allows App Runner to securely access RDS:
# Create VPC connector
aws apprunner create-vpc-connector \
--vpc-connector-name points-vpc-connector \
--subnets subnet-xxxxxx subnet-yyyyyy \
--security-groups sg-xxxxxx
# Get the ARN (needed for deployment)
aws apprunner list-vpc-connectors \
--query "VpcConnectors[?VpcConnectorName=='points-vpc-connector'].VpcConnectorArn" \
--output text
Save the VPC Connector ARN - you’ll need it for the deployment script.
Environment Variables
Store all configuration in AWS Systems Manager Parameter Store:
Production (/tally/prod/*)
Development (/tally-backend/dev/*)
# Django Configuration
aws ssm put-parameter --name "/tally/prod/secret_key" --value "$( python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')" --type "SecureString"
aws ssm put-parameter --name "/tally/prod/debug" --value "False" --type "String"
aws ssm put-parameter --name "/tally/prod/allowed_hosts" --value "your-domain.com,your-app-runner-url.us-east-1.awsapprunner.com" --type "String"
# Database (already created above)
aws ssm put-parameter --name "/tally/prod/database_url" --value "postgresql://user:pass@host:5432/db" --type "SecureString"
# CORS & CSRF
aws ssm put-parameter --name "/tally/prod/csrf_trusted_origins" --value "https://your-domain.com" --type "String"
aws ssm put-parameter --name "/tally/prod/siwe_domain" --value "your-domain.com" --type "String"
# Blockchain
aws ssm put-parameter --name "/tally/prod/validator_contract_address" --value "0x10eCB157734c8152f1d84D00040c8AA46052CB27" --type "String"
aws ssm put-parameter --name "/tally/prod/validator_rpc_url" --value "https://zksync-os-testnet-genlayer.zksync.dev" --type "String"
# reCAPTCHA (get from https://www.google.com/recaptcha/admin)
aws ssm put-parameter --name "/tally/prod/recaptcha_public_key" --value "your-site-key" --type "String"
aws ssm put-parameter --name "/tally/prod/recaptcha_private_key" --value "your-secret-key" --type "SecureString"
# GitHub OAuth (optional)
aws ssm put-parameter --name "/tally/prod/github_client_id" --value "your-client-id" --type "String"
aws ssm put-parameter --name "/tally/prod/github_client_secret" --value "your-client-secret" --type "SecureString"
aws ssm put-parameter --name "/tally/prod/github_encryption_key" --value "$( python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')" --type "SecureString"
Production parameters use /tally/prod/* prefix. Development uses /tally-backend/dev/* prefix. Make sure to use the correct prefix for your environment.
Backend Deployment
Using Deployment Script (Recommended)
The deploy-apprunner.sh script automates the entire deployment:
Navigate to backend directory
Make script executable
chmod +x deploy-apprunner.sh
Deploy with VPC connector
# Production deployment
./deploy-apprunner.sh tally-backend arn:aws:apprunner:us-east-1:ACCOUNT_ID:vpcconnector/points-vpc-connector
# Development deployment
./deploy-apprunner-dev.sh tally-backend-dev arn:aws:apprunner:us-east-1:ACCOUNT_ID:vpcconnector/points-vpc-connector
What the script does:
Builds Docker image from Dockerfile
Pushes to Amazon ECR
Creates or updates App Runner service
Configures VPC connector
Loads environment variables from SSM Parameter Store
Waits for deployment to complete
Manual Deployment
If you prefer manual control:
Build and push Docker image
# Login to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
# Create repository
aws ecr create-repository --repository-name tally-backend
# Build image
docker build -t tally-backend .
# Tag image
docker tag tally-backend:latest ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/tally-backend:latest
# Push image
docker push ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/tally-backend:latest
Create IAM roles
# Create instance role for App Runner
aws iam create-role \
--role-name AppRunnerInstanceRole \
--assume-role-policy-document file://trust-policy.json
# Attach SSM read policy
aws iam attach-role-policy \
--role-name AppRunnerInstanceRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
Create App Runner service
Create a file apprunner-config.json: {
"ServiceName" : "tally-backend" ,
"SourceConfiguration" : {
"ImageRepository" : {
"ImageIdentifier" : "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/tally-backend:latest" ,
"ImageRepositoryType" : "ECR" ,
"ImageConfiguration" : {
"Port" : "8000" ,
"RuntimeEnvironmentVariables" : {}
}
},
"AutoDeploymentsEnabled" : false
},
"InstanceConfiguration" : {
"Cpu" : "1024" ,
"Memory" : "2048" ,
"InstanceRoleArn" : "arn:aws:iam::ACCOUNT_ID:role/AppRunnerInstanceRole"
},
"HealthCheckConfiguration" : {
"Protocol" : "HTTP" ,
"Path" : "/" ,
"Interval" : 10 ,
"Timeout" : 5 ,
"HealthyThreshold" : 1 ,
"UnhealthyThreshold" : 5
},
"NetworkConfiguration" : {
"EgressConfiguration" : {
"EgressType" : "VPC" ,
"VpcConnectorArn" : "arn:aws:apprunner:us-east-1:ACCOUNT_ID:vpcconnector/points-vpc-connector"
}
}
}
Deploy: aws apprunner create-service --cli-input-json file://apprunner-config.json
Run Migrations
After first deployment, run Django migrations:
# SSH into App Runner (via AWS Console logs) or use startup script
python manage.py migrate
python manage.py createsuperuser
Or include in Dockerfile:
# Add to startup
CMD python manage.py migrate && \
gunicorn backend.wsgi:application --bind 0.0.0.0:8000
Frontend Deployment
Using AWS Amplify
Connect to GitHub
Go to AWS Amplify in AWS Console
Click New app → Host web app
Select GitHub and authorize
Choose your repository and branch (main or dev)
Configure build settings
Amplify will auto-detect amplify.yml. Ensure it contains: version : 1
applications :
- appRoot : frontend
frontend :
phases :
preBuild :
commands :
- npm ci
build :
commands :
- npm run build
artifacts :
baseDirectory : dist
files :
- '**/*'
cache :
paths :
- node_modules/**/*
Set environment variables
In Amplify console, go to Environment variables and add: VITE_API_URL=https://your-app-runner-url.us-east-1.awsapprunner.com
VITE_VALIDATOR_RPC_URL=https://zksync-os-testnet-genlayer.zksync.dev
VITE_VALIDATOR_CONTRACT_ADDRESS=0x10eCB157734c8152f1d84D00040c8AA46052CB27
VITE_EXPLORER_URL=https://explorer-asimov.genlayer.com
VITE_RECAPTCHA_SITE_KEY=your-site-key
VITE_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
Deploy
Click Save and deploy . Amplify will:
Clone your repository
Install dependencies
Build the Svelte app
Deploy to CloudFront CDN
Your app will be available at: https://main.xxxxxx.amplifyapp.com
Configure custom domain (optional)
Go to Domain management
Click Add domain
Enter your domain (e.g., points.genlayer.com)
Follow DNS configuration instructions
Wait for SSL certificate provisioning
Post-Deployment
Update CORS and CSRF
Once you have your frontend URL, update backend settings:
# Update CORS allowed origins
aws ssm put-parameter \
--name "/tally/prod/allowed_hosts" \
--value "points.genlayer.com,your-app-runner-url.us-east-1.awsapprunner.com" \
--overwrite
# Update CSRF trusted origins
aws ssm put-parameter \
--name "/tally/prod/csrf_trusted_origins" \
--value "https://points.genlayer.com" \
--overwrite
# Update SIWE domain
aws ssm put-parameter \
--name "/tally/prod/siwe_domain" \
--value "points.genlayer.com" \
--overwrite
Then redeploy the backend:
cd backend
./deploy-apprunner.sh tally-backend
Update Frontend API URL
In Amplify environment variables:
VITE_API_URL=https://your-app-runner-url.us-east-1.awsapprunner.com
Trigger a new build in Amplify console.
CI/CD Setup
Backend CI/CD (GitHub Actions)
Create .github/workflows/deploy-backend.yml:
name : Deploy Backend to App Runner
on :
push :
branches : [ main ]
paths :
- 'backend/**'
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Configure AWS credentials
uses : aws-actions/configure-aws-credentials@v2
with :
aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region : us-east-1
- name : Login to Amazon ECR
id : login-ecr
uses : aws-actions/amazon-ecr-login@v1
- name : Build and push Docker image
env :
ECR_REGISTRY : ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY : tally-backend
IMAGE_TAG : ${{ github.sha }}
run : |
cd backend
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name : Deploy to App Runner
run : |
aws apprunner update-service \
--service-arn ${{ secrets.APP_RUNNER_SERVICE_ARN }} \
--source-configuration ImageRepository={ImageIdentifier=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG}
Frontend CI/CD
Amplify automatically deploys on push to connected branch. No additional configuration needed!
Monitoring
App Runner Logs
View logs in AWS Console:
Go to App Runner → Your service
Click Logs tab
View Application logs and System logs
Or use CLI:
aws apprunner describe-service --service-arn YOUR_ARN
Amplify Logs
Go to Amplify → Your app
Click on a deployment
View build logs
CloudWatch
Both App Runner and Amplify send logs to CloudWatch:
aws logs tail /aws/apprunner/tally-backend --follow
Scaling
App Runner Auto Scaling
aws apprunner update-service \
--service-arn YOUR_ARN \
--auto-scaling-configuration-arn arn:aws:apprunner:us-east-1:ACCOUNT_ID:autoscalingconfiguration/HighConcurrency
Or create custom configuration:
aws apprunner create-auto-scaling-configuration \
--auto-scaling-configuration-name HighConcurrency \
--max-concurrency 100 \
--min-size 1 \
--max-size 10
RDS Scaling
Upgrade instance class:
aws rds modify-db-instance \
--db-instance-identifier points-db \
--db-instance-class db.t3.small \
--apply-immediately
Costs
Estimated Monthly Costs (US East 1):
App Runner : $25-50/month (1 vCPU, 2GB RAM)
RDS db.t3.micro : $15-20/month (free tier eligible for 12 months)
Amplify : $0-5/month (generous free tier)
Data Transfer : $5-10/month
Total : ~$45-85/month
AWS Free Tier covers RDS for 12 months and includes 750 hours/month of App Runner build time.
Troubleshooting
App Runner Health Checks Failing
Solution: Ensure Django returns 200 OK at root path:
# backend/api/views.py
def health_check ( request ):
return JsonResponse({ 'status' : 'healthy' })
# backend/urls.py
urlpatterns = [
path( '' , health_check),
# ...
]
Database Connection Refused
Solutions:
Verify VPC connector is attached
Check RDS security group allows traffic from App Runner subnets
Verify database URL in Parameter Store is correct
CORS Errors
Solutions:
Add Amplify URL to ALLOWED_HOSTS
Add https:// prefix to CSRF_TRUSTED_ORIGINS
Redeploy backend after changes
Next Steps
Environment Variables Complete list of configuration options
Backend Setup Local backend development
Frontend Setup Local frontend development
Architecture System architecture overview