chore: deploy prod to aws

This commit is contained in:
2026-01-21 18:00:32 -08:00
parent 8bce0bd957
commit cfd2e0d2c2
4 changed files with 181 additions and 7 deletions

View File

@@ -4,5 +4,9 @@ import { GhostV2Stack } from '../lib/cdk-stack';
const app = new cdk.App();
new GhostV2Stack(app, "GhostV2Stack-dev", {
env: { region: "ca-central-1", account: process.env.AWS_ACCOUNT_ID! },
env: { region: "ca-central-1", account: process.env.AWS_ACCOUNT_ID! }, environment: "dev"
});
new GhostV2Stack(app, "GhostV2Stack-prod", {
env: { region: "ca-central-1", account: process.env.AWS_ACCOUNT_ID!, }, environment: "prod"
})

7
cdk/cdk.context.json Normal file
View File

@@ -0,0 +1,7 @@
{
"availability-zones:account=585061171043:region=ca-central-1": [
"ca-central-1a",
"ca-central-1b",
"ca-central-1d"
]
}

View File

@@ -1,20 +1,156 @@
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ssm from "aws-cdk-lib/aws-ssm"
import * as servicediscovery from "aws-cdk-lib/aws-servicediscovery";
import * as apigatewayv2 from "aws-cdk-lib/aws-apigatewayv2";
import * as path from "path";
interface StackProps extends cdk.StackProps {
environment: string
}
export class GhostV2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
const table = new dynamodb.TableV2(this, "ghostv2-table", {
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
billing: dynamodb.Billing.onDemand(),
})
});
table.addGlobalSecondaryIndex({
indexName: "gsi1",
partitionKey: { name: "gsi1pk", type: dynamodb.AttributeType.STRING },
sortKey: { name: "gsi1sk", type: dynamodb.AttributeType.STRING }
})
sortKey: { name: "gsi1sk", type: dynamodb.AttributeType.STRING },
});
const vpc = new ec2.Vpc(this, "ghostv2 vpc", {
maxAzs: 2,
natGateways: 0,
subnetConfiguration: [
{
name: "Public",
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
],
});
const ecsSecurityGroup = new ec2.SecurityGroup(this, "ghostv2 security group", {
vpc,
description: "Security group for ECS Fargate tasks",
allowAllOutbound: true,
});
ecsSecurityGroup.addIngressRule(
ec2.Peer.ipv4(vpc.vpcCidrBlock),
ec2.Port.tcp(8080),
"Allow traffic from VPC"
);
const cluster = new ecs.Cluster(this, "ghostv2 cluster", {
vpc,
});
const namespace = new servicediscovery.PrivateDnsNamespace(
this,
"ghostv2 namespace",
{
name: "ghostv2.local",
vpc,
}
);
const taskDefinition = new ecs.FargateTaskDefinition(
this,
"ghostv2 api",
{
memoryLimitMiB: 512,
cpu: 256,
runtimePlatform: {
cpuArchitecture: ecs.CpuArchitecture.X86_64,
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
},
}
);
table.grantReadWriteData(taskDefinition.taskRole);
const secretImports = ["DATABASE_URL", "GH_CLIENT_ID", "GH_CLIENT_SECRET", "REPOS_TABLE_NAME", "SENTRY_DSN"];
const secrets = secretImports.reduce<Record<string, ecs.Secret>>((acc, name) => {
acc[name] = ecs.Secret.fromSsmParameter(ssm.StringParameter.fromSecureStringParameterAttributes(this, `param${name}`, {
parameterName: `/ghostv2/${props.environment}/${name}`
}))
return acc
}, {})
taskDefinition.addContainer("api", {
image: ecs.ContainerImage.fromAsset(path.join(__dirname, "../../api")),
environment: {
RUST_LOG: "info",
},
portMappings: [
{
containerPort: 8080,
protocol: ecs.Protocol.TCP,
},
],
secrets
});
const service = new ecs.FargateService(this, "ghostv2 service", {
cluster,
taskDefinition,
desiredCount: 1,
assignPublicIp: true,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
securityGroups: [ecsSecurityGroup],
capacityProviderStrategies: [
{
capacityProvider: "FARGATE_SPOT",
weight: 1,
},
],
cloudMapOptions: {
name: "api",
cloudMapNamespace: namespace,
dnsRecordType: servicediscovery.DnsRecordType.SRV,
containerPort: 8080,
dnsTtl: cdk.Duration.seconds(10),
},
});
const vpcLink = new apigatewayv2.CfnVpcLink(this, "ghostv2 vpc link", {
name: "ghostv2-vpc-link",
subnetIds: vpc.publicSubnets.map((s) => s.subnetId),
securityGroupIds: [ecsSecurityGroup.securityGroupId],
});
const httpApi = new apigatewayv2.CfnApi(this, "ghostv2 http-api", {
name: "ghostv2-api",
protocolType: "HTTP",
});
const integration = new apigatewayv2.CfnIntegration(this, "Integration", {
apiId: httpApi.ref,
integrationType: "HTTP_PROXY",
integrationMethod: "ANY",
integrationUri: service.cloudMapService!.serviceArn,
connectionType: "VPC_LINK",
connectionId: vpcLink.ref,
payloadFormatVersion: "1.0",
});
new apigatewayv2.CfnRoute(this, "DefaultRoute", {
apiId: httpApi.ref,
routeKey: "$default",
target: `integrations/${integration.ref}`,
});
new apigatewayv2.CfnStage(this, "Stage", {
apiId: httpApi.ref,
stageName: "$default",
autoDeploy: true,
});
}
}