Deploy to AWS
Deploy your Bunty application to AWS using various services like EC2, ECS, Elastic Beanstalk, or Lambda.
Deployment Options
1. EC2 (Virtual Machines)
2. ECS/Fargate (Containers)
3. Elastic Beanstalk (Platform as a Service)
4. Lambda (Serverless)
5. EKS (Kubernetes)
Option 1: AWS EC2
Setup EC2 Instance
# Launch EC2 instance (Ubuntu 22.04 LTS)
# Choose t3.small or larger
# Configure security group: Allow ports 22, 80, 443, 3000
Connect and Setup
# SSH into instance
ssh -i your-key.pem ubuntu@your-ec2-ip
# Update system
sudo apt update && sudo apt upgrade -y
# Install Bun
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
# Install Node.js (if needed)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install PM2
sudo npm install -g pm2
Deploy Application
# Clone repository
git clone https://github.com/yourusername/your-bunty-app.git
cd your-bunty-app
# Install dependencies
bun install --production
# Set environment variables
sudo nano /etc/environment
# Add: NODE_ENV=production, DATABASE_URL, etc.
# Start with PM2
pm2 start ecosystem.config.js --env production
# Save PM2 config
pm2 save
pm2 startup
Setup Nginx Reverse Proxy
# Install Nginx
sudo apt install nginx -y
# Configure
sudo nano /etc/nginx/sites-available/bunty-app
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/bunty-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
# Install SSL with Let's Encrypt
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.com
Option 2: AWS ECS (Fargate)
Create Dockerfile
FROM oven/bun:1-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
EXPOSE 3000
CMD ["bun", "run", "src/main.ts"]
Push to ECR
# Create ECR repository
aws ecr create-repository --repository-name bunty-app
# 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
# Build and tag
docker build -t bunty-app .
docker tag bunty-app:latest \
<account-id>.dkr.ecr.us-east-1.amazonaws.com/bunty-app:latest
# Push
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/bunty-app:latest
Create ECS Task Definition
{
"family": "bunty-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::<account>:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "bunty-app",
"image": "<account>.dkr.ecr.us-east-1.amazonaws.com/bunty-app:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:db-url"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/bunty-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3
}
}
]
}
Create ECS Service
# Create cluster
aws ecs create-cluster --cluster-name bunty-cluster
# Create service
aws ecs create-service \
--cluster bunty-cluster \
--service-name bunty-service \
--task-definition bunty-app \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \
--load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=bunty-app,containerPort=3000"
Application Load Balancer
# Create ALB
aws elbv2 create-load-balancer \
--name bunty-alb \
--subnets subnet-xxx subnet-yyy \
--security-groups sg-xxx
# Create target group
aws elbv2 create-target-group \
--name bunty-targets \
--protocol HTTP \
--port 3000 \
--vpc-id vpc-xxx \
--target-type ip \
--health-check-path /health
# Create listener
aws elbv2 create-listener \
--load-balancer-arn arn:aws:elasticloadbalancing:... \
--protocol HTTP \
--port 80 \
--default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...
Option 3: Elastic Beanstalk
Install EB CLI
pip install awsebcli
Initialize
# Initialize EB application
eb init -p docker bunty-app --region us-east-1
# Create environment
eb create bunty-prod \
--instance-type t3.small \
--envvars NODE_ENV=production,PORT=3000
Deploy
# Deploy
eb deploy
# Open in browser
eb open
# View logs
eb logs
# SSH into instance
eb ssh
Configuration File
Create .ebextensions/environment.config:
option_settings:
aws:elasticbeanstalk:application:environment:
NODE_ENV: production
PORT: 3000
aws:autoscaling:launchconfiguration:
InstanceType: t3.small
IamInstanceProfile: aws-elasticbeanstalk-ec2-role
aws:autoscaling:asg:
MinSize: 2
MaxSize: 6
aws:elbv2:listener:443:
Protocol: HTTPS
SSLCertificateArns: arn:aws:acm:region:account:certificate/xxx
Option 4: AWS Lambda
Create Handler
// src/lambda.ts
import { BuntyApplication } from '@bunty/common';
import { AppModule } from './app.module';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
let app: any;
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
if (!app) {
app = await BuntyApplication.create({
module: AppModule,
});
}
const response = await app.handleLambda(event);
return response;
};
Package for Lambda
# Install dependencies
bun install --production
# Create deployment package
zip -r function.zip . -x "*.git*" "node_modules/.cache/*"
# Or use esbuild for smaller bundle
bunx esbuild src/lambda.ts \
--bundle \
--platform=node \
--target=node20 \
--outfile=dist/index.js
Deploy with SAM
Create template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
BuntyFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: bunty-app
Runtime: nodejs20.x
Handler: index.handler
CodeUri: ./dist
MemorySize: 512
Timeout: 30
Environment:
Variables:
NODE_ENV: production
DATABASE_URL: !Ref DatabaseUrl
Events:
ApiEvent:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
DatabaseUrl:
Type: AWS::SSM::Parameter
Properties:
Name: /bunty/database-url
Type: String
Value: postgresql://...
Deploy:
sam build
sam deploy --guided
Database: RDS
Create RDS Instance
aws rds create-db-instance \
--db-instance-identifier bunty-db \
--db-instance-class db.t3.micro \
--engine postgres \
--master-username admin \
--master-user-password YourPassword123 \
--allocated-storage 20 \
--vpc-security-group-ids sg-xxx \
--publicly-accessible
Connection String
postgresql://admin:YourPassword123@bunty-db.xxxxx.us-east-1.rds.amazonaws.com:5432/bunty
Cache: ElastiCache
Create Redis Cluster
aws elasticache create-cache-cluster \
--cache-cluster-id bunty-cache \
--cache-node-type cache.t3.micro \
--engine redis \
--num-cache-nodes 1 \
--security-group-ids sg-xxx
Storage: S3
Create Bucket
aws s3 mb s3://bunty-uploads --region us-east-1
Use in Application
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: 'us-east-1' });
async function upload(key: string, body: Buffer) {
await s3.send(new PutObjectCommand({
Bucket: 'bunty-uploads',
Key: key,
Body: body,
}));
}
Secrets Manager
Store Secrets
aws secretsmanager create-secret \
--name bunty/production \
--secret-string '{"DATABASE_URL":"postgresql://...","JWT_SECRET":"..."}'
Access in Application
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getSecrets() {
const response = await client.send(
new GetSecretValueCommand({ SecretId: 'bunty/production' })
);
return JSON.parse(response.SecretString);
}
CloudWatch Monitoring
Enable Logging
import { CloudWatchLogsClient, PutLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs';
const logs = new CloudWatchLogsClient({ region: 'us-east-1' });
async function log(message: string) {
await logs.send(new PutLogEventsCommand({
logGroupName: '/aws/bunty-app',
logStreamName: '2024/01/01',
logEvents: [{
timestamp: Date.now(),
message,
}],
}));
}
CloudWatch Alarms
aws cloudwatch put-metric-alarm \
--alarm-name bunty-high-cpu \
--alarm-description "Alert when CPU exceeds 80%" \
--metric-name CPUUtilization \
--namespace AWS/ECS \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold
CI/CD with CodePipeline
buildspec.yml
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REPO
build:
commands:
- echo Build started on `date`
- docker build -t bunty-app .
- docker tag bunty-app:latest $ECR_REPO:$IMAGE_TAG
post_build:
commands:
- docker push $ECR_REPO:$IMAGE_TAG
- printf '[{"name":"bunty-app","imageUri":"%s"}]' $ECR_REPO:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json
Cost Optimization
- Use Spot Instances for non-critical workloads
- Enable auto-scaling
- Use S3 Intelligent-Tiering
- Set up CloudWatch billing alarms
- Use Reserved Instances for predictable workloads
Next Steps
- Set up CI/CD pipeline
- Configure monitoring
- Implement autoscaling
- Add CDN with CloudFront