Skip to content

Commit aadd14d

Browse files
committed
add npm ignore
1 parent 8289907 commit aadd14d

File tree

12 files changed

+137
-63
lines changed

12 files changed

+137
-63
lines changed

.npmignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

README.md

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
1-
# serverless-dify
1+
# sample-serverless-dify-stack
22

3-
Deploy a dify cluster using Amazon serverless stack.
3+
Deploy [dify](https://dify.ai/) cluster using Amazon serverless services with AWS CDK.
44

5+
![architecture](./resources/architecture.png)
6+
7+
### Key Features
8+
9+
1. **Simplified Container Deployment:** Leverages AWS Elastic Container Registry (ECR) as the container orchestration tool, significantly reducing the complexity and entry barrier of containerized deployment.
10+
11+
2. **Cost-Effective Scalability:** Implements AWS Serverless technology stack to ensure economic efficiency through elastic scaling, optimizing resource utilization based on actual demand.
12+
13+
### Requirements
14+
15+
1. Node.js (version 14.x or later) and TypeScript/JavaScript development environment
16+
17+
2. CDK bootstrapped in your target region, refer to [here](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) for detailed setup instructions.
518

619
### Deploy
720

21+
1. Prepare env values, copy `.env.example` to `.env` and update the env values;
22+
23+
2. Deploy the cdk stack using following command:
824
```
9-
npm install [email protected]
1025
cdk deploy --region <region-code> --all --concurrency 5 --require-approval never
1126
```
27+
3. After deploy, you can find the dify endpoint in output:
28+
```
29+
✅ ServerlessDifyStack
1230
13-
## Security
14-
15-
See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
16-
17-
## License
31+
✨ Deployment time: 435.37s
1832
19-
This library is licensed under the MIT-0 License. See the LICENSE file.
33+
Outputs:
34+
ServerlessDifyStack.DifyEndpoint = Server-Ingre-xxxxxxx-xxxxxxx.us-east-1.elb.amazonaws.com
35+
Stack ARN:
36+
arn:aws:cloudformation:us-east-1:xxxxxxxx:stack/ServerlessDifyStack/16d8e980-ed22-11ef-b28b-xxxxxxxxxx
37+
```

bin/serverless-dify.ts

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22
import * as cdk from 'aws-cdk-lib';
3+
import * as dotenv from 'dotenv';
34
import 'source-map-support/register';
45
import { CeleryBrokerStack } from '../lib/celery-broker-stack';
56
import { DifyStack } from '../lib/dify-stack';
@@ -10,25 +11,10 @@ import { NetworkStack } from '../lib/network-stack';
1011
import { RedisStack } from '../lib/redis-stack';
1112
import { VectorStoreStack } from '../lib/vector-store-stack';
1213

13-
const app = new cdk.App();
14-
15-
// new ServerlessDifyStack(app, 'ServerlessDifyStack', {
16-
// /* If you don't specify 'env', this stack will be environment-agnostic.
17-
// * Account/Region-dependent features and context lookups will not work,
18-
// * but a single synthesized template can be deployed anywhere. */
19-
20-
// /* Uncomment the next line to specialize this stack for the AWS Account
21-
// * and Region that are implied by the current CLI configuration. */
22-
// // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
23-
24-
// /* Uncomment the next line if you know exactly what Account and Region you
25-
// * want to deploy the stack to. */
26-
// // env: { account: '123456789012', region: 'us-east-1' },
27-
28-
// /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
29-
30-
// });
14+
dotenv.config()
3115

16+
// initialize cdk context
17+
const app = new cdk.App();
3218

3319
// create network environment
3420
const network = new NetworkStack(app, "ServerlessDifyNetworkStack", {})
@@ -59,12 +45,17 @@ const dify = new DifyStack(app, "ServerlessDifyStack", {
5945
redis: redis.exportProps(),
6046
celeryBroker: celeryBroker.exportProps(),
6147
smtp: {
62-
host: "email-smtp.us-east-1.amazonaws.com",
63-
port: 587,
64-
username: "xxxxxxxxxxxxxxxxxxxxx",
65-
password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
66-
tls: true,
67-
fromEmail: ""
48+
host: process.env.SMTP_SERVER_HOST || 'email-smtp.us-east-1.amazonaws.com',
49+
port: process.env.SMTP_SERVER_PORT || '578',
50+
username: process.env.SMTP_SERVER_USERNAME || 'admin',
51+
password: process.env.SMTP_SERVER_PASSWORD || 'admin',
52+
tls: process.env.SMTP_SERVER_TLS_ENABLED == 'true' ? true : false,
53+
fromEmail: process.env.SMTP_SERVER_SEND_FROM || '[email protected]'
54+
},
55+
difyVersion: {
56+
api: process.env.DIFY_VERSION_API || 'latest',
57+
web: process.env.DIFY_VERSION_WEB || 'latest',
58+
sandbox: process.env.DIFY_VERSION_SANDBOX || 'latest'
6859
}
6960
})
7061

cdk.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
4747
"@aws-cdk/aws-redshift:columnId": true,
4848
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49-
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49+
"@aws-cdk/aws-ec2:ipv4IgnoreEgressRule": true,
5050
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
5151
"@aws-cdk/aws-kms:aliasNameRef": true,
5252
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
@@ -68,4 +68,4 @@
6868
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
6969
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false
7070
}
71-
}
71+
}

lib/dify-stack.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Duration, Stack, StackProps } from "aws-cdk-lib";
1+
import { CfnOutput, Duration, Stack, StackProps } from "aws-cdk-lib";
22
import { SecurityGroup, SubnetType } from "aws-cdk-lib/aws-ec2";
33
import { Cluster, FargateService } from "aws-cdk-lib/aws-ecs";
44
import { ApplicationListener, ApplicationProtocol, ListenerCondition } from "aws-cdk-lib/aws-elasticloadbalancingv2";
@@ -7,7 +7,7 @@ import { Construct } from "constructs";
77
import { DifyApiTaskDefinitionStack } from "./task-definitions/dify-api";
88
import { DifyWebTaskDefinitionStack } from "./task-definitions/dify-web";
99
import { DifyWorkerTaskDefinitionStack } from "./task-definitions/dify-worker";
10-
import { DifyCeleryBrokerProps, DifyFileStoreProps, DifyIngressProps, DifyMetadataStoreProps, DifyNetworkProps, DifyRedisProps, DifyTaskDefinitionStackProps, DifyVectorStorePgProps, SmtpServerProps } from "./task-definitions/props";
10+
import { DifyCeleryBrokerProps, DifyFileStoreProps, DifyIngressProps, DifyMetadataStoreProps, DifyNetworkProps, DifyRedisProps, DifyTaskDefinitionStackProps, DifyVectorStorePgProps, DifyVersion, SmtpServerProps } from "./task-definitions/props";
1111

1212
export interface DifyStackProps extends StackProps {
1313

@@ -25,7 +25,9 @@ export interface DifyStackProps extends StackProps {
2525

2626
readonly vectorStore: DifyVectorStorePgProps
2727

28-
readonly smtp: SmtpServerProps
28+
readonly smtp: SmtpServerProps,
29+
30+
readonly difyVersion: DifyVersion
2931
}
3032

3133
export class DifyStack extends Stack {
@@ -50,12 +52,15 @@ export class DifyStack extends Stack {
5052
metadataStore: props.metadataStore, vectorStore: props.vectorStore,
5153
apiSecretKey: new Secret(this, 'ServerlessDifyApiSecretKey', { generateSecretString: { passwordLength: 32 } }),
5254
sandboxCodeExecutionKey: new Secret(this, 'ServerlessDifySandboxCodeExecutionKey', { generateSecretString: { passwordLength: 32 } }),
53-
stmp: props.smtp
55+
stmp: props.smtp,
56+
difyVersion: props.difyVersion
5457
}
5558

5659
this.runApiService(difyTaskDefinitionStackProps)
5760
this.runWorkService(difyTaskDefinitionStackProps)
5861
this.runWebService(difyTaskDefinitionStackProps)
62+
63+
new CfnOutput(this, "DifyEndpoint", { value: props.ingress.lb.loadBalancerDnsName })
5964
}
6065

6166

@@ -73,15 +78,16 @@ export class DifyStack extends Stack {
7378
this.listener.addTargets('DifyApiTargets', {
7479
priority: 50000,
7580
targets: [service.loadBalancerTarget({ containerName: "main" })],
76-
conditions: [ListenerCondition.pathPatterns(["/console/api/*", "/api/*", "/v1/", "/files/"])],
81+
conditions: [ListenerCondition.pathPatterns(["/console/api/*", "/api/*", "/v1/*", "/files/*"])],
7782
targetGroupName: "serverless-dify-api-tg",
7883
port: DifyApiTaskDefinitionStack.DIFY_API_PORT,
7984
protocol: ApplicationProtocol.HTTP,
8085
healthCheck: {
8186
path: DifyApiTaskDefinitionStack.HEALTHY_ENDPOINT,
82-
healthyHttpCodes: '200',
87+
healthyHttpCodes: '200-400',
8388
interval: Duration.seconds(30),
84-
timeout: Duration.seconds(5)
89+
healthyThresholdCount: 3,
90+
unhealthyThresholdCount: 10
8591
}
8692
})
8793

lib/file-store-stack.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
1+
import { Duration, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
22
import { Bucket } from "aws-cdk-lib/aws-s3";
33
import { Construct } from "constructs";
44
import { DifyFileStoreProps } from "./task-definitions/props";
@@ -10,9 +10,17 @@ export class FileStoreStack extends Stack {
1010
constructor(scope: Construct, id: string, props: StackProps) {
1111
super(scope, id, props)
1212

13-
this.bucket = new Bucket(this, 'ObjectStorage', {
14-
removalPolicy: RemovalPolicy.DESTROY
13+
14+
this.bucket = new Bucket(this, 'ServerlessDifyObjectFileStore', {
15+
removalPolicy: RemovalPolicy.DESTROY,
16+
versioned: true,
1517
});
18+
19+
this.bucket.addLifecycleRule({
20+
abortIncompleteMultipartUploadAfter: Duration.days(1),
21+
noncurrentVersionExpiration: Duration.days(7),
22+
noncurrentVersionsToRetain: 2,
23+
})
1624
}
1725

1826
public exportProps(): DifyFileStoreProps {

lib/task-definitions/dify-api.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { NestedStack, RemovalPolicy } from "aws-cdk-lib";
1+
import { Duration, NestedStack, RemovalPolicy } from "aws-cdk-lib";
22
import { AppProtocol, AwsLogDriverMode, Compatibility, ContainerImage, CpuArchitecture, LogDriver, NetworkMode, OperatingSystemFamily, Protocol, Secret, TaskDefinition } from "aws-cdk-lib/aws-ecs";
3-
import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
3+
import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
44
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
55
import { Construct } from "constructs";
66
import { DifyTaskDefinitionStackProps } from "./props";
@@ -18,13 +18,23 @@ export class DifyApiTaskDefinitionStack extends NestedStack {
1818

1919
const taskRole = new Role(this, 'ServerlessDifyClusterApiTaskRole', {
2020
assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
21-
managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess' }]
2221
})
22+
taskRole.addToPrincipalPolicy(new PolicyStatement({
23+
actions: [
24+
'bedrock:InvokeModel',
25+
'bedrock:InvokeModelWithResponseStream',
26+
'bedrock:Rerank',
27+
'bedrock:Retrieve',
28+
'bedrock:RetrieveAndGenerate',
29+
],
30+
resources: ['*']
31+
}))
32+
33+
props.fileStore.bucket.grantReadWrite(taskRole)
2334

2435
this.definition = new TaskDefinition(this, 'DifyApiTaskDefinitionStack', {
2536
family: "serverless-dify-api",
2637
taskRole: taskRole,
27-
executionRole: taskRole,
2838
compatibility: Compatibility.EC2_AND_FARGATE,
2939
networkMode: NetworkMode.AWS_VPC,
3040
runtimePlatform: {
@@ -38,7 +48,7 @@ export class DifyApiTaskDefinitionStack extends NestedStack {
3848
this.definition.addContainer('api', {
3949
containerName: "main",
4050
essential: true,
41-
image: ContainerImage.fromRegistry("langgenius/dify-api"),
51+
image: ContainerImage.fromRegistry(`langgenius/dify-api:${props.difyVersion.api}`),
4252
cpu: 512,
4353
memoryLimitMiB: 1024,
4454
portMappings: [
@@ -58,6 +68,13 @@ export class DifyApiTaskDefinitionStack extends NestedStack {
5868
logGroupName: '/ecs/serverless-dify/api'
5969
}),
6070
}),
71+
healthCheck: {
72+
command: ['CMD-SHELL', 'curl -f http://localhost:5001/health || exit 1'],
73+
interval: Duration.seconds(15),
74+
startPeriod: Duration.seconds(90),
75+
retries: 10,
76+
timeout: Duration.seconds(5)
77+
},
6178
environment: {
6279

6380
"MODE": "api",
@@ -127,7 +144,7 @@ export class DifyApiTaskDefinitionStack extends NestedStack {
127144

128145
this.definition.addContainer('sandbox', {
129146
containerName: "sandbox",
130-
image: ContainerImage.fromRegistry("langgenius/dify-sandbox"),
147+
image: ContainerImage.fromRegistry(`langgenius/dify-sandbox:${props.difyVersion.sandbox}`),
131148
portMappings: [
132149
{ containerPort: 8194, hostPort: 8194, name: "serverless-dify-sandbox-8194-tcp", appProtocol: AppProtocol.http, protocol: Protocol.TCP }
133150
],

lib/task-definitions/dify-web.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NestedStack, RemovalPolicy } from "aws-cdk-lib";
22
import { AppProtocol, AwsLogDriverMode, Compatibility, ContainerImage, CpuArchitecture, LogDriver, NetworkMode, OperatingSystemFamily, Protocol, TaskDefinition } from "aws-cdk-lib/aws-ecs";
3-
import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
3+
import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
44
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
55
import { Construct } from "constructs";
66
import { DifyTaskDefinitionStackProps } from "./props";
@@ -18,8 +18,13 @@ export class DifyWebTaskDefinitionStack extends NestedStack {
1818

1919
const taskRole = new Role(this, 'ServerlessDifyClusterWebTaskRole', {
2020
assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
21-
managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess' }]
2221
})
22+
taskRole.addToPrincipalPolicy(new PolicyStatement({
23+
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
24+
resources: ['*']
25+
}))
26+
27+
props.fileStore.bucket.grantReadWrite(taskRole)
2328

2429
this.definition = new TaskDefinition(this, 'DifyWebTaskDefinitionStack', {
2530
family: "serverless-dify-web",
@@ -38,7 +43,7 @@ export class DifyWebTaskDefinitionStack extends NestedStack {
3843
this.definition.addContainer('web', {
3944
containerName: "main",
4045
essential: true,
41-
image: ContainerImage.fromRegistry("langgenius/dify-web"),
46+
image: ContainerImage.fromRegistry(`langgenius/dify-web:${props.difyVersion.web}`),
4247
cpu: 512,
4348
memoryLimitMiB: 1024,
4449
portMappings: [{

lib/task-definitions/dify-worker.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NestedStack, RemovalPolicy } from "aws-cdk-lib";
22
import { AwsLogDriverMode, Compatibility, ContainerImage, CpuArchitecture, LogDriver, NetworkMode, OperatingSystemFamily, Secret, TaskDefinition } from "aws-cdk-lib/aws-ecs";
3-
import { Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
3+
import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
44
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
55
import { Construct } from "constructs";
66
import { DifyTaskDefinitionStackProps } from "./props";
@@ -14,9 +14,15 @@ export class DifyWorkerTaskDefinitionStack extends NestedStack {
1414

1515
const taskRole = new Role(this, 'ServerlessDifyClusterWorkerTaskRole', {
1616
assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
17-
managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess' }]
1817
})
1918

19+
taskRole.addToPrincipalPolicy(new PolicyStatement({
20+
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
21+
resources: ['*']
22+
}))
23+
24+
props.fileStore.bucket.grantReadWrite(taskRole)
25+
2026
this.definition = new TaskDefinition(this, 'DifyWorkerTaskDefinitionStack', {
2127
family: "serverless-dify-worker",
2228
taskRole: taskRole,
@@ -34,7 +40,7 @@ export class DifyWorkerTaskDefinitionStack extends NestedStack {
3440
this.definition.addContainer('worker', {
3541
containerName: "worker",
3642
essential: true,
37-
image: ContainerImage.fromRegistry('langgenius/dify-api'),
43+
image: ContainerImage.fromRegistry(`langgenius/dify-api:${props.difyVersion.api}`),
3844
command: ['worker'],
3945
cpu: 512,
4046
memoryLimitMiB: 1024,

lib/task-definitions/props.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export interface DifyCeleryBrokerProps { hostname: string, port: string }
1818

1919
export interface DifyIngressProps { lb: ApplicationLoadBalancer, listener: ApplicationListener }
2020

21-
export interface SmtpServerProps { host: string, port: number, username: string, password: string, tls: boolean, fromEmail: string }
21+
export interface SmtpServerProps { host: string, port: string, username: string, password: string, tls: boolean, fromEmail: string }
22+
23+
export interface DifyVersion { api: string, web: string, sandbox: string }
2224

2325
export interface DifyTaskDefinitionStackProps extends StackProps {
2426

@@ -40,4 +42,5 @@ export interface DifyTaskDefinitionStackProps extends StackProps {
4042

4143
stmp: SmtpServerProps
4244

45+
difyVersion: DifyVersion
4346
}

0 commit comments

Comments
 (0)