├── .gitignore ├── LICENSE ├── README.md ├── docs ├── README.md ├── cli-reference.md ├── configuration.md ├── credits.md ├── deployment.md ├── development.md ├── examples │ ├── ai-streaming.md │ ├── apollo-graphql.md │ ├── astro-static.md │ ├── deno.md │ ├── express.md │ ├── hono.md │ ├── nextjs.md │ ├── react-router-v7.md │ └── react.md ├── faq.md ├── getting-started.md └── research.md ├── example-ai-streaming ├── README.md ├── serverless.containers.yml └── service │ ├── package-lock.json │ ├── package.json │ └── src │ ├── __tests__ │ └── ai.test.js │ ├── index.js │ ├── middleware │ └── errorHandler.js │ ├── public │ ├── css │ │ └── styles.css │ ├── images │ │ ├── background.png │ │ ├── favicon.png │ │ └── logo.png │ ├── index.html │ └── js │ │ └── main.js │ ├── routes │ └── ai.js │ └── utils │ └── errors.js ├── example-apollo-graphql ├── README.md ├── serverless.containers.yml └── service │ ├── Dockerfile │ ├── package-lock.json │ ├── package.json │ └── src │ └── index.js ├── example-astro-static ├── README.md ├── serverless.containers.yml └── service │ ├── .astro │ ├── content-assets.mjs │ ├── content-modules.mjs │ ├── content.d.ts │ ├── data-store.json │ ├── settings.json │ └── types.d.ts │ ├── astro.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── css │ │ └── styles.css │ └── images │ │ ├── background.png │ │ ├── favicon.png │ │ └── logo.png │ └── src │ └── pages │ ├── health.ts │ └── index.astro ├── example-deno ├── README.md ├── serverless.containers.yml └── service │ ├── Dockerfile │ ├── deno.json │ ├── deno.lock │ └── src │ ├── index.ts │ └── public │ ├── css │ └── styles.css │ └── images │ ├── background.png │ ├── favicon.png │ └── logo.png ├── example-express ├── README.md ├── serverless.containers.yml └── service │ ├── package-lock.json │ ├── package.json │ └── src │ ├── index.js │ └── public │ ├── css │ └── styles.css │ └── images │ ├── background.png │ ├── favicon.png │ └── logo.png ├── example-hono ├── README.md ├── serverless.containers.yml └── service │ ├── package-lock.json │ ├── package.json │ └── src │ ├── index.js │ └── public │ ├── css │ └── styles.css │ └── images │ ├── background.png │ ├── favicon.png │ └── logo.png ├── example-mastra-slack ├── .env-example ├── README.md ├── package-lock.json ├── package.json ├── serverless.containers.yml ├── src │ └── mastra │ │ ├── agents │ │ └── weather-agent.ts │ │ ├── index.ts │ │ ├── store.ts │ │ └── tools │ │ └── weather-tool.ts └── tsconfig.json ├── example-nextjs ├── README.md ├── serverless.containers.yml └── service │ ├── .gitignore │ ├── eslint.config.mjs │ ├── next.config.ts │ ├── package-lock.json │ ├── package.json │ ├── public │ └── images │ │ ├── background.png │ │ ├── favicon.png │ │ └── logo.png │ ├── src │ └── app │ │ ├── about │ │ └── page.tsx │ │ ├── components │ │ └── NavBar.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ └── tsconfig.json ├── example-react-router-v7 ├── README.md ├── serverless.containers.yml └── service │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── app │ ├── app.css │ ├── root.tsx │ ├── routes.ts │ ├── routes │ │ ├── about.tsx │ │ ├── health.tsx │ │ └── home.tsx │ └── welcome │ │ ├── logo-dark.svg │ │ ├── logo-light.svg │ │ └── welcome.tsx │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── react-router.config.ts │ ├── tsconfig.json │ └── vite.config.ts └── example-react ├── README.md ├── serverless.containers.yml └── service ├── dev.js ├── esbuild.config.js ├── package-lock.json ├── package.json ├── public ├── css │ └── styles.css └── images │ ├── background.png │ ├── favicon.png │ └── logo.png ├── server.js └── src ├── About.jsx ├── App.jsx ├── Home.jsx └── index.jsx /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/.vscode 3 | **/.env 4 | **/.env.* 5 | **/node_modules 6 | **/.serverless 7 | **/.next 8 | **/dist/ 9 | **/build/ 10 | **/releases/ 11 | **/release-builds/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2025] [Serverless, Inc.] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Container Framework - Documentation 3 | short_title: Serverless Container Framework 4 | description: >- 5 | Develop and deploy containers seamlessly across AWS Lambda and AWS ECS 6 | Fargate. Develop containerized applications with hot reloading, local 7 | emulation, and automated infrastructure setup. The perfect bridge between 8 | serverless and containers. 9 | keywords: 10 | - Serverless Container Framework 11 | - AWS Fargate 12 | - AWS Lambda 13 | - AWS ECS 14 | - Container Deployment 15 | - Serverless Containers 16 | - Docker Development 17 | - Application Load Balancer 18 | - Container Orchestration 19 | - Serverless Architecture 20 | - DevOps Automation 21 | - Infrastructure as Code 22 | - Cloud Native Development 23 | - Microservices Architecture 24 | - Local Container Development 25 | - Hot Reloading 26 | --- 27 | 28 | ![Serverless Container Framework](https://assets.serverless-extras.com/website/general/serverless-container-framework-docs-header.png) 29 | 30 | # Serverless Container Framework 31 | 32 | **One solution to deploy serverless workloads everywhere** - Serverless Container Framework (SCF) is a unified development and deployment experience for containers on serverless platforms. 33 | 34 | The value of SCF lies in its Heroku-like experience for deploying containers on serverless platforms, without vendor lock-in. Deploy to any container-supporting serverless platform. While PaaS providers charge hefty markups based on compute usage, SCF charges only a fixed cost per each container you deploy. Even better, SCF's license is free for every developer/organization making less than $2 million in revenue per year. 35 | 36 | In this initial release, SCF focuses on delivering an API architecture that leverages AWS Application Load Balancer for request routing, allowing developers to freely mix and transition between AWS Lambda and AWS ECS Fargate compute options, accompanied by a rich development experience. 37 | 38 | * [Examples](https://github.com/serverless/containers) 39 | * [Overview Video (90 seconds)](https://youtu.be/KXNYemGzda4) 40 | * [Feedback Form](https://form.typeform.com/to/iqaERaLP) 41 | 42 | ::youtube{id="KXNYemGzda4"} 43 | 44 | ## Features 45 | 46 | ### Unified Container Development & Deployment 47 | - Deploy seamlessly to AWS Lambda and ECS Fargate via a single workflow (and more providers soon) 48 | - Mix Lambda and Fargate compute within a single API 49 | - Switch compute platforms instantly without code rewrites or downtime 50 | - Optimize container builds automatically for each compute service 51 | - Write Node.js and Python apps naturally - No Dockerfiles needed 52 | - Get production-ready infrastructure in seconds with automated VPC, networking & ALB setup 53 | 54 | ### Rich Development Experience 55 | - Develop Lambda and Fargate containers rapidly with true local emulation 56 | - Route and simulate AWS ALB requests via localhost 57 | - Accelerate development with instant hot reloading 58 | - Inject live AWS IAM roles into your containers 59 | - Enjoy an elegant logging and debugging experience 60 | 61 | ### Production-Ready Features 62 | - Smart code/config change detection for deployments 63 | - Supports one or multiple custom domains on the same API 64 | - Automatic SSL certificate management 65 | - Secure AWS IAM and network defaults 66 | - Load environment variables from .env, AWS Secrets Manager, AWS Systems Manager Parameter Store, HashiCorp Vault, HashiCorp Terraform state, and more via [Serverless Framework Variables](https://www.serverless.com/framework/docs/guides/variables) 67 | - Multi-cloud support coming soon 68 | 69 | # Configuration 70 | 71 | Serverless Container Framework offers simple YAML to deliver complex architectures via a `serverless.containers.yml` file. Here is a simple example of a full-stack application. 72 | 73 | ```yaml 74 | name: acmeinc 75 | 76 | deployment: 77 | type: awsApi@1.0 78 | 79 | containers: 80 | # Web (Frontend) 81 | service-web: 82 | src: ./web 83 | routing: 84 | domain: acmeinc.com 85 | pathPattern: /* 86 | compute: 87 | type: awsLambda 88 | # API (Backend) 89 | service-api: 90 | src: ./api 91 | routing: 92 | domain: api.acmeinc.com 93 | pathPattern: /api/* 94 | pathHealthCheck: /health 95 | compute: 96 | type: awsFargateEcs 97 | awsFargateEcs: 98 | memory: 4096 99 | cpu: 1024 100 | environment: 101 | HELLO: world 102 | awsIam: 103 | customPolicy: 104 | Version: "2012-10-17" 105 | Statement: 106 | - Effect: Allow 107 | Action: 108 | - dynamodb:GetItem 109 | Resource: 110 | - "*" 111 | ``` 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/cli-reference.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Container Framework - CLI Reference 3 | short_title: CLI Reference 4 | description: >- 5 | Comprehensive guide to Serverless Container Framework CLI commands. Learn how to deploy, 6 | develop, and manage containerized applications using SCF's powerful command line interface 7 | with detailed examples and options. 8 | keywords: 9 | - Serverless Container Framework 10 | - Serverless Container Framework CLI 11 | - Container Deployment Commands 12 | - Serverless Container Development Commands 13 | - AWS Container Management 14 | - AWS Lambda 15 | - AWS ECS 16 | - AWS Fargate 17 | - Serverless Development Tools 18 | - Container CLI Tools 19 | - DevOps Commands 20 | - Infrastructure Management 21 | - AWS Deployment Tools 22 | - Container Orchestration CLI 23 | - Serverless Framework Commands 24 | - Development Workflow 25 | - Container Management Tools 26 | --- 27 | 28 | # CLI Reference 29 | 30 | The Serverless Container Framework (SCF) provides several CLI commands to manage your containerized applications. Here's a comprehensive guide to all available commands. 31 | 32 | ## Commands 33 | 34 | ### `deploy` 35 | 36 | Deploy your containers and foundational infrastructure: 37 | 38 | ```bash 39 | serverless deploy 40 | ``` 41 | 42 | #### `--force` 43 | 44 | Disregard config/code change detection and deploy all containers and infrastructure. By default, SCF uses intelligent change detection to perform incremental deployments. 45 | 46 | ### `dev` 47 | 48 | Start a local development environment with hot-reloading and local AWS emulation: 49 | 50 | ```bash 51 | serverless dev 52 | ``` 53 | 54 | ### `info` 55 | 56 | Display information about your deployed container services: 57 | 58 | ```bash 59 | serverless info 60 | ``` 61 | 62 | ### `remove` 63 | 64 | Remove deployed container services: 65 | 66 | ```bash 67 | serverless remove 68 | ``` 69 | 70 | #### `--all` 71 | 72 | Remove all resources including shared infrastructure (e.g. VPC, ALB, ECS Cluster, etc.). Be careful when using this if some resources were created by other projects. 73 | 74 | #### `--force` 75 | 76 | Force removal of all resources including shared infrastructure without confirmation. Useful for CI/CD pipelines. 77 | 78 | ## Global Options 79 | 80 | These options can be used with any command: 81 | 82 | ### `--stage` 83 | 84 | Specify the stage to target (e.g., dev, staging, prod). Defaults to `dev` if not specified. 85 | 86 | ### `--debug` 87 | 88 | Enable detailed debug logging. Useful for troubleshooting issues. 89 | 90 | ### `--aws-profile` 91 | 92 | Specify which AWS credentials profile to use. This overrides the default AWS profile. 93 | -------------------------------------------------------------------------------- /docs/credits.md: -------------------------------------------------------------------------------- 1 | Serverless Container Framework was built by Tomasz Czubocha, Max Marze, Austen Collins, and the rest of the team at Serverless Inc. -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Container Framework - Development Guide 3 | short_title: Development 4 | description: >- 5 | Master local development with Serverless Container Framework. Learn about 6 | emulating AWS Lambda and ECS Fargate locally with hot-reloading, AWS IAM role 7 | testing, AWS Application Load Balancer (ALB) routing, and more. 8 | keywords: 9 | - Serverless Container Framework 10 | - Serverless Container Framework Development 11 | - Serverless Container Framework Local Development 12 | - Serverless Container Framework Hot Reloading 13 | - Serverless Container Framework Container Development 14 | - Serverless Container Framework AWS IAM Testing 15 | - Serverless Container Framework Development Mode 16 | - Serverless Container Framework Node.js Development 17 | - Python Development 18 | - Docker Development 19 | - Development Tools 20 | - Local Testing 21 | - Container Testing 22 | - DevOps Tools 23 | - Development Workflow 24 | - Local Containers 25 | - Development Environment 26 | --- 27 | 28 | # Development 29 | 30 | When developing applications with the Serverless Container Framework (SCF), you can take advantage of the Dev Mode feature which provides a local development environment that closely mirrors your production setup. It provides the following benefits: 31 | 32 | - Develop AWS Lambda and AWS Fargate containers rapidly with true local emulation 33 | - Route and simulate AWS ALB requests via `localhost` 34 | - Accelerate development with instant hot reloading 35 | - Inject live AWS IAM roles into your containers 36 | - Enjoy an elegant logging and debugging experience 37 | 38 | ## Command 39 | 40 | To start Dev Mode for your SCF project: 41 | 42 | ```bash 43 | serverless dev 44 | ``` 45 | 46 | ### `--stage` 47 | 48 | By default, the project will deploy all containers in the `dev` stage. You can target a different stage by using the `--stage` flag: `serverless dev --stage prod`. 49 | 50 | ### `--debug` 51 | 52 | This flag will enable debug logging. If you encounter an issue, please enable debug logging and provide the logs to the Serverless team. 53 | 54 | ### `--port-proxy` 55 | 56 | This option will set up the local proxy that emulates the AWS Application Load Balancer (ALB) routing to use a specific port. Otherwise, the default port is `3000`. 57 | 58 | ### `--port-control` 59 | 60 | This option will set up the control plan that runs your containers locally to use a specific port. Otherwise, the default port is `3001`. 61 | 62 | ## Container Hot-Reloading 63 | 64 | Dev Mode supports hot-reloading for: 65 | 66 | - Node.js applications 67 | - Python applications 68 | 69 | When you make code changes, the containers automatically rebuild and restart while maintaining your application state. 70 | 71 | ### Node.js Hot-Reloading 72 | 73 | For Node.js applications, Hot Module Reloading (HMR) is enabled by default. Your application will automatically restart when files change. 74 | 75 | By default, SCF uses a file-watching tool (chokidar) to monitor changes in the source code. 76 | 77 | However, the container’s entrypoint script first checks for a "dev" command in the project's package.json and, when found, runs it to start the development process instead of the default HMR. If you want to customize your HMR or you are working with a framework that offers its own HMR, you can do so by adding a "dev" command to your project's package.json. 78 | 79 | ### Python Hot-Reloading 80 | 81 | Python applications use watchdog to monitor file changes and trigger rebuilds automatically. 82 | 83 | ### Custom Dockerfiles 84 | 85 | When using custom Dockerfiles, Dev Mode will still watch for file changes and trigger rebuilds. 86 | 87 | ## AWS IAM Role Testing 88 | 89 | When developing locally, SCF enables testing with live AWS IAM roles, if they are configured for a container in `containers` and the architecture has been deployed to AWS in that stage. 90 | 91 | For clarity, if you have deployed into a `dev` stage, and you run Dev Mode locally within that `dev` stage, the AWS IAM Roles for your project within that stage will be located in the live AWS account, temporary credentials for them will be created, and those credentials will be injected into your containers, allowing you to test exact IAM permissions that will be used in production. 92 | 93 | If you have not deployed your stage to AWS, no AWS IAM Roles will be available and therefore no temporary credentials will be created. 94 | 95 | This provides several key benefits: 96 | 97 | - Test exact IAM permissions that will be used in production 98 | - Catch permission issues before deployment 99 | - Use and validate AWS service integrations locally 100 | 101 | For example, if your container needs to access AWS S3 buckets or AWS DynamoDB tables, you can test these permissions locally using the actual IAM role that will be used in production. 102 | 103 | **IMPORTANT:** Local development requires a specific AWS IAM trust policy to be injected into IAM roles. This trust policy allows the local development environment to assume the role. If you have already deployed your architecture to AWS, the `dev` command will automatically inject the trust policy into the IAM roles for your containers. **You'll want to ensure that you do not do this in your production environment.** 104 | 105 | -------------------------------------------------------------------------------- /docs/examples/express.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Express Application 3 | short_title: Serverless Express Application 4 | description: >- 5 | Deploy an Express-based web application with Serverless Container Framework. 6 | This example demonstrates how to build a simple Express application that can be deployed to 7 | AWS Lambda or AWS ECS Fargate without rearchitecting. 8 | keywords: 9 | - Serverless Container Framework 10 | - Express Framework 11 | - Web Application 12 | - Static File Serving 13 | - Health Check Endpoint 14 | - AWS Lambda Deployment 15 | - AWS ECS Fargate Deployment 16 | - Serverless Express 17 | - Container Deployment 18 | - Serverless Deployment 19 | - AWS Application Load Balancer 20 | - Local Development 21 | - Hot Reloading 22 | - Cloud Infrastructure 23 | - Node.js Application 24 | --- 25 | 26 | # Serverless Express Application 27 | 28 | This example demonstrates how to build and deploy a simple Express-based web application using the Serverless Container Framework (SCF). The application sets up basic routes—including a health check, static file delivery, and a fallback 404 page—with minimal configuration. The Serverless Container Framework enables this application to be deployed to either AWS Lambda or AWS ECS Fargate without rearchitecting. 29 | 30 | ## Features 31 | 32 | - **Express Framework:** 33 | Leverages Express for routing and middleware handling. 34 | - **Static File Serving:** 35 | Serves static assets from a dedicated public directory. 36 | - **Health Check Endpoint:** 37 | Provides a simple `/health` route for monitoring application health. 38 | - **Flexible Compute Options:** 39 | Easily switch between AWS Lambda and AWS Fargate ECS deployments via SCF configuration. 40 | 41 | ## Prerequisites 42 | 43 | Before getting started, make sure you have: 44 | 45 | - **Docker:** Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 46 | - **Serverless Framework:** Install globally: 47 | ```bash 48 | npm i -g serverless 49 | ``` 50 | - **Node.js & npm:** Ensure you have a recent Node.js LTS version installed. 51 | - **AWS Credentials:** Properly configure your AWS credentials (via environment variables or AWS profiles) to allow SCF to provision and update AWS resources. 52 | 53 | For more information on setting up AWS credentials, see the [SCF Getting Started guide](../getting-started.md). 54 | 55 | ## Configuration 56 | 57 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 58 | 59 | ```yaml 60 | name: express 61 | 62 | deployment: 63 | type: awsApi@1.0 64 | 65 | containers: 66 | service: 67 | src: ./service 68 | routing: 69 | pathPattern: /* 70 | pathHealthCheck: /health 71 | environment: 72 | HELLO: world 73 | compute: 74 | type: awsLambda # or awsFargateEcs 75 | ``` 76 | 77 | This configuration sets: 78 | - **Project Namespace:** The project name (express) is used as a namespace in your AWS account. 79 | - **Deployment Settings:** Configures networking (ALB, VPC, API Gateway) via the AWS API deployment type. 80 | - **Container Details:** 81 | - The source code is located in the `./service` directory. 82 | - A catch-all routing rule (`/*`) is used with a dedicated health check endpoint (`/health`). 83 | - An environment variable (`HELLO`) is provided. 84 | - The compute type is set to `awsLambda` by default (switchable to `awsFargateEcs` as needed). 85 | 86 | For more details on SCF configuration options, see the [SCF Configuration documentation](../configuration.md). 87 | 88 | ## Project Structure 89 | 90 | A typical project structure for this Express example: 91 | ``` 92 | example-express/ 93 | ├── serverless.containers.yml # SCF configuration file 94 | └── service/ 95 | ├── package.json # Node.js project configuration and dependencies 96 | └── src/ 97 | ├── index.js # Main Express application entrypoint 98 | └── public/ # Static assets (HTML, CSS, images, etc.) 99 | ``` 100 | 101 | ## Development 102 | 103 | Serverless Container Framework provides a local development mode that emulates AWS routing and compute environments, including AWS Application Load Balancer emulation: 104 | ```bash 105 | serverless dev 106 | ``` 107 | 108 | This will automatically start everything and set up hot reloading. 109 | 110 | For more information on local development with SCF, see the [SCF Development documentation](../development.md). 111 | 112 | ## Deployment 113 | 114 | Deploy your Express application to AWS by running: 115 | ```bash 116 | serverless deploy 117 | ``` 118 | 119 | During deployment, SCF builds the container image (using the provided multi-stage Dockerfile) and provisions the necessary AWS resources (ALB, VPC, Lambda function or ECS Fargate service). 120 | 121 | For more details on deployment options and processes, see the [SCF Deployment documentation](../deployment.md). 122 | 123 | ## Integrating with other resources 124 | 125 | Serverless Container Framework supports the Serverless Framework Variables system to reference infrastructure details, secrets, and more from various sources: 126 | 127 | ```yaml 128 | containers: 129 | service: 130 | environment: 131 | # Simple static value 132 | SERVICE_NAME: express-service 133 | 134 | # Environment variable reference 135 | NODE_ENV: ${env:NODE_ENV} 136 | 137 | # AWS Systems Manager Parameter Store reference 138 | DATABASE_URL: ${aws:ssm:/path/to/database/url} 139 | 140 | # AWS Secrets Manager reference 141 | DATABASE_PASSWORD: ${aws:secretsmanager:ExpressDbSecret.password} 142 | 143 | # HashiCorp Vault reference 144 | API_SECRET: ${vault:secret/data/api/credentials.secret} 145 | 146 | # HashiCorp Terraform state reference 147 | REDIS_ENDPOINT: ${terraform:outputs:redis_endpoint} 148 | 149 | # S3 bucket value reference 150 | CONFIG_JSON: ${aws:s3:config-bucket/express-config.json} 151 | 152 | # CloudFormation stack output reference 153 | VPC_ID: ${aws:cf:networking-stack.VpcIdOutput} 154 | ``` 155 | 156 | For more details on using variables, see the [Serverless Framework Variables documentation](https://www.serverless.com/framework/docs/guides/variables). 157 | 158 | ## Cleanup 159 | 160 | To remove deployed AWS resources when they are no longer needed: 161 | ```bash 162 | serverless remove --force --all 163 | ``` 164 | 165 | ## Additional Resources 166 | 167 | - [Serverless Container Framework Documentation](../README.md) 168 | - [Express Documentation](https://expressjs.com) 169 | - [Docker Documentation](https://docs.docker.com) 170 | - [AWS Lambda Documentation](https://aws.amazon.com/lambda) 171 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) 172 | -------------------------------------------------------------------------------- /docs/examples/nextjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Next.js Application 3 | short_title: Serverless Next.js Application 4 | description: >- 5 | Deploy a Next.js web application with Serverless Container Framework. 6 | This example demonstrates how to build a server-side rendered application that can be deployed to 7 | AWS Lambda or AWS ECS Fargate without rearchitecting. 8 | keywords: 9 | - Serverless Container Framework 10 | - Next.js Framework 11 | - Server-Side Rendering 12 | - Dynamic Routing 13 | - Static Site Generation 14 | - AWS Lambda Deployment 15 | - AWS ECS Fargate Deployment 16 | - Serverless Next.js 17 | - Container Deployment 18 | - Serverless Deployment 19 | - AWS Application Load Balancer 20 | - Local Development 21 | - Hot Reloading 22 | - Cloud Infrastructure 23 | - Docker Multi-stage Builds 24 | --- 25 | 26 | # Serverless Next.js Application 27 | 28 | This example demonstrates how to build and deploy a Next.js web application using the Serverless Container Framework (SCF). Due to the potential for large SSR outputs, this project is configured for deployment on AWS Fargate ECS, though the Serverless Container Framework enables this application to be deployed to either AWS Lambda or AWS ECS Fargate without rearchitecting. 29 | 30 | ## Features 31 | 32 | - **Next.js Framework:** 33 | Leverages Next.js for server-side rendering (SSR) and static site generation. 34 | - **Dynamic Routing & SSR:** 35 | Provides robust routing and dynamic page generation. 36 | - **Optimized Production Builds:** 37 | Docker multi-stage builds ensure efficient deployments. 38 | - **Flexible Compute Options:** 39 | Configured for AWS Fargate ECS to handle large HTML responses, but can be switched to AWS Lambda if response sizes are manageable. 40 | 41 | ## Prerequisites 42 | 43 | Before getting started, make sure you have: 44 | 45 | - **Docker:** Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 46 | - **Serverless Framework:** Install globally: 47 | ```bash 48 | npm i -g serverless 49 | ``` 50 | - **Node.js & npm:** Ensure you have a recent Node.js LTS version installed. 51 | - **AWS Credentials:** Configure your AWS credentials (via environment variables or profiles) for SCF deployments. 52 | 53 | For more information on setting up AWS credentials, see the [SCF Getting Started guide](../getting-started.md). 54 | 55 | ## Configuration 56 | 57 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 58 | 59 | ```yaml 60 | name: nextjs 61 | 62 | deployment: 63 | type: awsApi@1.0 64 | 65 | containers: 66 | service: 67 | src: ./service 68 | routing: 69 | pathPattern: /* 70 | pathHealthCheck: /health 71 | environment: 72 | HELLO: world 73 | compute: 74 | # awsLambda is not recommended for this Next.js app. 75 | # SSR can generate large HTML responses, which may exceed 76 | # the req/res size limits for AWS Lambda. 77 | type: awsFargateEcs 78 | ``` 79 | 80 | For more details on SCF configuration options, see the [SCF Configuration documentation](../configuration.md). 81 | 82 | ## Project Structure 83 | 84 | A typical project structure for this Next.js example: 85 | ``` 86 | example-nextjs/ 87 | ├── serverless.containers.yml # SCF configuration file 88 | └── service/ 89 | ├── next.config.ts # Next.js configuration file 90 | ├── package.json # Project configuration and dependencies 91 | ├── public/ # Static assets (images, CSS, etc.) 92 | └── src/ 93 | ├── app/ # Next.js app folder (pages, components, etc.) 94 | └── (other directories) # Additional assets and logic 95 | ``` 96 | 97 | ## Development 98 | 99 | Serverless Container Framework provides a local development mode that emulates AWS routing and compute environments, including AWS Application Load Balancer emulation: 100 | ```bash 101 | serverless dev 102 | ``` 103 | This will automatically start the Next.js development server with hot reloading and AWS emulation. It detects the dev npm script and uses that for hot reloading. 104 | 105 | For more information on local development with SCF, see the [SCF Development documentation](../development.md). 106 | 107 | ## Deployment 108 | 109 | Deploy your Next.js application to AWS by running: 110 | ```bash 111 | serverless deploy 112 | ``` 113 | SCF builds the container image (using the provided multi-stage Dockerfile) and provisions the necessary AWS resources. 114 | 115 | For more details on deployment options and processes, see the [SCF Deployment documentation](../deployment.md). 116 | 117 | ## Integrating with other resources 118 | 119 | Serverless Container Framework supports the Serverless Framework Variables system to reference infrastructure details, secrets, and more from various sources: 120 | 121 | ```yaml 122 | containers: 123 | service: 124 | environment: 125 | # Simple static value 126 | SERVICE_NAME: nextjs-application 127 | 128 | # Environment variable reference 129 | NODE_ENV: ${env:NODE_ENV} 130 | 131 | # AWS Systems Manager Parameter Store reference 132 | API_ENDPOINT: ${aws:ssm:/path/to/api/endpoint} 133 | 134 | # AWS Secrets Manager reference 135 | DATABASE_PASSWORD: ${aws:secretsmanager:NextjsDbSecret.password} 136 | 137 | # HashiCorp Vault reference 138 | AUTH_SECRET: ${vault:secret/data/auth/credentials.secret} 139 | 140 | # HashiCorp Terraform state reference 141 | REDIS_ENDPOINT: ${terraform:outputs:redis_endpoint} 142 | 143 | # S3 bucket value reference 144 | CONFIG_JSON: ${aws:s3:config-bucket/nextjs-config.json} 145 | 146 | # CloudFormation stack output reference 147 | VPC_ID: ${aws:cf:networking-stack.VpcIdOutput} 148 | ``` 149 | 150 | For more details on using variables, see the [Serverless Framework Variables documentation](https://www.serverless.com/framework/docs/guides/variables). 151 | 152 | ## Cleanup 153 | 154 | To remove the deployed AWS resources, run: 155 | ```bash 156 | serverless remove --force --all 157 | ``` 158 | 159 | ## Additional Resources 160 | 161 | - [Serverless Container Framework Documentation](../README.md) 162 | - [Next.js Documentation](https://nextjs.org/docs) 163 | - [Docker Documentation](https://docs.docker.com) 164 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) 165 | -------------------------------------------------------------------------------- /docs/examples/react.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless React Application 3 | short_title: Serverless React Application 4 | description: >- 5 | Deploy a React application with Serverless Container Framework. 6 | This example demonstrates how to build a client-side React application that can be deployed to 7 | AWS Lambda or AWS ECS Fargate without rearchitecting. 8 | keywords: 9 | - Serverless Container Framework 10 | - React Framework 11 | - Client-Side Application 12 | - esbuild Bundling 13 | - Static Asset Serving 14 | - AWS Lambda Deployment 15 | - AWS ECS Fargate Deployment 16 | - Serverless React 17 | - Container Deployment 18 | - Serverless Deployment 19 | - AWS Application Load Balancer 20 | - Local Development 21 | - Hot Reloading 22 | - Cloud Infrastructure 23 | - Docker Multi-stage Builds 24 | --- 25 | 26 | # Serverless React Application 27 | 28 | This example demonstrates how to build and deploy a React application using the Serverless Container Framework (SCF). The application is bundled using esbuild and optimized for production deployments. The Serverless Container Framework enables this application to be deployed to either AWS Lambda or AWS ECS Fargate without rearchitecting, though it is configured for AWS Fargate ECS to accommodate larger bundle sizes. 29 | 30 | ## Features 31 | 32 | - **React Framework:** 33 | Builds a client-side React application with a component-based architecture. 34 | - **Fast Bundling with esbuild:** 35 | Uses esbuild for rapid development builds and efficient bundling. 36 | - **Static Asset Serving:** 37 | Supports serving static assets and client-side routing. 38 | - **Flexible Compute Options:** 39 | Configured for AWS Fargate ECS to accommodate larger bundle sizes that might exceed AWS Lambda request/response limits, but can be switched to AWS Lambda for smaller applications. 40 | 41 | ## Prerequisites 42 | 43 | Before getting started, make sure you have: 44 | 45 | - **Docker:** Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 46 | - **Serverless Framework:** Install globally: 47 | ```bash 48 | npm i -g serverless 49 | ``` 50 | - **Node.js & npm:** Ensure you have a recent Node.js LTS version installed. 51 | - **AWS Credentials:** Set up your AWS credentials (via environment variables or profiles) for SCF deployment. 52 | 53 | For more information on setting up AWS credentials, see the [SCF Getting Started guide](../getting-started.md). 54 | 55 | ## Configuration 56 | 57 | The SCF configuration is defined in the `serverless.containers.yml` file at the project root: 58 | 59 | ```yaml 60 | name: react 61 | 62 | deployment: 63 | type: awsApi@1.0 64 | 65 | containers: 66 | service: 67 | src: ./service 68 | routing: 69 | pathPattern: /* 70 | pathHealthCheck: /health 71 | environment: 72 | HELLO: world 73 | compute: 74 | # awsLambda is not recommended for this React app. 75 | # The bundled app may exceed AWS Lambda's request/response size limits. 76 | type: awsFargateEcs 77 | ``` 78 | 79 | For more details on SCF configuration options, see the [SCF Configuration documentation](../configuration.md). 80 | 81 | ## Project Structure 82 | 83 | A typical project structure for this React example: 84 | ``` 85 | example-react/ 86 | ├── serverless.containers.yml # SCF configuration file 87 | └── service/ 88 | ├── package.json # Project configuration and dependencies 89 | ├── public/ # Static assets (HTML, CSS, images, etc.) 90 | ├── server.js # Server entrypoint for serving the React app 91 | └── src/ 92 | ├── index.jsx # React application entrypoint 93 | └── (other components) # React components and logic 94 | ``` 95 | 96 | ## Development 97 | 98 | Serverless Container Framework provides a local development mode that emulates AWS routing and compute environments, including AWS Application Load Balancer emulation: 99 | ```bash 100 | serverless dev 101 | ``` 102 | This will automatically start the development environment with hot reloading and AWS-like routing. 103 | 104 | For more information on local development with SCF, see the [SCF Development documentation](../development.md). 105 | 106 | ## Deployment 107 | 108 | Deploy your React application to AWS by running: 109 | ```bash 110 | serverless deploy 111 | ``` 112 | SCF takes care of building the container image (using the provided Dockerfile) and provisioning the necessary resources. 113 | 114 | For more details on deployment options and processes, see the [SCF Deployment documentation](../deployment.md). 115 | 116 | ## Integrating with other resources 117 | 118 | Serverless Container Framework supports the Serverless Framework Variables system to reference infrastructure details, secrets, and more from various sources: 119 | 120 | ```yaml 121 | containers: 122 | service: 123 | environment: 124 | # Simple static value 125 | SERVICE_NAME: react-application 126 | 127 | # Environment variable reference 128 | NODE_ENV: ${env:NODE_ENV} 129 | 130 | # AWS Systems Manager Parameter Store reference 131 | API_ENDPOINT: ${aws:ssm:/path/to/api/endpoint} 132 | 133 | # AWS Secrets Manager reference 134 | API_KEY: ${aws:secretsmanager:ReactApiSecret.key} 135 | 136 | # HashiCorp Vault reference 137 | AUTH_SECRET: ${vault:secret/data/auth/credentials.secret} 138 | 139 | # HashiCorp Terraform state reference 140 | REDIS_ENDPOINT: ${terraform:outputs:redis_endpoint} 141 | 142 | # S3 bucket value reference 143 | CONFIG_JSON: ${aws:s3:config-bucket/react-config.json} 144 | 145 | # CloudFormation stack output reference 146 | VPC_ID: ${aws:cf:networking-stack.VpcIdOutput} 147 | ``` 148 | 149 | For more details on using variables, see the [Serverless Framework Variables documentation](https://www.serverless.com/framework/docs/guides/variables). 150 | 151 | ## Cleanup 152 | 153 | To remove the deployed AWS resources, run: 154 | ```bash 155 | serverless remove --force --all 156 | ``` 157 | 158 | ## Additional Resources 159 | 160 | - [Serverless Container Framework Documentation](../README.md) 161 | - [React Documentation](https://reactjs.org/docs/getting-started.html) 162 | - [esbuild Documentation](https://esbuild.github.io) 163 | - [Docker Documentation](https://docs.docker.com) 164 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) 165 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## How is Serverless Container Framework different from Serverless Framework? 4 | 5 | Serverless Framework has historically focused on AWS Lambda deployment — and it is the best in class at it. If you're looking for a tool that specializes in AWS Lambda use-cases (e.g. APIs, event-driven architectures, Step Functions) and has a large ecosystem of plugins expanding those capabilities, Serverless Framework is your best choice. While it offers some container support, this isn't its core focus. 6 | 7 | Serverless Container Framework (SCF) takes a fundamentally different approach by embracing containers as the deployment artifact. This brings several key advantages: 8 | 9 | - **Cross-Provider Deployment**: Deploy to different compute services and cloud providers without being an expert in each one 10 | - **Consistent Experience**: Use the same development and deployment workflow everywhere 11 | - **No Re-architecting**: Move your containerized applications between providers without changing your code 12 | - **Container Ecosystem**: Leverage the rich container tooling and practices you already know 13 | 14 | While SCF and Serverless Framework share some common ground, SCF's focus on container portability and provider-agnostic deployments sets it on a distinct path. It maintains all the convenience of serverless while adding the flexibility and portability that containers provide. 15 | 16 | ## Why does the Serverless Container Framework need to exist? 17 | 18 | We built the Serverless Container Framework in response to a clear need from our Serverless Framework customers. Many have high-volume AWS Lambda functions they need to migrate—whether to reduce costs, improve performance, or access features that Lambda doesn't support. While AWS Lambda is excellent for prototyping and rapid scaling, it left many wondering: what comes after that? The serverless story felt incomplete. 19 | 20 | Further, we observed that serverless architectures often struggle to integrate with organizations' main workflows, largely because enterprise standards are built around containers—an area where AWS Lambda has historically fallen short. 21 | 22 | Now, with recent performance improvements in container support on AWS Lambda, we believe the time is right for a new container deployment tool that fully embraces containers as the deployment artifact and leverages the rich container ecosystem. 23 | 24 | ## How much does Serverless Container Framework cost? 25 | 26 | Serverless Container Framework follows Serverless Inc's Customer & License Agreement, making it free for developers and organizations earning less than $2 million USD in gross revenue per year. For organizations exceeding this revenue threshold, a credit-based pricing model is currently in effect, where a credit is charged for each deployed container per month. 27 | 28 | For clarity, Serverless Inc does not charge for 1) individual users, 2) usage (e.g. requests served, or compute used, like a hosting provider), 3) invidividual CLI actions (e.g. each time you run "deploy"). Instead, the credit price is a fixed price for a container that has been in a deployed state for longer than 10 days within a current month. The 10 day timeframe allows many testing/preview deployed instances to be created without any cost. 29 | 30 | ## How does Serverless Container Framework compare to other container deployment tools? 31 | 32 | There are lots of tools for deploying containers (Terraform, Pulumi, etc.), but they only give you pieces, requiring you to assemble (and maintain!) a rich deployment and development experience. 33 | 34 | While that's great for a lot of infra, for your core compute development experience, we feel developers need more. 35 | 36 | What we do at Serverless Inc is offer exceptional infrastructure-as-code experiences for critical use-cases. We go beyond basic deploy/remove operations to provide the advanced automation developers need. SCF features a rich development mode that emulates cloud compute locally, with a local API that matches AWS Application Load Balancer behavior, real-time log streaming, hot module reloading, and zero-downtime switching between AWS Lambda and ECS Fargate. 37 | 38 | ## What are the downsides of Serverless Container Framework? 39 | 40 | If you're coming from AWS Lambda, some of the downsides are: 41 | 42 | * **Longer deployment times**: Container deployments take longer than function deployments for two reasons: 43 | - Container images are larger than function packages 44 | - AWS Fargate requires additional infrastructure setup for safe deployments 45 | 46 | * **Request/Response limitations for AWS Lambda and AWS ALB**: AWS Lambda configured to AWS Application Load Balancer has a 1MB payload limit on request and response sizes (this includes headers). While compression can help, you'll need to architect your application with these limits in mind. We're actively working with AWS to advocate for higher limits, and are very optimistic that this will be addressed in the near future. 47 | 48 | * **Lack of streaming response support for AWS Lambda and AWS ALB**: AWS Lambda and AWS ALB do not support streaming responses. This means that if your application attempts to stream a response, ALB will await the whole response before sending any data to the client. 49 | 50 | If you're coming from other container deployment tools, the main consideration is maturity. As a newer solution in this space, we're continuously evolving. However, we believe our developer experience already surpasses existing tools, offering unique features like local cloud emulation, real-time logs, and seamless Lambda/ECS switching. 51 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Serverless Container Framework - Getting Started 3 | short_title: Getting Started 4 | description: >- 5 | Quick start guide for Serverless Container Framework. Learn how to install, 6 | configure, and deploy your first containerized application with step-by-step 7 | instructions for AWS Lambda and AWS ECS Fargate. 8 | keywords: 9 | - Serverless Container Framework 10 | - Serverless Container Framework Getting Started 11 | - Serverless Container Framework Quick Start 12 | - Serverless Container Framework Container Setup 13 | - Serverless Container Framework AWS Setup 14 | - Serverless Container Framework Installation Guide 15 | - Serverless Container Framework First Deployment 16 | - Serverless Container Framework Container Tutorial 17 | - Serverless Container Framework AWS Configuration 18 | - Serverless Container Framework Development Setup 19 | - Serverless Container Framework Docker Setup 20 | - Serverless Container Framework Initial Configuration 21 | - Serverless Setup 22 | - Container Basics 23 | - AWS Integration 24 | --- 25 | 26 | # Getting Started 27 | 28 | This guide will help you get started with Serverless Container Framework (SCF) by deploying a simple API. 29 | 30 | ### Prerequisites 31 | - Node.js 20.x or later 32 | - AWS Account with administrative access 33 | - Docker installed and running 34 | 35 | ### Installation & Setup 36 | 37 | 1. SCF resides within the [Serverless Framework](https://github.com/serverless/serverless). Install the Serverless Framework CLI globally: 38 | 39 | ```bash 40 | npm install -g serverless 41 | ``` 42 | 43 | 2. Configure your AWS credentials using one of these methods. [Additional options can be found here.](https://www.serverless.com/framework/docs/providers/aws/guide/credentials) 44 | 45 | ```bash 46 | # Option 1: AWS CLI (recommended) 47 | aws configure 48 | 49 | # Option 2: Environment variables 50 | export AWS_ACCESS_KEY_ID=your-key-id 51 | export AWS_SECRET_ACCESS_KEY=your-access-key 52 | export AWS_SESSION_TOKEN=your-session-token 53 | ``` 54 | 55 | ### Getting Started 56 | 57 | 1. Start with an example project by cloning the repository: 58 | ```bash 59 | git clone https://github.com/serverless/containers.git 60 | ``` 61 | 62 | 2. Navigate to the example project directory, and install any dependencies: 63 | ```bash 64 | cd example-express/service 65 | npm install 66 | ``` 67 | 68 | ### Development 69 | 70 | Ensure you are within the directory containing the `serverless.containers.yml` file. 71 | ```bash 72 | cd example-express 73 | ``` 74 | 75 | Start the local development environment: 76 | ```bash 77 | serverless dev 78 | ``` 79 | 80 | This starts a local emulation of AWS Application Load Balancer at `http://localhost:3000`. This will forward requests to your containers. Logs, requests and more from your containers will be available in the terminal. Your containers will auto-reload or rebuild on code changes. 81 | 82 | ### Deployment 83 | 84 | Deploy to AWS: 85 | ```bash 86 | serverless deploy 87 | ``` 88 | 89 | The initial deployment creates AWS resources (ALB, VPC, etc.) and takes ~5-10 minutes. Subsequent deploys are faster. 90 | 91 | ### Cleanup 92 | 93 | Remove your deployment: 94 | ```bash 95 | # Remove application only 96 | serverless remove 97 | 98 | # Remove all AWS resources including VPC 99 | serverless remove --force 100 | ``` 101 | 102 | ### Troubleshooting 103 | - Ensure Docker daemon is running for local development 104 | - Check AWS credentials are properly configured using `aws sts get-caller-identity` 105 | - View detailed logs with `serverless dev --debug` or `serverless deploy --debug` 106 | -------------------------------------------------------------------------------- /example-ai-streaming/README.md: -------------------------------------------------------------------------------- 1 | # Example Streaming AI Chat Interface 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) is a powerful tool that simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy an Express-based AI fullstack streaming application that uses Server-Sent Events (SSE) to stream live AI responses from multiple providers (OpenAI and Anthropic). It includes both a front-end and a back-end.The application leverages SCF for local development, flexible compute configurations, and smooth AWS deployments. 6 | 7 | **Updated with Claude 3.7 Sonnet Support** 8 | 9 | ## Features 10 | 11 | - **Express & SSE Streaming:** 12 | Leverages Express as the HTTP server and streams AI responses using Server-Sent Events. 13 | - **Multi-Provider Support:** 14 | Integrates with both OpenAI and Anthropic APIs to provide AI completions. 15 | - **Node.js Application:** 16 | Built with Node.js and modern JavaScript using lightweight frameworks. 17 | - **Compute Flexibility:** 18 | Easily switch between AWS Lambda and AWS Fargate ECS deployments via the SCF configuration. 19 | - **Local Development Experience:** 20 | SCF provides a rich local development mode that emulates the cloud environment, complete with hot reloading and AWS-like routing. 21 | 22 | ## Prerequisites 23 | 24 | **Docker:** 25 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 26 | 27 | **Serverless Framework:** 28 | Install the Serverless Framework globally: 29 | ```bash 30 | npm i -g serverless 31 | ``` 32 | 33 | **Node.js & npm:** 34 | Ensure you have a recent Node.js LTS version installed. 35 | 36 | **AWS Credentials:** 37 | Properly configure your AWS credentials (via environment variables or AWS profiles) to allow SCF to provision and update AWS resources. 38 | 39 | ## Configuration 40 | 41 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 42 | 43 | ```yaml 44 | name: ai-streaming 45 | 46 | deployment: 47 | type: awsApi@1.0 48 | 49 | containers: 50 | service: 51 | src: ./service 52 | routing: 53 | pathPattern: /* 54 | pathHealthCheck: /health 55 | environment: 56 | OPENAI_API_KEY: ${env:OPENAI_API_KEY} 57 | ANTHROPIC_API_KEY: ${env:ANTHROPIC_API_KEY} 58 | compute: 59 | type: awsLambda # or awsFargateEcs 60 | ``` 61 | 62 | This file specifies: 63 | - **Project Name:** Used as a namespace in your AWS account. 64 | - **Deployment Settings:** Configures networking (ALB, VPC) via the AWS API deployment type. 65 | - **Container Details:** 66 | - The source code is located in the `./service` directory. 67 | - A catch-all routing rule (`/*`) is used with a dedicated health check endpoint (`/health`). 68 | - API keys for OpenAI and Anthropic are injected as environment variables. 69 | - The compute type is set to `awsLambda` by default (switchable to `awsFargateEcs` as needed). 70 | 71 | ## Project Structure 72 | 73 | A typical project structure looks like this: 74 | ``` 75 | example-ai-streaming/ 76 | ├── serverless.containers.yml # SCF project configuration file 77 | └── service/ 78 | ├── Dockerfile # Multi-stage Dockerfile for Lambda and Fargate 79 | ├── package.json # Node.js project configuration and dependencies 80 | ├── .env # Environment variables (not committed) 81 | └── src/ 82 | ├── index.js # Main Express application entrypoint 83 | ├── routes/ # API route definitions (including AI streaming endpoint) 84 | ├── middleware/ # Custom middleware (error handling, etc.) 85 | └── public/ # Static assets (HTML, CSS, JS, images) 86 | ``` 87 | 88 | ## Development 89 | 90 | SCF provides a local development mode that emulates AWS routing, Lambda, and ECS Fargate environments. To start the development mode (with hot reloading on file changes), run: 91 | 92 | ```bash 93 | serverless dev 94 | ``` 95 | 96 | Additionally, you can run the application directly using: 97 | ```bash 98 | npm start 99 | ``` 100 | 101 | ## Deployment 102 | 103 | Deploy your AI streaming application to AWS with: 104 | ```bash 105 | serverless deploy 106 | ``` 107 | 108 | During deployment, SCF builds the container image (using the provided Dockerfile) and provisions AWS resources (ALB, VPC, Lambda function or ECS service) automatically. 109 | 110 | ## Cleanup 111 | 112 | To remove deployed AWS resources when they are no longer needed: 113 | ```bash 114 | serverless remove 115 | ``` 116 | 117 | For complete cleanup (including shared infrastructure): 118 | ```bash 119 | serverless remove --force --all 120 | ``` 121 | 122 | ## Additional Resources 123 | 124 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 125 | - [Express Documentation](https://expressjs.com) 126 | - [OpenAI API Documentation](https://platform.openai.com/docs) 127 | - [Anthropic API Documentation](https://docs.anthropic.com) 128 | -------------------------------------------------------------------------------- /example-ai-streaming/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: ai-streaming 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | OPENAI_API_KEY: ${env:OPENAI_API_KEY} 14 | ANTHROPIC_API_KEY: ${env:ANTHROPIC_API_KEY} 15 | compute: 16 | type: awsLambda # or awsFargateEcs -------------------------------------------------------------------------------- /example-ai-streaming/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-express-ai-streaming", 3 | "version": "1.0.0", 4 | "description": "Example of AI streaming with Express and SSE", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "test": "jest" 9 | }, 10 | "dependencies": { 11 | "express": "^4.21.2", 12 | "openai": "^4.82.0", 13 | "@anthropic-ai/sdk": "^0.36.0", 14 | "dotenv": "^16.4.1", 15 | "cors": "^2.8.5" 16 | }, 17 | "devDependencies": { 18 | "jest": "^29.7.0", 19 | "supertest": "^6.3.4" 20 | } 21 | } -------------------------------------------------------------------------------- /example-ai-streaming/service/src/__tests__/ai.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const app = require("../index"); 3 | 4 | describe("AI Streaming API", () => { 5 | test("OpenAI streaming endpoint returns SSE response", async () => { 6 | const response = await request(app) 7 | .post("/api/chat") 8 | .send({ 9 | provider: "openai", 10 | model: "gpt-4", 11 | messages: [{ role: "user", content: "Hello" }], 12 | }); 13 | 14 | expect(response.headers["content-type"]).toMatch(/text\/event-stream/); 15 | expect(response.status).toBe(200); 16 | }); 17 | 18 | test("Anthropic streaming endpoint returns SSE response", async () => { 19 | const response = await request(app) 20 | .post("/api/chat") 21 | .send({ 22 | provider: "anthropic", 23 | model: "claude-3-5-sonnet-20241022", 24 | messages: [{ role: "user", content: "Hello" }], 25 | }); 26 | 27 | expect(response.headers["content-type"]).toMatch(/text\/event-stream/); 28 | expect(response.status).toBe(200); 29 | }); 30 | }); -------------------------------------------------------------------------------- /example-ai-streaming/service/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Main application file for the Express AI Streaming example. 3 | * Sets up middleware, routes, and error handling before starting the server. 4 | */ 5 | 6 | const express = require("express"); 7 | const path = require("path"); 8 | const cors = require("cors"); 9 | const { setupAIRoutes } = require("./routes/ai"); 10 | const { errorHandler } = require("./middleware/errorHandler"); 11 | 12 | const app = express(); 13 | const port = process.env.PORT || 8080; 14 | 15 | /** 16 | * Configure middleware 17 | */ 18 | app.use(cors()); 19 | app.use(express.json()); 20 | app.use(express.static(path.join(__dirname, "public"))); 21 | 22 | // API Routes - these should be handled first 23 | setupAIRoutes(app); 24 | 25 | /** 26 | * Health check endpoint handler. 27 | * 28 | * @param {Object} req - Express request object. 29 | * @param {Object} res - Express response object. 30 | */ 31 | app.get("/health", (req, res) => { 32 | res.status(200).json({ status: "healthy" }); 33 | }); 34 | 35 | /** 36 | * Serve index.html for all remaining routes 37 | * This enables client-side routing to work properly 38 | * 39 | * @param {Object} req - Express request object 40 | * @param {Object} res - Express response object 41 | */ 42 | app.get("*", (req, res) => { 43 | res.sendFile(path.join(__dirname, "public", "index.html")); 44 | }); 45 | 46 | // Error handling 47 | app.use(errorHandler); 48 | 49 | /** 50 | * Starts the Express server on the specified port. 51 | * 52 | * @param {Object} config - Server configuration object. 53 | * @param {number} config.port - Port number on which to run the server. 54 | */ 55 | const startServer = ({ port: serverPort }) => { 56 | app.listen(serverPort, "0.0.0.0", () => { 57 | console.log(`Server running on port ${serverPort}`); 58 | }); 59 | }; 60 | 61 | startServer({ port }); 62 | 63 | module.exports = app; -------------------------------------------------------------------------------- /example-ai-streaming/service/src/middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global error handling middleware 3 | * 4 | * @param {Error} err - Error object 5 | * @param {Object} req - Express request object 6 | * @param {Object} res - Express response object 7 | * @param {Function} next - Next middleware function 8 | */ 9 | function errorHandler(err, req, res, next) { 10 | console.error(err); 11 | 12 | const status = err.status || 500; 13 | const message = err.message || "Internal Server Error"; 14 | const code = err.code || "internal_error"; 15 | 16 | res.status(status).json({ 17 | error: message, 18 | message, 19 | code, 20 | status, 21 | }); 22 | } 23 | 24 | module.exports = { errorHandler }; -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, body { 8 | width: 100%; 9 | height: 100%; 10 | font-family: "Roboto Mono", monospace; 11 | /* Set a static background image */ 12 | background: url('/images/background.png') no-repeat center center fixed; 13 | background-size: cover; 14 | background-color: #000000; 15 | color: #ffffff; 16 | } 17 | 18 | /** 19 | * Container for centering content. No animation is applied here, 20 | * so that the logo and chat container animate independently. 21 | */ 22 | .container { 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | transform: translateY(-50px); 28 | min-height: 100vh; 29 | width: 90%; 30 | max-width: 800px; 31 | margin: 0 auto; 32 | padding: 0 1rem; 33 | text-align: center; 34 | } 35 | 36 | /** 37 | * Logo element styles. The logo starts slightly scaled down (0.9) and hidden (opacity 0), 38 | * then fades in with a slight zoom effect over 0.25s, starting after a 0.75s delay. 39 | * The ease-out timing function makes the zoom effect decelerate smoothly. 40 | */ 41 | .logo { 42 | max-width: 200px; 43 | width: 100%; 44 | height: auto; 45 | margin-bottom: 60px; 46 | opacity: 0; 47 | transform: scale(0.9); 48 | animation: logoFadeZoom 0.25s ease-out forwards; 49 | animation-delay: 0.75s; 50 | } 51 | 52 | /** 53 | * Chat container fades in over 0.25s, with a delay of 0.5s. 54 | */ 55 | .chat-container { 56 | background: #1c1c1c; 57 | border: 1px solid #333333; 58 | border-radius: 8px; 59 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6); 60 | overflow: hidden; 61 | width: 100%; 62 | color: #ffffff; 63 | opacity: 0; 64 | animation: fadeIn 0.25s ease-in forwards; 65 | animation-delay: 0.5s; 66 | } 67 | 68 | .chat-output { 69 | height: 400px; 70 | padding: 1rem; 71 | overflow-y: auto; 72 | border-bottom: 1px solid #333333; 73 | } 74 | 75 | .loading { 76 | opacity: 0.3; 77 | } 78 | 79 | .input-container { 80 | padding: 1rem; 81 | } 82 | 83 | textarea { 84 | width: 100%; 85 | min-height: 100px; 86 | padding: 0.5rem; 87 | margin-bottom: 1rem; 88 | border: 1px solid #333333; 89 | border-radius: 4px; 90 | background: #1f1f1f; 91 | color: #ffffff; 92 | resize: vertical; 93 | } 94 | 95 | /** 96 | * Removes the default outline on focus for the textarea. 97 | */ 98 | textarea:focus { 99 | outline: none; 100 | border-color: #555555; 101 | } 102 | 103 | .message { 104 | display: flex; 105 | align-items: flex-start; 106 | padding-bottom: 15px; 107 | } 108 | 109 | .message .sender-label { 110 | font-weight: bold; 111 | margin-right: 8px; 112 | min-width: 100px; 113 | width: 100px; 114 | text-align: left; 115 | word-wrap: break-word; 116 | } 117 | 118 | .message .message-text { 119 | flex: 1; 120 | text-align: left; 121 | padding-left: 10px; 122 | } 123 | 124 | .button-group { 125 | display: flex; 126 | width: 100%; 127 | justify-content: space-between; 128 | align-items: center; 129 | } 130 | 131 | select { 132 | padding: 0.5rem; 133 | border: 1px solid #333333; 134 | border-radius: 4px; 135 | background: #1f1f1f; 136 | color: #ffffff; 137 | } 138 | 139 | /** 140 | * Removes the default outline on focus for the dropdown. 141 | */ 142 | select:focus { 143 | outline: none; 144 | border-color: #555555; 145 | } 146 | 147 | button { 148 | padding: 0.5rem 1rem; 149 | background: #fd5750; 150 | color: white; 151 | border: 1px solid #333333; 152 | border-radius: 4px; 153 | cursor: pointer; 154 | } 155 | 156 | /** 157 | * Removes the default outline on focus/active state for the button. 158 | */ 159 | button:focus, 160 | button:active { 161 | outline: none; 162 | border-color: #555555; 163 | } 164 | 165 | /** 166 | * Keyframes for a simple fade-in effect. 167 | */ 168 | @keyframes fadeIn { 169 | from { 170 | opacity: 0; 171 | } 172 | to { 173 | opacity: 1; 174 | } 175 | } 176 | 177 | /** 178 | * Keyframes for the logo's fade and zoom in effect. 179 | */ 180 | @keyframes logoFadeZoom { 181 | from { 182 | opacity: 0; 183 | transform: scale(0.9); 184 | } 185 | to { 186 | opacity: 1; 187 | transform: scale(1); 188 | } 189 | } 190 | 191 | /* Responsive design adjustments */ 192 | @media (max-width: 600px) { 193 | .chat-output { 194 | height: 300px; 195 | } 196 | 197 | .logo { 198 | max-width: 150px; 199 | margin-bottom: 30px; 200 | } 201 | 202 | textarea, button, select { 203 | font-size: 1rem; 204 | padding: 0.5rem; 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-ai-streaming/service/src/public/images/background.png -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-ai-streaming/service/src/public/images/favicon.png -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-ai-streaming/service/src/public/images/logo.png -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Serverless Container Framework - AI Streaming Demo 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 | 59 | 60 | 61 |
62 |
63 |
64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /example-ai-streaming/service/src/public/js/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously sends a message to the specified AI provider with the selected model version and streams the response. 3 | * 4 | * @param {Object} options - Options object. 5 | * @param {string} options.provider - The AI provider (e.g., "openai" or "anthropic"). 6 | * @param {string} options.model - The model version (e.g., "gpt-4", "claude-3-5-sonnet-20241022"). 7 | */ 8 | async function sendMessage({ provider, model }) { 9 | const input = document.getElementById('user-input'); 10 | const output = document.getElementById('chat-output'); 11 | const messageText = input.value.trim(); 12 | 13 | if (!messageText) return; 14 | 15 | // Create user message element with sender label 16 | const userMessageDiv = document.createElement('div'); 17 | userMessageDiv.className = 'message user-message'; 18 | 19 | const userLabel = document.createElement('span'); 20 | userLabel.className = 'sender-label'; 21 | userLabel.textContent = 'User:'; 22 | 23 | const userTextSpan = document.createElement('span'); 24 | userTextSpan.className = 'message-text'; 25 | userTextSpan.textContent = messageText; 26 | 27 | userMessageDiv.appendChild(userLabel); 28 | userMessageDiv.appendChild(userTextSpan); 29 | output.appendChild(userMessageDiv); 30 | 31 | // Clear the input field 32 | input.value = ''; 33 | 34 | // Create AI message element with sender label using the provider name 35 | const aiMessageDiv = document.createElement('div'); 36 | aiMessageDiv.className = 'message model-message'; 37 | 38 | const aiLabel = document.createElement('span'); 39 | aiLabel.className = 'sender-label'; 40 | // Capitalize the provider to serve as the model name label. 41 | aiLabel.textContent = provider.charAt(0).toUpperCase() + provider.slice(1) + ':'; 42 | // Apply loading class to reduce opacity while waiting 43 | aiLabel.classList.add('loading'); 44 | 45 | const aiTextSpan = document.createElement('span'); 46 | aiTextSpan.className = 'message-text'; 47 | // Set initial loading text for AI response with reduced opacity 48 | aiTextSpan.textContent = 'Loading...'; 49 | aiTextSpan.classList.add('loading'); 50 | 51 | aiMessageDiv.appendChild(aiLabel); 52 | aiMessageDiv.appendChild(aiTextSpan); 53 | output.appendChild(aiMessageDiv); 54 | 55 | try { 56 | const response = await fetch(`/api/chat`, { 57 | method: 'POST', 58 | headers: { 59 | 'Content-Type': 'application/json', 60 | }, 61 | body: JSON.stringify({ 62 | provider, 63 | model, 64 | messages: [{ role: 'user', content: messageText }], 65 | }), 66 | }); 67 | 68 | // Flag to clear the loading text/classes on the first chunk of data 69 | let firstChunk = true; 70 | const reader = response.body.getReader(); 71 | const decoder = new TextDecoder(); 72 | 73 | while (true) { 74 | const { value, done } = await reader.read(); 75 | if (done) break; 76 | 77 | const chunk = decoder.decode(value); 78 | const lines = chunk.split('\n'); 79 | 80 | for (const line of lines) { 81 | if (line.startsWith('data: ')) { 82 | const data = line.slice(6); 83 | if (data === '[DONE]') break; 84 | 85 | try { 86 | const parsed = JSON.parse(data); 87 | const { content } = parsed; 88 | if (firstChunk) { 89 | // Remove the loading class from both the sender label and text span 90 | aiLabel.classList.remove('loading'); 91 | aiTextSpan.classList.remove('loading'); 92 | // Clear the "Loading..." text on first chunk received 93 | aiTextSpan.textContent = ''; 94 | firstChunk = false; 95 | } 96 | // Append the streamed content to the AI message text 97 | aiTextSpan.textContent += content; 98 | } catch (e) { 99 | console.error('Error parsing SSE data:', e); 100 | } 101 | } 102 | } 103 | } 104 | } catch (error) { 105 | console.error('Error:', error); 106 | aiTextSpan.textContent = 'Error: Failed to get AI response'; 107 | } 108 | 109 | // Scroll to the bottom of the chat output 110 | output.scrollTop = output.scrollHeight; 111 | } 112 | 113 | /** 114 | * Triggers the send message action by extracting the selected provider and model version from the dropdown. 115 | */ 116 | function triggerSendMessage() { 117 | const modelSelect = document.getElementById('model-select'); 118 | // The value is expected to be in the format "provider:model" 119 | const [provider, model] = modelSelect.value.split(':'); 120 | sendMessage({ provider, model }); 121 | } 122 | 123 | // Add an event listener to send the message when the Enter key is pressed in the textarea 124 | document.getElementById('user-input').addEventListener('keydown', (event) => { 125 | if (event.key === 'Enter' && !event.shiftKey) { 126 | event.preventDefault(); 127 | triggerSendMessage(); 128 | } 129 | }); 130 | 131 | // Add an event listener to the Send button 132 | document.getElementById('send-button').addEventListener('click', () => { 133 | triggerSendMessage(); 134 | }); -------------------------------------------------------------------------------- /example-ai-streaming/service/src/routes/ai.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Sets up a unified AI chat route for the Express application. 3 | * Streams AI responses using Server-Sent Events (SSE) from real providers. 4 | */ 5 | 6 | const OpenAI = require("openai"); 7 | const Anthropic = require("@anthropic-ai/sdk"); 8 | const { AIStreamingError } = require("../utils/errors"); 9 | 10 | /** 11 | * Configures the AI streaming route on the provided Express app instance. 12 | * 13 | * @param {Object} app - Express application instance. 14 | */ 15 | const setupAIRoutes = (app) => { 16 | // Initialize SDK clients for OpenAI and Anthropic using API keys from environment variables. 17 | const openai = new OpenAI({ 18 | apiKey: process.env.OPENAI_API_KEY, 19 | }); 20 | 21 | const anthropic = new Anthropic({ 22 | apiKey: process.env.ANTHROPIC_API_KEY, 23 | }); 24 | 25 | /** 26 | * Handles the POST /api/chat endpoint. 27 | * Streams AI chat completions from the specified provider. 28 | * Expects a JSON payload containing provider, model, and messages. 29 | * 30 | * Example payload: 31 | * { 32 | * "provider": "openai", 33 | * "model": "gpt-4", 34 | * "messages": [{ "role": "user", "content": "Hello" }] 35 | * } 36 | * 37 | * @param {Object} req - Express request object. 38 | * @param {Object} res - Express response object. 39 | * @param {Function} next - Next middleware function. 40 | */ 41 | app.post("/api/chat", async (req, res, next) => { 42 | try { 43 | const { provider, model, messages } = req.body; 44 | if (!provider || !model || !messages) { 45 | res.status(400).json({ error: "Missing required fields: provider, model, or messages" }); 46 | return; 47 | } 48 | 49 | // Set up Server-Sent Events (SSE) headers. 50 | res.setHeader("Content-Type", "text/event-stream"); 51 | res.setHeader("Cache-Control", "no-cache"); 52 | res.setHeader("Connection", "keep-alive"); 53 | 54 | let stream; 55 | if (provider === "openai") { 56 | // Stream chat completions using OpenAI. 57 | stream = await openai.chat.completions.create({ 58 | model, 59 | messages, 60 | stream: true, 61 | }); 62 | } else if (provider === "anthropic") { 63 | // Stream messages using Anthropic. 64 | stream = await anthropic.messages.create({ 65 | model, 66 | messages, 67 | stream: true, 68 | max_tokens: 1024, 69 | }); 70 | } else { 71 | res.status(400).json({ error: "Invalid provider" }); 72 | return; 73 | } 74 | 75 | // Stream the AI response data back to the client. 76 | for await (const chunk of stream) { 77 | let content = ""; 78 | if (provider === "openai") { 79 | content = chunk.choices[0]?.delta?.content || ""; 80 | } else if (provider === "anthropic") { 81 | content = chunk.delta?.text || ""; 82 | } 83 | if (content) { 84 | res.write(`data: ${JSON.stringify({ content })}\n\n`); 85 | } 86 | } 87 | 88 | // Signal end of stream. 89 | res.write("data: [DONE]\n\n"); 90 | res.end(); 91 | } catch (error) { 92 | next(new AIStreamingError(`${req.body.provider} streaming failed`, error)); 93 | } 94 | }); 95 | }; 96 | 97 | module.exports = { setupAIRoutes }; -------------------------------------------------------------------------------- /example-ai-streaming/service/src/utils/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom error class for AI streaming errors 3 | */ 4 | class AIStreamingError extends Error { 5 | /** 6 | * Creates an AI streaming error 7 | * 8 | * @param {string} message - Error message 9 | * @param {Error} originalError - Original error object 10 | */ 11 | constructor(message, originalError) { 12 | super(message); 13 | this.name = "AIStreamingError"; 14 | this.status = 500; 15 | this.code = "ai_streaming_error"; 16 | this.originalError = originalError; 17 | } 18 | } 19 | 20 | module.exports = { AIStreamingError }; -------------------------------------------------------------------------------- /example-apollo-graphql/README.md: -------------------------------------------------------------------------------- 1 | # Apollo GraphQL API Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) streamlines containerized application development and deployment on AWS Lambda and AWS Fargate ECS. 4 | 5 | This example demonstrates building a GraphQL API using Apollo Server with Express integration. It showcases a modern Apollo Server setup complete with a GraphQL schema, resolvers, and a health check endpoint—all deployable via SCF. 6 | 7 | ## Features 8 | 9 | - **Apollo Server & GraphQL:** 10 | Leverages Apollo Server 4 to provide a robust GraphQL API. 11 | - **Express Integration:** 12 | Uses Express middleware for seamless HTTP handling and CORS support. 13 | - **Node.js Application:** 14 | Developed with Node.js using ES module syntax. 15 | - **Flexible Compute Options:** 16 | Easily deployable as an AWS Lambda function or on AWS Fargate ECS with a simple configuration change. 17 | - **Local Development:** 18 | Utilize SCF's development mode for near-production emulation on your local machine. 19 | 20 | ## Prerequisites 21 | 22 | **Docker:** 23 | Docker Desktop is required for container builds and local development. 24 | [Get Docker](https://www.docker.com) 25 | 26 | **Serverless Framework:** 27 | Install globally: 28 | ```bash 29 | npm i -g serverless 30 | ``` 31 | 32 | **Node.js & npm:** 33 | Ensure a recent Node.js LTS version is installed. 34 | 35 | **AWS Credentials:** 36 | Properly configure your AWS credentials (using environment variables or AWS profiles) to enable resource provisioning via SCF. 37 | 38 | ## Configuration 39 | 40 | The SCF configuration is defined in the `serverless.containers.yml` file at the project root: 41 | 42 | ```yaml 43 | name: apollo-graphql 44 | 45 | deployment: 46 | type: awsApi@1.0 47 | 48 | containers: 49 | service: 50 | src: ./service 51 | routing: 52 | pathPattern: /* 53 | pathHealthCheck: /health 54 | environment: 55 | NODE_ENV: production 56 | compute: 57 | type: awsLambda # Can be switched to awsFargateEcs 58 | ``` 59 | 60 | This file sets the project name, deployment type, and container settings (including routing, environment variables, and compute type). 61 | 62 | ## Project Structure 63 | 64 | A typical project structure is as follows: 65 | ``` 66 | example-apollo-graphql/ 67 | ├── serverless.containers.yml # SCF project configuration file 68 | └── service/ 69 | ├── Dockerfile # Multi-stage Dockerfile for Lambda and Fargate 70 | ├── package.json # Node.js project configuration and dependencies 71 | └── src/ 72 | ├── index.js # Main Apollo Server and Express entrypoint 73 | └── (additional files) # GraphQL schema, resolvers, and middleware 74 | ``` 75 | 76 | ## Development 77 | 78 | SCF provides a development mode that mimics the AWS environment: 79 | 80 | ```bash 81 | serverless dev 82 | ``` 83 | 84 | This mode supports hot reloading and simulates API Gateway routing, enabling thorough local testing. 85 | 86 | ## Deployment 87 | 88 | Deploy your GraphQL API to AWS using: 89 | 90 | ```bash 91 | serverless deploy 92 | ``` 93 | 94 | SCF will build the container image, push it to AWS ECR, and provision the necessary AWS resources (ALB, VPC, Lambda or ECS Fargate service). 95 | 96 | ## Cleanup 97 | 98 | For complete infrastructure removal, including shared networking resources: 99 | ```bash 100 | serverless remove --force --all 101 | ``` 102 | 103 | ## Example Queries 104 | 105 | ```graphql 106 | # Health Check 107 | query { 108 | health 109 | } 110 | 111 | # Server Info 112 | query { 113 | info { 114 | namespace 115 | containerName 116 | stage 117 | computeType 118 | local 119 | } 120 | } 121 | ``` 122 | 123 | ## Additional Resources 124 | 125 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 126 | - [Apollo Server Documentation](https://www.apollographql.com/docs/apollo-server/) 127 | - [GraphQL Documentation](https://graphql.org) 128 | - [Express Documentation](https://expressjs.com) -------------------------------------------------------------------------------- /example-apollo-graphql/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: apollo-graphql 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | NODE_ENV: production 14 | compute: 15 | type: awsLambda # Can be switched to awsFargateEcs 16 | -------------------------------------------------------------------------------- /example-apollo-graphql/service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image for both Lambda and Fargate 2 | FROM node:20-alpine as base 3 | 4 | WORKDIR /app 5 | COPY package*.json ./ 6 | RUN npm install 7 | COPY . . 8 | 9 | # Lambda-specific configuration 10 | FROM base as lambda 11 | # Install AWS Lambda Web Adapter 12 | RUN apk add --no-cache curl && \ 13 | curl -Lo /usr/local/bin/aws-lambda-web-adapter \ 14 | https://github.com/awslabs/aws-lambda-web-adapter/releases/latest/download/aws-lambda-web-adapter && \ 15 | chmod +x /usr/local/bin/aws-lambda-web-adapter 16 | 17 | EXPOSE 8080 18 | CMD ["aws-lambda-web-adapter", "--port", "8080", "--", "npm", "start"] 19 | 20 | # Fargate-specific configuration 21 | FROM base as fargate 22 | EXPOSE 8080 23 | CMD ["npm", "start"] 24 | -------------------------------------------------------------------------------- /example-apollo-graphql/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-apollo-graphql", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "start": "node src/index.js" 7 | }, 8 | "dependencies": { 9 | "@apollo/server": "^4.10.0", 10 | "graphql": "^16.8.1", 11 | "express": "^4.18.2", 12 | "cors": "^2.8.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-apollo-graphql/service/src/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from '@apollo/server'; 2 | import { expressMiddleware } from '@apollo/server/express4'; 3 | import express from 'express'; 4 | import cors from 'cors'; 5 | 6 | // Define GraphQL schema 7 | const typeDefs = `#graphql 8 | type ServerInfo { 9 | namespace: String 10 | containerName: String 11 | stage: String 12 | computeType: String 13 | local: String 14 | } 15 | 16 | type Query { 17 | health: String! 18 | info: ServerInfo! 19 | } 20 | `; 21 | 22 | // Define resolvers 23 | const resolvers = { 24 | Query: { 25 | health: () => 'OK', 26 | info: () => ({ 27 | namespace: process.env.SERVERLESS_NAMESPACE, 28 | containerName: process.env.SERVERLESS_CONTAINER_NAME, 29 | stage: process.env.SERVERLESS_STAGE, 30 | computeType: process.env.SERVERLESS_COMPUTE_TYPE, 31 | local: process.env.SERVERLESS_LOCAL 32 | }) 33 | } 34 | }; 35 | 36 | const app = express(); 37 | app.use(cors()); 38 | app.use(express.json()); 39 | 40 | // Health check endpoint (non-GraphQL) 41 | app.get('/health', (req, res) => { 42 | res.send('OK'); 43 | }); 44 | 45 | // Create Apollo Server 46 | const server = new ApolloServer({ 47 | typeDefs, 48 | resolvers, 49 | }); 50 | 51 | // Start server 52 | await server.start(); 53 | 54 | // Apply middleware 55 | app.use('/', expressMiddleware(server)); 56 | 57 | const port = process.env.PORT || 8080; 58 | app.listen(port, '0.0.0.0', () => { 59 | console.log(`Server running at http://localhost:${port}`); 60 | }); 61 | -------------------------------------------------------------------------------- /example-astro-static/README.md: -------------------------------------------------------------------------------- 1 | # Astro Static Site Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy an Astro-based static site that leverages Astro's modern static site generation (with optional SSR) for an optimized production deployment. The application is configured for both AWS Lambda and AWS ECS Fargate deployments using SCF. 6 | 7 | ## Features 8 | 9 | - **Astro Static Site:** 10 | Built with Astro to generate a fast, optimized, and zero-JS default static site. 11 | - **Optional Server-Side Rendering:** 12 | Supports SSR mode through Astro when dynamic rendering is needed. 13 | - **Zero-JS by Default:** 14 | Delivers pure static content with minimal or no client-side JavaScript. 15 | - **Flexible Compute Options:** 16 | Easily switch between AWS Lambda and AWS Fargate ECS deployments via SCF configuration. 17 | - **Optimized Production Builds:** 18 | Utilizes Docker multi-stage builds and production optimizations for efficient deployment. 19 | 20 | ## Prerequisites 21 | 22 | **Docker:** 23 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 24 | 25 | **Serverless Framework:** 26 | Install globally: 27 | ```bash 28 | npm i -g serverless 29 | ``` 30 | 31 | **Node.js & npm:** 32 | Ensure you have a recent Node.js LTS version installed. 33 | 34 | **AWS Credentials:** 35 | Properly configure your AWS credentials (using environment variables or AWS profiles) to allow SCF to provision and update AWS resources. 36 | 37 | ## Configuration 38 | 39 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 40 | 41 | ```yaml 42 | name: astro-static 43 | 44 | deployment: 45 | type: awsApi@1.0 46 | 47 | containers: 48 | service: 49 | src: ./service 50 | routing: 51 | pathPattern: /* 52 | pathHealthCheck: /health 53 | environment: 54 | NODE_ENV: production 55 | ASTRO_TELEMETRY_DISABLED: 1 # Disable Astro telemetry 56 | compute: 57 | type: awsLambda # or awsFargateEcs 58 | ``` 59 | 60 | This configuration sets: 61 | - **Project Namespace:** The project name is used as a namespace in your AWS account. 62 | - **Deployment Settings:** Configures networking (ALB, VPC, API Gateway) via the AWS API deployment type. 63 | - **Container Details:** 64 | - The source code resides in the `./service` directory. 65 | - A catch-all routing rule (`/*`) is used with a dedicated health check endpoint (`/health`). 66 | - Environment variables are set for production and to disable Astro telemetry. 67 | - The default compute type is set to `awsLambda` (switchable to `awsFargateEcs` as needed). 68 | 69 | ## Project Structure 70 | 71 | A typical project structure for this Astro static site example: 72 | ``` 73 | example-astro-static/ 74 | ├── serverless.containers.yml # SCF configuration file 75 | └── service/ 76 | ├── astro.config.mjs # Astro configuration file 77 | ├── package.json # Node.js project configuration and dependencies 78 | ├── public/ # Static assets (images, CSS, etc.) 79 | └── src/ 80 | ├── pages/ # Astro pages (including health check and index) 81 | └── (other directories) # Additional assets or components 82 | ``` 83 | 84 | ## Development 85 | 86 | SCF provides a development mode that emulates AWS routing and compute environments: 87 | ```bash 88 | serverless dev 89 | ``` 90 | 91 | ## Deployment 92 | 93 | Deploy your Astro static site to AWS by running: 94 | ```bash 95 | serverless deploy 96 | ``` 97 | 98 | During deployment, SCF builds the container image (using the multi-stage Dockerfile) and provisions the necessary AWS resources (ALB, VPC, Lambda function, or ECS Fargate service). 99 | 100 | ## Cleanup 101 | 102 | To remove deployed AWS resources when they are no longer needed, run: 103 | ```bash 104 | serverless remove --force --all 105 | ``` 106 | 107 | ## Additional Resources 108 | 109 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 110 | - [Astro Documentation](https://docs.astro.build) 111 | - [Docker Documentation](https://docs.docker.com) 112 | - [AWS Lambda Documentation](https://aws.amazon.com/lambda) 113 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) 114 | -------------------------------------------------------------------------------- /example-astro-static/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: astro-static 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | NODE_ENV: production 14 | ASTRO_TELEMETRY_DISABLED: 1 # Disable Astro telemetry 15 | compute: 16 | type: awsLambda # Can be switched to awsFargateEcs -------------------------------------------------------------------------------- /example-astro-static/service/.astro/content-assets.mjs: -------------------------------------------------------------------------------- 1 | export default new Map(); -------------------------------------------------------------------------------- /example-astro-static/service/.astro/content-modules.mjs: -------------------------------------------------------------------------------- 1 | export default new Map(); -------------------------------------------------------------------------------- /example-astro-static/service/.astro/content.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'astro:content' { 2 | export interface RenderResult { 3 | Content: import('astro/runtime/server/index.js').AstroComponentFactory; 4 | headings: import('astro').MarkdownHeading[]; 5 | remarkPluginFrontmatter: Record; 6 | } 7 | interface Render { 8 | '.md': Promise; 9 | } 10 | 11 | export interface RenderedContent { 12 | html: string; 13 | metadata?: { 14 | imagePaths: Array; 15 | [key: string]: unknown; 16 | }; 17 | } 18 | } 19 | 20 | declare module 'astro:content' { 21 | type Flatten = T extends { [K: string]: infer U } ? U : never; 22 | 23 | export type CollectionKey = keyof AnyEntryMap; 24 | export type CollectionEntry = Flatten; 25 | 26 | export type ContentCollectionKey = keyof ContentEntryMap; 27 | export type DataCollectionKey = keyof DataEntryMap; 28 | 29 | type AllValuesOf = T extends any ? T[keyof T] : never; 30 | type ValidContentEntrySlug = AllValuesOf< 31 | ContentEntryMap[C] 32 | >['slug']; 33 | 34 | export type ReferenceDataEntry< 35 | C extends CollectionKey, 36 | E extends keyof DataEntryMap[C] = string, 37 | > = { 38 | collection: C; 39 | id: E; 40 | }; 41 | export type ReferenceContentEntry< 42 | C extends keyof ContentEntryMap, 43 | E extends ValidContentEntrySlug | (string & {}) = string, 44 | > = { 45 | collection: C; 46 | slug: E; 47 | }; 48 | 49 | /** @deprecated Use `getEntry` instead. */ 50 | export function getEntryBySlug< 51 | C extends keyof ContentEntryMap, 52 | E extends ValidContentEntrySlug | (string & {}), 53 | >( 54 | collection: C, 55 | // Note that this has to accept a regular string too, for SSR 56 | entrySlug: E, 57 | ): E extends ValidContentEntrySlug 58 | ? Promise> 59 | : Promise | undefined>; 60 | 61 | /** @deprecated Use `getEntry` instead. */ 62 | export function getDataEntryById( 63 | collection: C, 64 | entryId: E, 65 | ): Promise>; 66 | 67 | export function getCollection>( 68 | collection: C, 69 | filter?: (entry: CollectionEntry) => entry is E, 70 | ): Promise; 71 | export function getCollection( 72 | collection: C, 73 | filter?: (entry: CollectionEntry) => unknown, 74 | ): Promise[]>; 75 | 76 | export function getEntry< 77 | C extends keyof ContentEntryMap, 78 | E extends ValidContentEntrySlug | (string & {}), 79 | >( 80 | entry: ReferenceContentEntry, 81 | ): E extends ValidContentEntrySlug 82 | ? Promise> 83 | : Promise | undefined>; 84 | export function getEntry< 85 | C extends keyof DataEntryMap, 86 | E extends keyof DataEntryMap[C] | (string & {}), 87 | >( 88 | entry: ReferenceDataEntry, 89 | ): E extends keyof DataEntryMap[C] 90 | ? Promise 91 | : Promise | undefined>; 92 | export function getEntry< 93 | C extends keyof ContentEntryMap, 94 | E extends ValidContentEntrySlug | (string & {}), 95 | >( 96 | collection: C, 97 | slug: E, 98 | ): E extends ValidContentEntrySlug 99 | ? Promise> 100 | : Promise | undefined>; 101 | export function getEntry< 102 | C extends keyof DataEntryMap, 103 | E extends keyof DataEntryMap[C] | (string & {}), 104 | >( 105 | collection: C, 106 | id: E, 107 | ): E extends keyof DataEntryMap[C] 108 | ? string extends keyof DataEntryMap[C] 109 | ? Promise | undefined 110 | : Promise 111 | : Promise | undefined>; 112 | 113 | /** Resolve an array of entry references from the same collection */ 114 | export function getEntries( 115 | entries: ReferenceContentEntry>[], 116 | ): Promise[]>; 117 | export function getEntries( 118 | entries: ReferenceDataEntry[], 119 | ): Promise[]>; 120 | 121 | export function render( 122 | entry: AnyEntryMap[C][string], 123 | ): Promise; 124 | 125 | export function reference( 126 | collection: C, 127 | ): import('astro/zod').ZodEffects< 128 | import('astro/zod').ZodString, 129 | C extends keyof ContentEntryMap 130 | ? ReferenceContentEntry> 131 | : ReferenceDataEntry 132 | >; 133 | // Allow generic `string` to avoid excessive type errors in the config 134 | // if `dev` is not running to update as you edit. 135 | // Invalid collection names will be caught at build time. 136 | export function reference( 137 | collection: C, 138 | ): import('astro/zod').ZodEffects; 139 | 140 | type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; 141 | type InferEntrySchema = import('astro/zod').infer< 142 | ReturnTypeOrOriginal['schema']> 143 | >; 144 | 145 | type ContentEntryMap = { 146 | 147 | }; 148 | 149 | type DataEntryMap = { 150 | 151 | }; 152 | 153 | type AnyEntryMap = ContentEntryMap & DataEntryMap; 154 | 155 | export type ContentConfig = typeof import("../src/content.config.mjs"); 156 | } 157 | -------------------------------------------------------------------------------- /example-astro-static/service/.astro/data-store.json: -------------------------------------------------------------------------------- 1 | [["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.3.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":true,\"port\":8080,\"streaming\":true},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[]},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":\"shiki\",\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"responsiveImages\":false,\"serializeConfig\":false},\"legacy\":{\"collections\":false}}"] -------------------------------------------------------------------------------- /example-astro-static/service/.astro/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "_variables": { 3 | "lastUpdateCheck": 1740157149603 4 | } 5 | } -------------------------------------------------------------------------------- /example-astro-static/service/.astro/types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example-astro-static/service/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import node from '@astrojs/node'; 3 | 4 | /** 5 | * Astro configuration for Serverless Container Framework. 6 | * 7 | * @module AstroConfig 8 | */ 9 | export default defineConfig({ 10 | output: 'server', 11 | adapter: node({ 12 | // Use 'standalone' mode so that the server code is bundled 13 | mode: 'standalone' 14 | }), 15 | server: { 16 | port: 8080, 17 | host: true 18 | }, 19 | vite: { 20 | server: { 21 | allowedHosts: ['service'] // Named after your container name 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /example-astro-static/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-astro-static", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro preview", 8 | "build": "astro build" 9 | }, 10 | "dependencies": { 11 | "astro": "^5.3.0", 12 | "@astrojs/node": "^9.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-astro-static/service/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | height: 100%; 11 | font-family: "Roboto Mono", monospace; 12 | font-optical-sizing: auto; 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | img { 18 | -webkit-user-drag: none; 19 | } 20 | 21 | body { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 26 | background: #000000; 27 | color: #ffffff; 28 | } 29 | 30 | body::after { 31 | content: ""; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-image: url("/images/background.png"); 38 | background-size: cover; 39 | background-repeat: no-repeat; 40 | background-position: center; 41 | background-attachment: fixed; 42 | opacity: 0; 43 | animation: fadeIn 0.75s ease-in forwards; 44 | z-index: -1; /* Places background behind content */ 45 | } 46 | 47 | a { 48 | color: #fd5750; 49 | text-decoration: none; 50 | } 51 | 52 | h1 { 53 | color: white; 54 | font-size: 2rem; 55 | font-weight: 500; 56 | text-align: center; 57 | margin: 20px 0; 58 | } 59 | 60 | .container { 61 | display: flex; 62 | flex-direction: column; 63 | align-items: center; 64 | width: 90%; /* Fluid width */ 65 | max-width: 1200px; /* Sets a max width for larger screens */ 66 | padding: 20px; 67 | text-align: center; 68 | } 69 | 70 | .logo { 71 | max-width: 400px; /* Controls logo size for responsiveness */ 72 | width: 100%; 73 | height: auto; 74 | margin-bottom: 40px; 75 | opacity: 0; 76 | transform: scale(0.75); /* Start scaled at 75% */ 77 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 0.75s duration, 0.35s delay */ 78 | } 79 | 80 | .info { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | font-size: 1.2rem; 85 | color: rgba(255, 255, 255, 0.7); 86 | margin-top: 5px; 87 | opacity: 0; 88 | animation: fadeIn 2s ease-in-out 1.5s forwards; 89 | } 90 | 91 | /** 92 | * Animations 93 | */ 94 | @keyframes fadeIn { 95 | from { 96 | opacity: 0; 97 | } 98 | to { 99 | opacity: 1; 100 | } 101 | } 102 | 103 | @keyframes fadeInZoom { 104 | from { 105 | opacity: 0; 106 | transform: scale(0.85); /* Start at 85% */ 107 | } 108 | to { 109 | opacity: 1; 110 | transform: scale(1); /* End at 100% */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /example-astro-static/service/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-astro-static/service/public/images/background.png -------------------------------------------------------------------------------- /example-astro-static/service/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-astro-static/service/public/images/favicon.png -------------------------------------------------------------------------------- /example-astro-static/service/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-astro-static/service/public/images/logo.png -------------------------------------------------------------------------------- /example-astro-static/service/src/pages/health.ts: -------------------------------------------------------------------------------- 1 | export async function GET() { 2 | return new Response('OK'); 3 | } 4 | -------------------------------------------------------------------------------- /example-astro-static/service/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | /** 3 | * Astro page for Serverless Container Framework. 4 | * 5 | * This page renders the main HTML content matching the Express version. 6 | * It displays environment variables and static resources (images, CSS, etc.). 7 | * 8 | * @module IndexPage 9 | */ 10 | const env = { 11 | namespace: process.env.SERVERLESS_NAMESPACE, 12 | containerName: process.env.SERVERLESS_CONTAINER_NAME, 13 | stage: process.env.SERVERLESS_STAGE, 14 | computeType: process.env.SERVERLESS_COMPUTE_TYPE, 15 | local: process.env.SERVERLESS_LOCAL 16 | }; 17 | --- 18 | 19 | 20 | 21 | 22 | Serverless Container Framework 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
Namespace: {env.namespace}
36 |
Container Name: {env.containerName}
37 |
Stage: {env.stage}
38 |
Compute Type: {env.computeType}
39 |
Local: {env.local}
40 |
41 | 42 | -------------------------------------------------------------------------------- /example-deno/README.md: -------------------------------------------------------------------------------- 1 | # Deno V2 Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) is a tool that simplifies the development and deployment of containerized applications to AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates the development and deployment of a simple, performant web application built with Deno V2, the Oak framework, and the Serverless Container Framework. 6 | 7 | ## Features 8 | 9 | - **Deno V2 Support:** Utilizes the official Deno V2 image to run your Deno application. 10 | - **Oak Framework:** Utilizes the [Oak framework](https://deno.land/x/oak), a middleware framework for handling HTTP requests. 11 | - **TypeScript:** Uses TypeScript for the application codebase. 12 | - **Compute Flexibility:** Configure the container's compute type to run either as an AWS Lambda function (`awsLambda`) or on AWS Fargate ECS (`awsFargateEcs`). A multi-stage Dockerfile is provided to support these configurations. 13 | - **Local Development:** SCF includes a rich development mode that emulates AWS ALB routing, AWS Lambda, and AWS Fargate ECS locally, allowing you to develop and test your Deno application with near-production parity. 14 | 15 | ## Prerequisites 16 | 17 | **Docker:** Install and start Docker Desktop, as it is required. Get it [here](https://www.docker.com). 18 | 19 | **Serverless Framework:** Serverless Container Framework is a feature of the Serverless Framework. 20 | 21 | ``` 22 | npm i -g serverless 23 | ``` 24 | 25 | **AWS Credentials:** Properly configure your AWS credentials (via environment variables or AWS profiles) to allow SCF to provision and update AWS resources. These are required to use the Serverless Container Framework. 26 | 27 | ## Configuration 28 | 29 | At the root of the example, a `serverless.containers.yml` file defines the project configuration: 30 | 31 | ```yaml 32 | name: deno 33 | 34 | deployment: 35 | type: awsApi@1.0 36 | 37 | containers: 38 | service: 39 | src: ./service 40 | routing: 41 | pathPattern: /* 42 | pathHealthCheck: /health 43 | environment: 44 | HELLO: world 45 | compute: 46 | type: awsLambda # Can be switched to awsFargateEcs 47 | build: 48 | options: 49 | - --target=awsLambda # Ensure you match the compute type set above. Sets the target build stage for the Dockerfile. 50 | ``` 51 | 52 | This file specifies: 53 | - **Namespace:** The project name is `deno`, which is used as a prefix to namespace resources in your AWS account. 54 | - **Deployment Type:** Uses the AWS API deployment type to configure networking (ALB, VPC, etc.). 55 | - **Container Details:** 56 | - The source code is in the `./service` directory. 57 | - Routing rules specify a catch-all route (`/*`) with a defined health check endpoint (`/health`). 58 | - An environment variable (`HELLO`) is provided. 59 | - The default compute type is set to `awsLambda` but can be switched to `awsFargateEcs` as needed. 60 | 61 | Learn more about Configuration in the [Serverless Container Framework Documentation](https://serverless.com/containers/docs/configuration). 62 | 63 | ``` 64 | example-deno/ 65 | ├── serverless.containers.yml # SCF project configuration file 66 | └── service/ 67 | ├── Dockerfile # Multi-stage Dockerfile for AWS Lambda and Fargate builds 68 | ├── deno.json # Deno configuration and task definitions 69 | ├── deno.lock # Deno lock file 70 | └── src/ 71 | ├── index.ts # Main application entrypoint (uses Oak and oakCors) 72 | └── public/ 73 | └── css/ 74 | └── styles.css # Static assets (CSS, images, etc.) 75 | ``` 76 | 77 | ## Development 78 | 79 | SCF includes a rich development mode that emulates AWS ALB routing, AWS Lambda, and AWS Fargate ECS locally. This mode allows you to test routing, static asset delivery, and health check endpoints while running your container with Deno, which proxies requests on port `8080`. 80 | 81 | Run the following command to start local development mode: 82 | 83 | ```bash 84 | serverless dev 85 | ``` 86 | 87 | This command watches for changes and rebuilds the container as needed. 88 | 89 | Learn more about Development in the [Serverless Container Framework Documentation](https://serverless.com/containers/docs/development). 90 | 91 | ## Deployment 92 | 93 | To deploy this example with SCF, open your terminal in the `example-deno` directory. 94 | 95 | Execute the following command to deploy your container to AWS: 96 | 97 | ```bash 98 | serverless deploy 99 | ``` 100 | 101 | This command builds the Deno container image using the provided Dockerfile and provisions AWS resources (ALB, VPC, Lambda function, or ECS Fargate service). 102 | 103 | If you switch compute types, ensure that you set the build `--target` option to the corresponding compute type, since the Dockerfile declares multiple build configurations. 104 | 105 | Once deployed, SCF will output the URLs and endpoints (such as the ALB endpoint) for your application. 106 | 107 | Access the application via the provided URL to see your Deno app live. 108 | 109 | To remove the deployed containers, run: 110 | 111 | ```bash 112 | serverless remove 113 | ``` 114 | 115 | To remove all AWS resources, including shared infrastructure, use: 116 | 117 | ```bash 118 | serverless remove --force --all 119 | ``` 120 | 121 | Learn more about Deployment in the [Serverless Container Framework Documentation](https://serverless.com/containers/docs/deployment). 122 | 123 | ## Additional Resources 124 | 125 | * [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 126 | * [Official Deno Website](https://deno.land/) 127 | -------------------------------------------------------------------------------- /example-deno/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: deno 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | type: awsLambda # Can be switched to awsFargateEcs 16 | build: 17 | options: 18 | - --target=awsLambda # Ensure you match the compute type set above 19 | -------------------------------------------------------------------------------- /example-deno/service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image for both Lambda and Fargate 2 | FROM denoland/deno:debian-2.0.0 AS base 3 | WORKDIR /app 4 | COPY . . 5 | # Generate lock file and cache all dependencies 6 | ENV DENO_DIR=/app/deno_dir 7 | RUN mkdir -p /app/deno_dir && \ 8 | deno cache --lock=deno.lock src/index.ts 9 | 10 | # Lambda-specific configuration 11 | FROM debian:bookworm-slim AS awsLambda 12 | # Install Deno binary only 13 | COPY --from=denoland/deno:bin-2.0.0 /deno /usr/local/bin/deno 14 | # Copy AWS Lambda Web Adapter 15 | COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter 16 | WORKDIR /app 17 | # Copy app and pre-cached dependencies 18 | COPY --from=base /app . 19 | # Set DENO_DIR for runtime (point to pre-cached directory) 20 | ENV DENO_DIR=/app/deno_dir 21 | # Set port for Lambda Web Adapter 22 | ENV PORT=8080 23 | EXPOSE 8080 24 | # Warm up (optional, shorter timeout to precompile without fetching) 25 | RUN timeout 5s deno run --allow-net --allow-env --allow-read --lock=deno.lock src/index.ts || [ $? -eq 124 ] || exit 1 26 | CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "--lock=deno.lock", "src/index.ts"] 27 | 28 | # Fargate-specific configuration 29 | FROM base AS awsFargateEcs 30 | EXPOSE 8080 31 | CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "src/index.ts"] -------------------------------------------------------------------------------- /example-deno/service/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "oak": "https://deno.land/x/oak@v12.6.2/mod.ts", 4 | "oak/": "https://deno.land/x/oak@v12.6.2/", 5 | "cors": "https://deno.land/x/cors@v1.2.2/mod.ts" 6 | }, 7 | "tasks": { 8 | "start": "deno run --allow-net --allow-env --allow-read src/index.ts" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example-deno/service/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Application, Router } from "oak"; 2 | import { oakCors } from "cors"; 3 | 4 | const app = new Application(); 5 | const router = new Router(); 6 | 7 | /** 8 | * Middleware to enable CORS. 9 | */ 10 | app.use(oakCors()); 11 | 12 | /** 13 | * Middleware to add custom headers. 14 | */ 15 | app.use(async (ctx, next) => { 16 | ctx.response.headers.set("x-powered-by", "serverless-container-framework"); 17 | await next(); 18 | }); 19 | 20 | // Define routes using router 21 | 22 | /** 23 | * Health check endpoint. 24 | */ 25 | router.get("/health", (ctx) => { 26 | ctx.response.body = "OK"; 27 | }); 28 | 29 | /** 30 | * Robots.txt endpoint. 31 | */ 32 | router.get("/robots.txt", (ctx) => { 33 | ctx.response.type = "text/plain"; 34 | ctx.response.body = "User-agent: *"; 35 | }); 36 | 37 | /** 38 | * Default route endpoint. 39 | * 40 | * This route returns the main HTML page that matches the Express app's content. 41 | */ 42 | router.get("/", (ctx) => { 43 | ctx.response.type = "text/html"; 44 | ctx.response.body = ` 45 | 46 | 47 | Serverless Container Framework 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
Namespace: ${Deno.env.get("SERVERLESS_NAMESPACE")}
58 |
Container Name: ${Deno.env.get("SERVERLESS_CONTAINER_NAME")}
59 |
Stage: ${Deno.env.get("SERVERLESS_STAGE")}
60 |
Compute Type: ${Deno.env.get("SERVERLESS_COMPUTE_TYPE")}
61 |
Local: ${Deno.env.get("SERVERLESS_LOCAL")}
62 |
63 | 64 | 65 | `; 66 | }); 67 | 68 | // Register router middlewares 69 | app.use(router.routes()); 70 | app.use(router.allowedMethods()); 71 | 72 | /** 73 | * Middleware to serve static files. 74 | */ 75 | app.use(async (ctx, next) => { 76 | try { 77 | await ctx.send({ 78 | root: `${Deno.cwd()}/src/public`, 79 | index: "index.html" 80 | }); 81 | } catch { 82 | await next(); 83 | } 84 | }); 85 | 86 | /** 87 | * 404 Fallback handler. 88 | * 89 | * If no route (or static file) matches the request, this middleware returns a 404 page 90 | * that matches the Express app's HTML content. 91 | */ 92 | app.use((ctx) => { 93 | ctx.response.status = 404; 94 | ctx.response.type = "text/html"; 95 | ctx.response.body = ` 96 | 97 | 98 | 404 - Page Not Found 99 | 100 | 101 | 104 | 105 | 106 | 107 | 108 |
109 |

404 - Page Not Found

110 | Return to Home 111 |
112 | 113 | 114 | `; 115 | }); 116 | 117 | const port = 8080; 118 | console.log(`Server running at http://localhost:${port}`); 119 | await app.listen({ port }); 120 | -------------------------------------------------------------------------------- /example-deno/service/src/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | height: 100%; 11 | font-family: "Roboto Mono", monospace; 12 | font-optical-sizing: auto; 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | img { 18 | -webkit-user-drag: none; 19 | } 20 | 21 | body { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 26 | background: #000000; 27 | color: #ffffff; 28 | } 29 | 30 | body::after { 31 | content: ""; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-image: url("/images/background.png"); 38 | background-size: cover; 39 | background-repeat: no-repeat; 40 | background-position: center; 41 | background-attachment: fixed; 42 | opacity: 0; 43 | animation: fadeIn 0.75s ease-in forwards; 44 | z-index: -1; /* Places background behind content */ 45 | } 46 | 47 | a { 48 | color: #fd5750; 49 | text-decoration: none; 50 | } 51 | 52 | h1 { 53 | color: white; 54 | font-size: 2rem; 55 | font-weight: 500; 56 | text-align: center; 57 | margin: 20px 0; 58 | } 59 | 60 | .container { 61 | display: flex; 62 | flex-direction: column; 63 | align-items: center; 64 | width: 90%; /* Fluid width */ 65 | max-width: 1200px; /* Sets a max width for larger screens */ 66 | padding: 20px; 67 | text-align: center; 68 | } 69 | 70 | .logo { 71 | max-width: 400px; /* Controls logo size for responsiveness */ 72 | width: 100%; 73 | height: auto; 74 | margin-bottom: 40px; 75 | opacity: 0; 76 | transform: scale(0.75); /* Start scaled at 75% */ 77 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 0.75s duration, 0.35s delay */ 78 | } 79 | 80 | .info { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | font-size: 1.2rem; 85 | color: rgba(255, 255, 255, 0.7); 86 | margin-top: 5px; 87 | opacity: 0; 88 | animation: fadeIn 2s ease-in-out 1.5s forwards; 89 | } 90 | 91 | /** 92 | * Animations 93 | */ 94 | @keyframes fadeIn { 95 | from { 96 | opacity: 0; 97 | } 98 | to { 99 | opacity: 1; 100 | } 101 | } 102 | 103 | @keyframes fadeInZoom { 104 | from { 105 | opacity: 0; 106 | transform: scale(0.85); /* Start at 85% */ 107 | } 108 | to { 109 | opacity: 1; 110 | transform: scale(1); /* End at 100% */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /example-deno/service/src/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-deno/service/src/public/images/background.png -------------------------------------------------------------------------------- /example-deno/service/src/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-deno/service/src/public/images/favicon.png -------------------------------------------------------------------------------- /example-deno/service/src/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-deno/service/src/public/images/logo.png -------------------------------------------------------------------------------- /example-express/README.md: -------------------------------------------------------------------------------- 1 | # Express Application Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy a simple Express-based web application using SCF. The application sets up basic routes—including a health check, static file delivery, and a fallback 404 page—with minimal configuration. 6 | 7 | ## Features 8 | 9 | - **Express Framework:** 10 | Leverages Express for routing and middleware handling. 11 | - **Static File Serving:** 12 | Serves static assets from a dedicated public directory. 13 | - **Health Check Endpoint:** 14 | Provides a simple `/health` route for monitoring application health. 15 | - **Flexible Compute Options:** 16 | Easily switch between AWS Lambda and AWS Fargate ECS deployments via SCF configuration. 17 | 18 | ## Prerequisites 19 | 20 | **Docker:** 21 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 22 | 23 | **Serverless Framework:** 24 | Install globally: 25 | ```bash 26 | npm i -g serverless 27 | ``` 28 | 29 | **Node.js & npm:** 30 | Ensure you have a recent Node.js LTS version installed. 31 | 32 | **AWS Credentials:** 33 | Properly configure your AWS credentials (via environment variables or AWS profiles) to allow SCF to provision and update AWS resources. 34 | 35 | ## Configuration 36 | 37 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 38 | 39 | ```yaml 40 | name: express 41 | 42 | deployment: 43 | type: awsApi@1.0 44 | 45 | containers: 46 | service: 47 | src: ./service 48 | routing: 49 | pathPattern: /* 50 | pathHealthCheck: /health 51 | environment: 52 | HELLO: world 53 | compute: 54 | type: awsLambda # or awsFargateEcs 55 | ``` 56 | 57 | This configuration sets: 58 | - **Project Namespace:** The project name (express) is used as a namespace in your AWS account. 59 | - **Deployment Settings:** Configures networking (ALB, VPC, API Gateway) via the AWS API deployment type. 60 | - **Container Details:** 61 | - The source code is located in the `./service` directory. 62 | - A catch-all routing rule (`/*`) is used with a dedicated health check endpoint (`/health`). 63 | - An environment variable (`HELLO`) is provided. 64 | - The compute type is set to `awsLambda` by default (switchable to `awsFargateEcs` as needed). 65 | 66 | ## Project Structure 67 | 68 | A typical project structure for this Express example: 69 | ``` 70 | example-express/ 71 | ├── serverless.containers.yml # SCF configuration file 72 | └── service/ 73 | ├── package.json # Node.js project configuration and dependencies 74 | └── src/ 75 | ├── index.js # Main Express application entrypoint 76 | └── public/ # Static assets (HTML, CSS, images, etc.) 77 | ``` 78 | 79 | ## Development 80 | 81 | For local development, use Serverless Container Framework's development mode: 82 | ```bash 83 | serverless dev 84 | ``` 85 | 86 | This will automatically start everything and set up hot reloading. 87 | 88 | ## Deployment 89 | 90 | Deploy your Express application to AWS by running: 91 | ```bash 92 | serverless deploy 93 | ``` 94 | 95 | During deployment, SCF builds the container image (using the provided multi-stage Dockerfile) and provisions the necessary AWS resources (ALB, VPC, Lambda function or ECS Fargate service). 96 | 97 | ## Cleanup 98 | 99 | To remove deployed AWS resources when they are no longer needed: 100 | ```bash 101 | serverless remove --force --all 102 | ``` 103 | 104 | ## Additional Resources 105 | 106 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 107 | - [Express Documentation](https://expressjs.com) 108 | - [Docker Documentation](https://docs.docker.com) 109 | - [AWS Lambda Documentation](https://aws.amazon.com/lambda) 110 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) -------------------------------------------------------------------------------- /example-express/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: express 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | type: awsLambda # or awsFargateEcs 16 | # awsIam: 17 | # customPolicy: 18 | # Version: "2012-10-17" 19 | # Statement: 20 | # - Effect: Allow 21 | # Action: 22 | # - dynamodb:* 23 | # Resource: 24 | # - "*" 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example-express/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.21.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-express/service/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const path = require("path"); 3 | const app = express(); 4 | const port = 8080; 5 | 6 | /** 7 | * Midddleware 8 | */ 9 | 10 | // Serve static files from the "public" directory 11 | app.use(express.static(path.join(__dirname, "public"), { redirect: false })); 12 | 13 | app.use((req, res, next) => { 14 | // Enable CORS 15 | res.header("Access-Control-Allow-Origin", "*"); 16 | res.header("Access-Control-Allow-Methods", "*"); 17 | res.header("Access-Control-Allow-Headers", "*"); 18 | res.header("x-powered-by", "serverless-container-framework"); 19 | next(); 20 | }); 21 | 22 | // Enable error handling of promises 23 | const asyncHandler = (fn) => (req, res, next) => { 24 | return Promise.resolve(fn(req, res, next)).catch(next); 25 | }; 26 | 27 | /** 28 | * Routes 29 | */ 30 | 31 | // Robots.txt 32 | app.get("/robots.txt", (req, res) => { 33 | res.type("text/plain"); 34 | res.send(`User-agent: *`); 35 | }); 36 | 37 | app.options(`*`, (req, res) => { 38 | res.status(200).send(); 39 | }); 40 | 41 | // Healthcheck 42 | app.get(`/health`, (req, res) => { 43 | res.status(200).send(`OK`); 44 | }); 45 | 46 | // Default 47 | app.get( 48 | `/*`, 49 | asyncHandler((req, res) => { 50 | res.send(` 51 | 52 | 53 | Serverless Container Framework 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 |
Namespace: ${process.env.SERVERLESS_NAMESPACE}
64 |
Container Name: ${process.env.SERVERLESS_CONTAINER_NAME}
65 |
Stage: ${process.env.SERVERLESS_STAGE}
66 |
Compute Type: ${process.env.SERVERLESS_COMPUTE_TYPE}
67 |
Local: ${process.env.SERVERLESS_LOCAL}
68 |
69 | 70 | 71 | `); 72 | }) 73 | ); 74 | 75 | // Catch-all 404 - for any unmatched paths 76 | app.use((req, res) => { 77 | res.status(404).send(` 78 | 79 | 80 | 404 - Page Not Found 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |

404 - Page Not Found

90 | Return to Home 91 |
92 | 93 | 94 | `); 95 | }); 96 | 97 | /** 98 | * Error Handler 99 | */ 100 | app.use((err, req, res, next) => { 101 | console.log(err); 102 | res.status(err.status || 500).json({ 103 | error: err.message || "Internal Server Error", 104 | message: err.message || "Internal Server Error", 105 | code: err.code || "internal_error", 106 | status: err.status, 107 | // stack: err.stack - Don't include stack trace 108 | }); 109 | }); 110 | 111 | app.listen(port, "0.0.0.0", () => { 112 | console.log(`App initialized`); 113 | }); 114 | -------------------------------------------------------------------------------- /example-express/service/src/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | height: 100%; 11 | font-family: "Roboto Mono", monospace; 12 | font-optical-sizing: auto; 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | img { 18 | -webkit-user-drag: none; 19 | } 20 | 21 | body { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 26 | background: #000000; 27 | color: #ffffff; 28 | } 29 | 30 | body::after { 31 | content: ""; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-image: url("/images/background.png"); 38 | background-size: cover; 39 | background-repeat: no-repeat; 40 | background-position: center; 41 | background-attachment: fixed; 42 | opacity: 0; 43 | animation: fadeIn 0.75s ease-in forwards; 44 | z-index: -1; /* Places background behind content */ 45 | } 46 | 47 | a { 48 | color: #fd5750; 49 | text-decoration: none; 50 | } 51 | 52 | h1 { 53 | color: white; 54 | font-size: 2rem; 55 | font-weight: 500; 56 | text-align: center; 57 | margin: 20px 0; 58 | } 59 | 60 | .container { 61 | display: flex; 62 | flex-direction: column; 63 | align-items: center; 64 | width: 90%; /* Fluid width */ 65 | max-width: 1200px; /* Sets a max width for larger screens */ 66 | padding: 20px; 67 | text-align: center; 68 | } 69 | 70 | .logo { 71 | max-width: 400px; /* Controls logo size for responsiveness */ 72 | width: 100%; 73 | height: auto; 74 | margin-bottom: 40px; 75 | opacity: 0; 76 | transform: scale(0.75); /* Start scaled at 75% */ 77 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 0.75s duration, 0.35s delay */ 78 | } 79 | 80 | .info { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | font-size: 1.2rem; 85 | color: rgba(255, 255, 255, 0.7); 86 | margin-top: 5px; 87 | opacity: 0; 88 | animation: fadeIn 2s ease-in-out 1.5s forwards; 89 | } 90 | 91 | /** 92 | * Animations 93 | */ 94 | @keyframes fadeIn { 95 | from { 96 | opacity: 0; 97 | } 98 | to { 99 | opacity: 1; 100 | } 101 | } 102 | 103 | @keyframes fadeInZoom { 104 | from { 105 | opacity: 0; 106 | transform: scale(0.85); /* Start at 85% */ 107 | } 108 | to { 109 | opacity: 1; 110 | transform: scale(1); /* End at 100% */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /example-express/service/src/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-express/service/src/public/images/background.png -------------------------------------------------------------------------------- /example-express/service/src/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-express/service/src/public/images/favicon.png -------------------------------------------------------------------------------- /example-express/service/src/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-express/service/src/public/images/logo.png -------------------------------------------------------------------------------- /example-hono/README.md: -------------------------------------------------------------------------------- 1 | # Hono Application Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy a Hono-based web application using SCF. Hono is a lightweight framework for building fast HTTP services. The application sets up basic routes—including static file delivery, a health check, and a fallback 404 page—and can be deployed on AWS Lambda or AWS Fargate ECS with minimal configuration. 6 | 7 | ## Features 8 | 9 | - **Hono Framework:** 10 | Leverages Hono for a fast and minimalistic HTTP service. 11 | - **Static File Serving:** 12 | Serves static content from a dedicated public directory. 13 | - **Health Check Endpoint:** 14 | Provides a reliable `/health` route to verify application status. 15 | - **Flexible Compute Options:** 16 | Easily deployable as an AWS Lambda function or on AWS Fargate ECS. 17 | - **Lightweight & Efficient:** 18 | Designed for minimal overhead and optimal performance in a containerized environment. 19 | 20 | ## Prerequisites 21 | 22 | **Docker:** 23 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 24 | 25 | **Serverless Framework:** 26 | Install globally: 27 | ```bash 28 | npm i -g serverless 29 | ``` 30 | 31 | **Node.js & npm:** 32 | Ensure you have a recent Node.js LTS version installed. 33 | 34 | **AWS Credentials:** 35 | Properly configure your AWS credentials (via environment variables or AWS profiles) to enable SCF to provision and update AWS resources. 36 | 37 | ## Configuration 38 | 39 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 40 | 41 | ```yaml 42 | name: hono 43 | 44 | deployment: 45 | type: awsApi@1.0 46 | 47 | containers: 48 | service: 49 | src: ./service 50 | routing: 51 | pathPattern: /* 52 | pathHealthCheck: /health 53 | environment: 54 | HELLO: world 55 | compute: 56 | type: awsLambda # or awsFargateEcs 57 | ``` 58 | 59 | This configuration sets: 60 | - **Project Namespace:** The project name (hono) is used to namespace resources in your AWS account. 61 | - **Deployment Settings:** Configures networking via the AWS API deployment type. 62 | - **Container Details:** 63 | - The source code is located in the `./service` directory. 64 | - A catch-all routing rule (`/*`) is used with a designated health check endpoint (`/health`). 65 | - An environment variable (`HELLO`) is provided. 66 | - The compute type is set to `awsLambda` by default (or can be switched to `awsFargateEcs`). 67 | 68 | ## Project Structure 69 | 70 | A typical project structure for this Hono example: 71 | ``` 72 | example-hono/ 73 | ├── serverless.containers.yml # SCF configuration file 74 | └── service/ 75 | ├── package.json # Node.js project configuration and dependencies 76 | └── src/ 77 | ├── index.js # Main Hono application entrypoint 78 | └── public/ # Static assets (HTML, CSS, images, etc.) 79 | ``` 80 | 81 | ## Development 82 | 83 | For local development, you can run the Hono application using SCF's development mode which emulates AWS routing and compute environments: 84 | ```bash 85 | serverless dev 86 | ``` 87 | 88 | This will automatically start everything and set up hot reloading. 89 | 90 | ## Deployment 91 | 92 | Deploy your Hono application to AWS using: 93 | ```bash 94 | serverless deploy 95 | ``` 96 | 97 | During deployment, SCF builds the container image (using the provided multi-stage Dockerfile) and provisions the necessary AWS resources (ALB, VPC, Lambda function, or ECS Fargate service). 98 | 99 | ## Cleanup 100 | 101 | To remove deployed AWS resources when they are no longer needed, run: 102 | ```bash 103 | serverless remove --force --all 104 | ``` 105 | 106 | ## Additional Resources 107 | 108 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 109 | - [Hono Documentation](https://hono.dev) 110 | - [Docker Documentation](https://docs.docker.com) 111 | - [AWS Lambda Documentation](https://aws.amazon.com/lambda) 112 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) -------------------------------------------------------------------------------- /example-hono/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: hono 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | type: awsLambda # or awsFargateEcs 16 | # awsIam: 17 | # customPolicy: 18 | # Version: "2012-10-17" 19 | # Statement: 20 | # - Effect: Allow 21 | # Action: 22 | # - dynamodb:* 23 | # Resource: 24 | # - "*" 25 | -------------------------------------------------------------------------------- /example-hono/service/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service-hono", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "service-hono", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@hono/node-server": "^1.13.3", 13 | "hono": "^4.6.8" 14 | } 15 | }, 16 | "node_modules/@hono/node-server": { 17 | "version": "1.13.3", 18 | "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.3.tgz", 19 | "integrity": "sha512-tEo3hcyQ6chvSnJ3tKzfX4z2sd7Q+ZkBwwBdW1Ya8Mz29dukxC2xcWiB/lAMwGJrYMW8QTgknIsLu1AsnMBe7A==", 20 | "engines": { 21 | "node": ">=18.14.1" 22 | }, 23 | "peerDependencies": { 24 | "hono": "^4" 25 | } 26 | }, 27 | "node_modules/hono": { 28 | "version": "4.6.8", 29 | "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.8.tgz", 30 | "integrity": "sha512-f+2Ec9JAzabT61pglDiLJcF/DjiSefZkjCn9bzm1cYLGkD5ExJ3Jnv93ax9h0bn7UPLHF81KktoyjdQfWI2n1Q==", 31 | "engines": { 32 | "node": ">=16.9.0" 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-hono/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service-hono", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@hono/node-server": "^1.13.3", 14 | "hono": "^4.6.8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example-hono/service/src/index.js: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { serveStatic } from "@hono/node-server/serve-static"; 3 | import { serve } from "@hono/node-server"; 4 | 5 | const app = new Hono(); 6 | 7 | /** 8 | * Middleware 9 | */ 10 | 11 | // Middleware to set headers 12 | app.use("*", async (c, next) => { 13 | // Enable CORS 14 | c.header("Access-Control-Allow-Origin", "*"); 15 | c.header("Access-Control-Allow-Methods", "*"); 16 | c.header("Access-Control-Allow-Headers", "*"); 17 | c.header("x-powered-by", "serverless-container-framework"); 18 | await next(); 19 | }); 20 | 21 | // Serve static files from the "public" directory 22 | app.use( 23 | "/*", 24 | serveStatic({ 25 | root: "src/public", 26 | }) 27 | ); 28 | 29 | /** 30 | * Routes 31 | */ 32 | 33 | // Robots.txt 34 | app.get("/robots.txt", (c) => c.text("User-agent: *")); 35 | 36 | // Options 37 | app.options("*", (c) => c.status(200).body("")); 38 | 39 | // Healthcheck 40 | app.get(`/health`, (c) => c.text(`OK`)); 41 | 42 | // Default Route 43 | app.get(`/*`, (c) => 44 | c.html(` 45 | 46 | 47 | Serverless Container Framework 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
Namespace: ${process.env.SERVERLESS_NAMESPACE}
58 |
Container Name: ${process.env.SERVERLESS_CONTAINER_NAME}
59 |
Stage: ${process.env.SERVERLESS_STAGE}
60 |
Compute Type: ${process.env.SERVERLESS_COMPUTE_TYPE}
61 |
Local: ${process.env.SERVERLESS_LOCAL}
62 |
63 | 64 | 65 | `) 66 | ); 67 | 68 | // Catch-all 404 Handler 69 | app.notFound((c) => 70 | c.html( 71 | ` 72 | 73 | 74 | 404 - Page Not Found 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 |

404 - Page Not Found

84 | Return to Home 85 |
86 | 87 | 88 | `, 89 | 404 90 | ) 91 | ); 92 | 93 | /** 94 | * Error Handler 95 | */ 96 | app.onError((err, c) => { 97 | console.error(err); 98 | const status = err.status || 500; 99 | return c.json( 100 | { 101 | error: err.message || "Internal Server Error", 102 | message: err.message || "Internal Server Error", 103 | code: err.code || "internal_error", 104 | status: status, 105 | // stack: err.stack, // Don't include stack trace 106 | }, 107 | status 108 | ); 109 | }); 110 | 111 | serve({ fetch: app.fetch, port: process.env.PORT || 8080 }); 112 | -------------------------------------------------------------------------------- /example-hono/service/src/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | height: 100%; 11 | font-family: "Roboto Mono", monospace; 12 | font-optical-sizing: auto; 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | img { 18 | -webkit-user-drag: none; 19 | } 20 | 21 | body { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 26 | background: #000000; 27 | color: #ffffff; 28 | } 29 | 30 | body::after { 31 | content: ""; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-image: url("/images/background.png"); 38 | background-size: cover; 39 | background-repeat: no-repeat; 40 | background-position: center; 41 | background-attachment: fixed; 42 | opacity: 0; 43 | animation: fadeIn 0.75s ease-in forwards; 44 | z-index: -1; /* Places background behind content */ 45 | } 46 | 47 | a { 48 | color: #fd5750; 49 | text-decoration: none; 50 | } 51 | 52 | h1 { 53 | color: white; 54 | font-size: 2rem; 55 | font-weight: 500; 56 | text-align: center; 57 | margin: 20px 0; 58 | } 59 | 60 | .container { 61 | display: flex; 62 | flex-direction: column; 63 | align-items: center; 64 | width: 90%; /* Fluid width */ 65 | max-width: 1200px; /* Sets a max width for larger screens */ 66 | padding: 20px; 67 | text-align: center; 68 | } 69 | 70 | .logo { 71 | max-width: 400px; /* Controls logo size for responsiveness */ 72 | width: 100%; 73 | height: auto; 74 | margin-bottom: 40px; 75 | opacity: 0; 76 | transform: scale(0.75); /* Start scaled at 75% */ 77 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 0.75s duration, 0.35s delay */ 78 | } 79 | 80 | .info { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | font-size: 1.2rem; 85 | color: rgba(255, 255, 255, 0.7); 86 | margin-top: 5px; 87 | opacity: 0; 88 | animation: fadeIn 2s ease-in-out 1.5s forwards; 89 | } 90 | 91 | /** 92 | * Animations 93 | */ 94 | @keyframes fadeIn { 95 | from { 96 | opacity: 0; 97 | } 98 | to { 99 | opacity: 1; 100 | } 101 | } 102 | 103 | @keyframes fadeInZoom { 104 | from { 105 | opacity: 0; 106 | transform: scale(0.85); /* Start at 85% */ 107 | } 108 | to { 109 | opacity: 1; 110 | transform: scale(1); /* End at 100% */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /example-hono/service/src/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-hono/service/src/public/images/background.png -------------------------------------------------------------------------------- /example-hono/service/src/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-hono/service/src/public/images/favicon.png -------------------------------------------------------------------------------- /example-hono/service/src/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-hono/service/src/public/images/logo.png -------------------------------------------------------------------------------- /example-mastra-slack/.env-example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | SLACK_APP_NAME= 3 | SLACK_SIGNING_SECRET= 4 | SLACK_BOT_TOKEN= 5 | SLACK_APP_TOKEN= 6 | DB_URL= 7 | -------------------------------------------------------------------------------- /example-mastra-slack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mastra-slack-agent", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "mastra dev", 8 | "build": "mastra build" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "type": "module", 15 | "engines": { 16 | "node": ">=20.9.0" 17 | }, 18 | "dependencies": { 19 | "@ai-sdk/openai": "^1.3.22", 20 | "@mastra/core": "^0.10.0", 21 | "@mastra/loggers": "^0.10.0", 22 | "@mastra/memory": "^0.10.0", 23 | "@mastra/pg": "^0.10.0", 24 | "@slack/bolt": "^4.4.0", 25 | "zod": "^3.25.20" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^22.15.21", 29 | "mastra": "^0.10.0", 30 | "typescript": "^5.8.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example-mastra-slack/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: mastra-slack 2 | 3 | deployment: 4 | type: "aws@1.0" 5 | 6 | containers: 7 | agent: 8 | src: ./ 9 | compute: 10 | type: awsFargateEcs 11 | awsFargateEcs: 12 | cpu: 1024 13 | memory: 2048 14 | routing: 15 | pathPattern: "/*" 16 | pathHealthCheck: "/" 17 | environment: 18 | OPENAI_API_KEY: ${env:OPENAI_API_KEY} 19 | PORT: 8080 20 | SLACK_APP_NAME: ${env:SLACK_APP_NAME} 21 | SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET} 22 | SLACK_BOT_TOKEN: ${env:SLACK_BOT_TOKEN} 23 | SLACK_APP_TOKEN: ${env:SLACK_APP_TOKEN} 24 | DB_URL: ${env:DB_URL} 25 | integrations: 26 | slack: 27 | type: slack 28 | name: ${env:SLACK_APP_NAME} 29 | -------------------------------------------------------------------------------- /example-mastra-slack/src/mastra/agents/weather-agent.ts: -------------------------------------------------------------------------------- 1 | import { openai } from '@ai-sdk/openai'; 2 | import { Agent } from '@mastra/core/agent'; 3 | import { Memory } from '@mastra/memory'; 4 | import { weatherTool } from '../tools/weather-tool'; 5 | import { storage } from '../store'; 6 | 7 | export const weatherAgent = new Agent({ 8 | name: 'Weather Agent', 9 | instructions: ` 10 | You are a helpful weather assistant that provides accurate weather information. 11 | 12 | Your primary function is to help users get weather details for specific locations. When responding: 13 | - Always ask for a location if none is provided 14 | - If the location name isn’t in English, please translate it 15 | - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") 16 | - Include relevant details like humidity, wind conditions, and precipitation 17 | - Keep responses concise but informative 18 | 19 | Use the weatherTool to fetch current weather data. 20 | `, 21 | model: openai('gpt-4o-mini'), 22 | tools: { weatherTool }, 23 | memory: new Memory({ 24 | storage: storage, 25 | }), 26 | }); 27 | -------------------------------------------------------------------------------- /example-mastra-slack/src/mastra/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Mastra } from '@mastra/core/mastra'; 3 | import { PinoLogger } from '@mastra/loggers'; 4 | import { storage } from './store'; 5 | import crypto from 'crypto'; 6 | 7 | import type { Context } from 'hono'; 8 | import { registerApiRoute } from '@mastra/core/server'; 9 | 10 | import bolt from "@slack/bolt"; 11 | import type { AgentGenerateOptions } from "@mastra/core/agent"; 12 | 13 | import { weatherAgent } from './agents/weather-agent'; 14 | 15 | const socketMode = process.env.SERVERLESS_LOCAL === "true"; 16 | 17 | const slackConfig: bolt.AppOptions = { 18 | token: process.env.SLACK_BOT_TOKEN, 19 | signingSecret: process.env.SLACK_SIGNING_SECRET, 20 | appToken: process.env.SLACK_APP_TOKEN, 21 | socketMode, 22 | } 23 | 24 | const boltApp = new bolt.App(slackConfig); 25 | 26 | const eventHandler = async ({ 27 | event, 28 | context, 29 | say, 30 | }: { 31 | event: bolt.KnownEventFromType<"message">; 32 | context: bolt.Context; 33 | say: (params: bolt.SayArguments) => Promise; 34 | }) => { 35 | const isDM = event.channel_type === "im"; 36 | 37 | const textMessage = (event as {text?: string}).text; 38 | const isMention = textMessage?.includes(`<@${context.botUserId}>`); 39 | 40 | const shouldRespond = isDM || isMention; 41 | 42 | if(shouldRespond) { 43 | const responseThreadId = "thread_ts" in event ? event.thread_ts : event.ts; 44 | 45 | const agentOptions: AgentGenerateOptions = { 46 | resourceId: context.botUserId || context.botId || "bot", 47 | threadId: responseThreadId!, 48 | }; 49 | 50 | let responseMessage = ""; 51 | 52 | try { 53 | const generateOptions = { ...agentOptions, output: undefined } 54 | const result = await weatherAgent.generate(textMessage!, generateOptions) 55 | responseMessage = result.text; 56 | } catch (error) { 57 | responseMessage = `Error occurred while calling Agent: ${(error as Error).message}` 58 | } 59 | 60 | await say({ 61 | text: responseMessage, 62 | thread_ts: responseThreadId, 63 | }) 64 | } 65 | 66 | } 67 | 68 | boltApp.event("message", eventHandler); 69 | 70 | const verifySlackSignature = async (c: Context) => { 71 | const slackTimestamp = c.req.header("x-slack-request-timestamp"); 72 | const slackSignature = c.req.header("x-slack-signature"); 73 | const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5; 74 | 75 | if (!slackTimestamp || Number(slackTimestamp) < fiveMinutesAgo) 76 | return false; 77 | 78 | const sigBasestring = `v0:${slackTimestamp}:${await c.req.text()}`; 79 | const hmac = crypto.createHmac("sha256", slackConfig.signingSecret!); 80 | hmac.update(sigBasestring); 81 | const mySignature = `v0=${hmac.digest("hex")}`; 82 | return crypto.timingSafeEqual( 83 | Buffer.from(mySignature), 84 | Buffer.from(slackSignature!), 85 | ); 86 | } 87 | 88 | const requestHandler = async (c: Context) => { 89 | const body = await c.req.json(); 90 | 91 | // Verify slack signature before we continue 92 | if(!(await verifySlackSignature(c))) { 93 | return c.json({ error: 'Invalid Slack signature' }, 401); 94 | } 95 | 96 | if(body.type === 'url_verification') { 97 | return c.json({challenge: body.challenge}) 98 | } 99 | 100 | let ackCalled = false; 101 | let ackResponse: Response | undefined; 102 | const ackFn = async (response?: unknown) => { 103 | if(ackCalled) { 104 | return; 105 | } 106 | 107 | ackCalled = true; 108 | 109 | if(response instanceof Error) { 110 | ackResponse = c.json({error: response.message}, 500) 111 | } else if (!response) { 112 | ackResponse = c.text("") 113 | } else { 114 | ackResponse = c.json(response) 115 | } 116 | } 117 | 118 | if (body.type === 'event_callback') { 119 | const response = c.json({status: 'ok'}) 120 | 121 | const event: bolt.ReceiverEvent = { 122 | body, 123 | ack: ackFn, 124 | } 125 | 126 | boltApp.processEvent(event).catch((error) => { 127 | console.error("Error processing event", error) 128 | }) 129 | 130 | return response; 131 | } else { 132 | return ackResponse || c.json({status: 'ok'}) 133 | } 134 | } 135 | 136 | const apiRoutes = socketMode ? [] : [ 137 | registerApiRoute('/slack',{ 138 | method: 'POST', 139 | handler: requestHandler 140 | }) 141 | ] 142 | 143 | export const mastra = new Mastra({ 144 | agents: { weatherAgent }, 145 | storage: storage, 146 | logger: new PinoLogger({ 147 | name: 'Mastra', 148 | level: 'info', 149 | }), 150 | server: { 151 | port: process.env.PORT ? parseInt(process.env.PORT) : 8080, 152 | host: '0.0.0.0', 153 | apiRoutes 154 | } 155 | }); 156 | 157 | if(socketMode) { 158 | await boltApp.start(); 159 | } -------------------------------------------------------------------------------- /example-mastra-slack/src/mastra/store.ts: -------------------------------------------------------------------------------- 1 | import { PostgresStore } from '@mastra/pg'; 2 | 3 | export const storage = new PostgresStore({ 4 | connectionString: process.env.DB_URL!, 5 | }); 6 | -------------------------------------------------------------------------------- /example-mastra-slack/src/mastra/tools/weather-tool.ts: -------------------------------------------------------------------------------- 1 | import { createTool } from '@mastra/core/tools'; 2 | import { z } from 'zod'; 3 | 4 | interface GeocodingResponse { 5 | results: { 6 | latitude: number; 7 | longitude: number; 8 | name: string; 9 | }[]; 10 | } 11 | interface WeatherResponse { 12 | current: { 13 | time: string; 14 | temperature_2m: number; 15 | apparent_temperature: number; 16 | relative_humidity_2m: number; 17 | wind_speed_10m: number; 18 | wind_gusts_10m: number; 19 | weather_code: number; 20 | }; 21 | } 22 | 23 | export const weatherTool = createTool({ 24 | id: 'get-weather', 25 | description: 'Get current weather for a location', 26 | inputSchema: z.object({ 27 | location: z.string().describe('City name'), 28 | }), 29 | outputSchema: z.object({ 30 | temperature: z.number(), 31 | feelsLike: z.number(), 32 | humidity: z.number(), 33 | windSpeed: z.number(), 34 | windGust: z.number(), 35 | conditions: z.string(), 36 | location: z.string(), 37 | }), 38 | execute: async ({ context }) => { 39 | return await getWeather(context.location); 40 | }, 41 | }); 42 | 43 | const getWeather = async (location: string) => { 44 | const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`; 45 | const geocodingResponse = await fetch(geocodingUrl); 46 | const geocodingData = (await geocodingResponse.json()) as GeocodingResponse; 47 | 48 | if (!geocodingData.results?.[0]) { 49 | throw new Error(`Location '${location}' not found`); 50 | } 51 | 52 | const { latitude, longitude, name } = geocodingData.results[0]; 53 | 54 | const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`; 55 | 56 | const response = await fetch(weatherUrl); 57 | const data = (await response.json()) as WeatherResponse; 58 | 59 | return { 60 | temperature: data.current.temperature_2m, 61 | feelsLike: data.current.apparent_temperature, 62 | humidity: data.current.relative_humidity_2m, 63 | windSpeed: data.current.wind_speed_10m, 64 | windGust: data.current.wind_gusts_10m, 65 | conditions: getWeatherCondition(data.current.weather_code), 66 | location: name, 67 | }; 68 | }; 69 | 70 | function getWeatherCondition(code: number): string { 71 | const conditions: Record = { 72 | 0: 'Clear sky', 73 | 1: 'Mainly clear', 74 | 2: 'Partly cloudy', 75 | 3: 'Overcast', 76 | 45: 'Foggy', 77 | 48: 'Depositing rime fog', 78 | 51: 'Light drizzle', 79 | 53: 'Moderate drizzle', 80 | 55: 'Dense drizzle', 81 | 56: 'Light freezing drizzle', 82 | 57: 'Dense freezing drizzle', 83 | 61: 'Slight rain', 84 | 63: 'Moderate rain', 85 | 65: 'Heavy rain', 86 | 66: 'Light freezing rain', 87 | 67: 'Heavy freezing rain', 88 | 71: 'Slight snow fall', 89 | 73: 'Moderate snow fall', 90 | 75: 'Heavy snow fall', 91 | 77: 'Snow grains', 92 | 80: 'Slight rain showers', 93 | 81: 'Moderate rain showers', 94 | 82: 'Violent rain showers', 95 | 85: 'Slight snow showers', 96 | 86: 'Heavy snow showers', 97 | 95: 'Thunderstorm', 98 | 96: 'Thunderstorm with slight hail', 99 | 99: 'Thunderstorm with heavy hail', 100 | }; 101 | return conditions[code] || 'Unknown'; 102 | } 103 | -------------------------------------------------------------------------------- /example-mastra-slack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "bundler", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "noEmit": true, 11 | "outDir": "dist" 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /example-nextjs/README.md: -------------------------------------------------------------------------------- 1 | # Next.js Application Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy a Next.js web application using SCF. Due to the potential for large SSR outputs, this project is configured for deployment on AWS Fargate ECS. 6 | 7 | ## Features 8 | 9 | - **Next.js Framework:** 10 | Leverages Next.js for server-side rendering (SSR) and static site generation. 11 | - **Dynamic Routing & SSR:** 12 | Provides robust routing and dynamic page generation. 13 | - **Optimized Production Builds:** 14 | Docker multi-stage builds ensure efficient deployments. 15 | - **Flexible Compute Options:** 16 | Configured for AWS Fargate ECS to handle large HTML responses. 17 | 18 | ## Prerequisites 19 | 20 | **Docker:** 21 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 22 | 23 | **Serverless Framework:** 24 | Install globally: 25 | ```bash 26 | npm i -g serverless 27 | ``` 28 | 29 | **Node.js & npm:** 30 | Ensure you have a recent Node.js LTS version installed. 31 | 32 | **AWS Credentials:** 33 | Configure your AWS credentials (via environment variables or profiles) for SCF deployments. 34 | 35 | ## Configuration 36 | 37 | At the project root, the `serverless.containers.yml` file defines the SCF configuration: 38 | 39 | ```yaml 40 | name: nextjs 41 | 42 | deployment: 43 | type: awsApi@1.0 44 | 45 | containers: 46 | service: 47 | src: ./service 48 | routing: 49 | pathPattern: /* 50 | pathHealthCheck: /health 51 | environment: 52 | HELLO: world 53 | compute: 54 | # awsLambda is not recommended for this Next.js app. 55 | # SSR can generate large HTML responses, which may exceed 56 | # the req/res size limits for AWS Lambda. 57 | type: awsFargateEcs 58 | ``` 59 | 60 | ## Project Structure 61 | 62 | A typical project structure for this Next.js example: 63 | ``` 64 | example-nextjs/ 65 | ├── serverless.containers.yml # SCF configuration file 66 | └── service/ 67 | ├── next.config.ts # Next.js configuration file 68 | ├── package.json # Project configuration and dependencies 69 | ├── public/ # Static assets (images, CSS, etc.) 70 | └── src/ 71 | ├── app/ # Next.js app folder (pages, components, etc.) 72 | └── (other directories) # Additional assets and logic 73 | ``` 74 | 75 | ## Development 76 | 77 | For local development, use Serverless Container Framework's development mode: 78 | ```bash 79 | serverless dev 80 | ``` 81 | This will automatically start the Next.js development server with hot reloading and AWS emulation. It detects the dev npm script and uses that for hot reloading. 82 | 83 | ## Deployment 84 | 85 | Deploy your Next.js application to AWS by running: 86 | ```bash 87 | serverless deploy 88 | ``` 89 | SCF builds the container image (using the provided multi-stage Dockerfile) and provisions the necessary AWS resources. 90 | 91 | ## Cleanup 92 | 93 | To remove the deployed AWS resources, run: 94 | ```bash 95 | serverless remove --force --all 96 | ``` 97 | 98 | ## Additional Resources 99 | 100 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 101 | - [Next.js Documentation](https://nextjs.org/docs) 102 | - [Docker Documentation](https://docs.docker.com) 103 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) -------------------------------------------------------------------------------- /example-nextjs/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: nextjs 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | # awsLambda is not recommended for this nextjs app. 16 | # SSR can generate large HTML responses, which may exceed 17 | # the req/res size limit for AWS ALB and AWS Lambda. 18 | type: awsFargateEcs 19 | -------------------------------------------------------------------------------- /example-nextjs/service/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /example-nextjs/service/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | { 15 | rules: { 16 | "react/react-in-jsx-scope": "off", 17 | }, 18 | }, 19 | ]; 20 | 21 | export default eslintConfig; 22 | -------------------------------------------------------------------------------- /example-nextjs/service/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /example-nextjs/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "react": "^19.0.0", 13 | "react-dom": "^19.0.0", 14 | "next": "15.1.2" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5", 18 | "@types/node": "^20", 19 | "@types/react": "^19", 20 | "@types/react-dom": "^19", 21 | "eslint": "^9", 22 | "eslint-config-next": "15.1.2", 23 | "@eslint/eslintrc": "^3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example-nextjs/service/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-nextjs/service/public/images/background.png -------------------------------------------------------------------------------- /example-nextjs/service/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-nextjs/service/public/images/favicon.png -------------------------------------------------------------------------------- /example-nextjs/service/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-nextjs/service/public/images/logo.png -------------------------------------------------------------------------------- /example-nextjs/service/src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * About page component that provides information about the application. 3 | * 4 | * @returns {JSX.Element} The rendered About page. 5 | */ 6 | export default function About() { 7 | return ( 8 |
9 |

About Page

10 |

This is a minimal React app with basic routing.

11 |
12 | ); 13 | } -------------------------------------------------------------------------------- /example-nextjs/service/src/app/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | /** 4 | * NavBar component that displays fixed top navigation links. 5 | * 6 | * @returns {JSX.Element} The rendered navigation bar. 7 | */ 8 | export default function NavBar() { 9 | return ( 10 |
11 | 25 |
26 | ); 27 | } -------------------------------------------------------------------------------- /example-nextjs/service/src/app/globals.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Global CSS updated to reflect the React app styles. 3 | */ 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | box-sizing: border-box; 8 | } 9 | 10 | html, 11 | body { 12 | width: 100%; 13 | height: 100%; 14 | font-family: "Roboto Mono", monospace; 15 | font-optical-sizing: auto; 16 | font-weight: 400; 17 | font-style: normal; 18 | } 19 | 20 | img { 21 | -webkit-user-drag: none; 22 | } 23 | 24 | body { 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 29 | background: #000000; 30 | color: #ffffff; 31 | } 32 | 33 | body::after { 34 | content: ""; 35 | position: fixed; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 100%; 40 | background-image: url("/images/background.png"); 41 | background-size: cover; 42 | background-repeat: no-repeat; 43 | background-position: center; 44 | background-attachment: fixed; 45 | opacity: 0; 46 | animation: fadeIn 0.75s ease-in forwards; 47 | z-index: -1; /* Places background behind content */ 48 | } 49 | 50 | a { 51 | color: #fd5750; 52 | text-decoration: none; 53 | } 54 | 55 | h1 { 56 | color: white; 57 | font-size: 2rem; 58 | font-weight: 500; 59 | text-align: center; 60 | margin: 20px 0; 61 | } 62 | 63 | .container { 64 | display: flex; 65 | flex-direction: column; 66 | align-items: center; 67 | width: 90%; /* Fluid width */ 68 | max-width: 1200px; /* Sets a max width for larger screens */ 69 | padding: 20px; 70 | text-align: center; 71 | } 72 | 73 | .logo { 74 | max-width: 400px; /* Controls logo size for responsiveness */ 75 | width: 100%; 76 | height: auto; 77 | margin-bottom: 40px; 78 | opacity: 0; 79 | transform: scale(0.75); /* Start scaled at 75% */ 80 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 2.5s duration for zoom effect */ 81 | } 82 | 83 | .info { 84 | display: flex; 85 | justify-content: center; 86 | align-items: center; 87 | font-size: 1.2rem; 88 | color: rgba(255, 255, 255, 0.7); 89 | margin-top: 5px; 90 | opacity: 0; 91 | animation: fadeIn 2s ease-in-out 1.5s forwards; 92 | } 93 | 94 | /** 95 | * Animations 96 | */ 97 | @keyframes fadeIn { 98 | from { 99 | opacity: 0; 100 | } 101 | to { 102 | opacity: 1; 103 | } 104 | } 105 | 106 | @keyframes fadeInZoom { 107 | from { 108 | opacity: 0; 109 | transform: scale(0.85); /* Start at 85% */ 110 | } 111 | to { 112 | opacity: 1; 113 | transform: scale(1); /* End at 100% */ 114 | } 115 | } 116 | 117 | /** 118 | * Styles for the fixed top navigation bar used for routing. 119 | */ 120 | .nav-header { 121 | position: fixed; 122 | top: 0; 123 | width: 100%; 124 | padding: 10px 0; 125 | z-index: 1000; 126 | } 127 | 128 | .nav-ul { 129 | display: flex; 130 | justify-content: center; 131 | list-style: none; 132 | margin: 0; 133 | padding: 0; 134 | } 135 | 136 | .nav-li { 137 | margin: 0 15px; 138 | } 139 | 140 | .nav-link { 141 | color: #fff; 142 | text-decoration: none; 143 | } 144 | 145 | .nav-link:hover { 146 | text-decoration: underline; 147 | } 148 | 149 | /* Main content top padding to avoid content being hidden behind the fixed nav bar */ 150 | main { 151 | padding-top: 60px; 152 | } 153 | 154 | .navbar { 155 | position: fixed; 156 | top: 1rem; 157 | right: 2rem; 158 | display: flex; 159 | gap: 1rem; 160 | z-index: 50; 161 | } -------------------------------------------------------------------------------- /example-nextjs/service/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import NavBar from "./components/NavBar"; 4 | import "./globals.css"; 5 | 6 | const geistSans = Geist({ 7 | variable: "--font-geist-sans", 8 | subsets: ["latin"], 9 | }); 10 | 11 | const geistMono = Geist_Mono({ 12 | variable: "--font-geist-mono", 13 | subsets: ["latin"], 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Serverless Container Framework - NextJS Example", 18 | description: "Serverless Container Framework NextJS Example", 19 | icons: { 20 | icon: [ 21 | { url: '/images/favicon.png' }, 22 | // { url: '/images/favicon-16x16.png', sizes: '16x16', type: 'image/png' }, 23 | // { url: '/images/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, 24 | ], 25 | // If you have an Apple touch icon, you can add it too 26 | apple: [ 27 | { url: '/images/apple-touch-icon.png' }, 28 | ], 29 | }, 30 | }; 31 | 32 | export default function RootLayout({ 33 | children, 34 | }: Readonly<{ 35 | children: React.ReactNode; 36 | }>) { 37 | return ( 38 | 39 | 42 | 43 | {children} 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /example-nextjs/service/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Image from "next/image"; 4 | 5 | /** 6 | * Home page component that displays the main landing content. 7 | * 8 | * @returns {JSX.Element} The rendered Home page. 9 | */ 10 | export default function Home() { 11 | return ( 12 |
13 | Logo 20 |
21 | ); 22 | } -------------------------------------------------------------------------------- /example-nextjs/service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./src/*" 28 | ] 29 | } 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /example-react-router-v7/README.md: -------------------------------------------------------------------------------- 1 | # React Router V7 Application Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) simplifies the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy a full‑stack React application using React Router v7 for dynamic routing and server-side rendering (SSR). It is optimized for deployment on AWS Fargate ECS to handle potentially large HTML responses. 6 | 7 | ## Features 8 | 9 | - **React Router v7:** 10 | Utilizes React Router v7 for advanced routing and SSR capabilities. 11 | - **Full‑Stack React Application:** 12 | Combines client‑side navigation with server‑side rendering for optimal performance. 13 | - **Optimized Bundling:** 14 | Built using modern bundling tools (e.g., Vite and React Router dev) for efficient production builds. 15 | - **Flexible Compute Options:** 16 | Configured for AWS Fargate ECS to manage larger HTML responses beyond AWS Lambda's limits. 17 | 18 | ## Prerequisites 19 | 20 | **Docker:** 21 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 22 | 23 | **Serverless Framework:** 24 | Install globally: 25 | ```bash 26 | npm i -g serverless 27 | ``` 28 | 29 | **Node.js & npm:** 30 | Ensure you have a recent Node.js LTS version installed. 31 | 32 | **AWS Credentials:** 33 | Set up your AWS credentials (via environment variables or profiles) for SCF deployments. 34 | 35 | ## Configuration 36 | 37 | The SCF configuration is defined in the `serverless.containers.yml` file at the project root: 38 | 39 | ```yaml 40 | name: rrouter-v7 41 | 42 | deployment: 43 | type: awsApi@1.0 44 | 45 | containers: 46 | service: 47 | src: ./service 48 | routing: 49 | pathPattern: /* 50 | pathHealthCheck: /health 51 | environment: 52 | HELLO: world 53 | compute: 54 | # awsLambda is not recommended for react-router-v7 55 | # due to potential large HTML responses. 56 | type: awsFargateEcs 57 | ``` 58 | 59 | ## Project Structure 60 | 61 | A typical project structure for this React Router v7 example: 62 | ``` 63 | example-react-router-v7/ 64 | ├── serverless.containers.yml # SCF configuration file 65 | └── service/ 66 | ├── package.json # Project configuration and dependencies 67 | ├── Dockerfile # Dockerfile for building the application container 68 | └── app/ # Application source code 69 | ├── routes/ # React Router route components 70 | ├── welcome/ # Welcome components and assets 71 | ├── app.css # Main CSS styles (e.g., Tailwind CSS) 72 | ├── root.tsx # Root component and layout 73 | └── (other assets and configuration files) 74 | ``` 75 | 76 | ## Development 77 | 78 | For local development, use Serverless Container Framework's development mode: 79 | ```bash 80 | serverless dev 81 | ``` 82 | This will automatically start the development environment with hot reloading and AWS-like routing. It detects the dev npm script and uses that for hot reloading. 83 | 84 | ## Deployment 85 | 86 | Deploy your React Router v7 application to AWS by running: 87 | ```bash 88 | serverless deploy 89 | ``` 90 | SCF builds the container image using Docker multi-stage builds and provisions the necessary AWS resources (ALB, VPC, and ECS Fargate service). 91 | 92 | ## Cleanup 93 | 94 | To remove the deployed AWS resources, run: 95 | ```bash 96 | serverless remove --force --all 97 | ``` 98 | 99 | ## Additional Resources 100 | 101 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 102 | - [React Router v7 Documentation](https://reactrouter.com/) 103 | - [React Documentation](https://reactjs.org/docs/getting-started.html) 104 | - [Vite Documentation](https://vitejs.dev) 105 | - [Docker Documentation](https://docs.docker.com) 106 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) -------------------------------------------------------------------------------- /example-react-router-v7/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: rrouter-v7 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | # awsLambda is not recommended for react-router-v7. 16 | # SSR can generate large HTML responses, which may exceed 17 | # the req/res size limit for AWS ALB and AWS Lambda. 18 | type: awsFargateEcs -------------------------------------------------------------------------------- /example-react-router-v7/service/.dockerignore: -------------------------------------------------------------------------------- 1 | .react-router 2 | build 3 | node_modules 4 | README.md -------------------------------------------------------------------------------- /example-react-router-v7/service/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | 4 | # React Router 5 | /.react-router/ 6 | /build/ 7 | -------------------------------------------------------------------------------- /example-react-router-v7/service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS development-dependencies-env 2 | COPY . /app 3 | WORKDIR /app 4 | RUN npm ci 5 | 6 | FROM node:20-alpine AS production-dependencies-env 7 | COPY ./package.json package-lock.json /app/ 8 | WORKDIR /app 9 | RUN npm ci --omit=dev 10 | 11 | FROM node:20-alpine AS build-env 12 | COPY . /app/ 13 | COPY --from=development-dependencies-env /app/node_modules /app/node_modules 14 | WORKDIR /app 15 | RUN npm run build 16 | 17 | FROM node:20-alpine 18 | COPY ./package.json package-lock.json /app/ 19 | COPY --from=production-dependencies-env /app/node_modules /app/node_modules 20 | COPY --from=build-env /app/build /app/build 21 | WORKDIR /app 22 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /example-react-router-v7/service/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to React Router! 2 | 3 | A modern, production-ready template for building full-stack React applications using React Router. 4 | 5 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default) 6 | 7 | ## Features 8 | 9 | - 🚀 Server-side rendering 10 | - ⚡️ Hot Module Replacement (HMR) 11 | - 📦 Asset bundling and optimization 12 | - 🔄 Data loading and mutations 13 | - 🔒 TypeScript by default 14 | - 🎉 TailwindCSS for styling 15 | - 📖 [React Router docs](https://reactrouter.com/) 16 | 17 | ## Getting Started 18 | 19 | ### Installation 20 | 21 | Install the dependencies: 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | ### Development 28 | 29 | Start the development server with HMR: 30 | 31 | ```bash 32 | npm run dev 33 | ``` 34 | 35 | Your application will be available at `http://localhost:5173`. 36 | 37 | ## Building for Production 38 | 39 | Create a production build: 40 | 41 | ```bash 42 | npm run build 43 | ``` 44 | 45 | ## Deployment 46 | 47 | ### Docker Deployment 48 | 49 | This template includes three Dockerfiles optimized for different package managers: 50 | 51 | - `Dockerfile` - for npm 52 | - `Dockerfile.pnpm` - for pnpm 53 | - `Dockerfile.bun` - for bun 54 | 55 | To build and run using Docker: 56 | 57 | ```bash 58 | # For npm 59 | docker build -t my-app . 60 | 61 | # For pnpm 62 | docker build -f Dockerfile.pnpm -t my-app . 63 | 64 | # For bun 65 | docker build -f Dockerfile.bun -t my-app . 66 | 67 | # Run the container 68 | docker run -p 3000:3000 my-app 69 | ``` 70 | 71 | The containerized application can be deployed to any platform that supports Docker, including: 72 | 73 | - AWS ECS 74 | - Google Cloud Run 75 | - Azure Container Apps 76 | - Digital Ocean App Platform 77 | - Fly.io 78 | - Railway 79 | 80 | ### DIY Deployment 81 | 82 | If you're familiar with deploying Node applications, the built-in app server is production-ready. 83 | 84 | Make sure to deploy the output of `npm run build` 85 | 86 | ``` 87 | ├── package.json 88 | ├── package-lock.json (or pnpm-lock.yaml, or bun.lockb) 89 | ├── build/ 90 | │ ├── client/ # Static assets 91 | │ └── server/ # Server-side code 92 | ``` 93 | 94 | ## Styling 95 | 96 | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. 97 | 98 | --- 99 | 100 | Built with ❤️ using React Router. 101 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, 5 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 6 | } 7 | 8 | html, 9 | body { 10 | @apply bg-white dark:bg-gray-950; 11 | 12 | @media (prefers-color-scheme: dark) { 13 | color-scheme: dark; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | isRouteErrorResponse, 3 | Links, 4 | Meta, 5 | Outlet, 6 | Scripts, 7 | ScrollRestoration, 8 | } from "react-router"; 9 | 10 | import type { Route } from "./+types/root"; 11 | import "./app.css"; 12 | 13 | export const links: Route.LinksFunction = () => [ 14 | { rel: "preconnect", href: "https://fonts.googleapis.com" }, 15 | { 16 | rel: "preconnect", 17 | href: "https://fonts.gstatic.com", 18 | crossOrigin: "anonymous", 19 | }, 20 | { 21 | rel: "stylesheet", 22 | href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", 23 | }, 24 | ]; 25 | 26 | export function Layout({ children }: { children: React.ReactNode }) { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {children} 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default function App() { 45 | return ; 46 | } 47 | 48 | export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { 49 | let message = "Oops!"; 50 | let details = "An unexpected error occurred."; 51 | let stack: string | undefined; 52 | 53 | if (isRouteErrorResponse(error)) { 54 | message = error.status === 404 ? "404" : "Error"; 55 | details = 56 | error.status === 404 57 | ? "The requested page could not be found." 58 | : error.statusText || details; 59 | } else if (import.meta.env.DEV && error && error instanceof Error) { 60 | details = error.message; 61 | stack = error.stack; 62 | } 63 | 64 | return ( 65 |
66 |

{message}

67 |

{details}

68 | {stack && ( 69 |
70 |           {stack}
71 |         
72 | )} 73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type RouteConfig, 3 | route, 4 | index, 5 | } from "@react-router/dev/routes"; 6 | 7 | export default [ 8 | index("./routes/home.tsx"), 9 | route("health", "./routes/health.tsx"), 10 | route("about", "./routes/about.tsx"), 11 | ] satisfies RouteConfig; 12 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/routes/about.tsx: -------------------------------------------------------------------------------- 1 | import type { Route } from "./+types/home"; 2 | 3 | export function meta({}: Route.MetaArgs) { 4 | return [ 5 | { title: "Serverless Container Framework - Example React Router v7" }, 6 | { name: "description", content: "Serverless Container Framework example using React Router v7" }, 7 | ]; 8 | } 9 | 10 | export default function Home() { 11 | return
A simple about page
; 12 | } 13 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/routes/health.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple health check route component that renders a minimal page and returns 200 status 3 | * @returns {JSX.Element} Health check page 4 | */ 5 | export default function Health() { 6 | return ( 7 |
8 |

OK

9 |
10 | ); 11 | } 12 | 13 | /** 14 | * Loader function to set status code for health check 15 | * @returns {Response} Response with 200 status code 16 | */ 17 | export function loader() { 18 | return new Response("OK", { 19 | status: 200, 20 | headers: { 21 | "Content-Type": "text/plain", 22 | }, 23 | }); 24 | } 25 | 26 | /** 27 | * Metadata configuration for the health check route 28 | * @returns {Array} Array of meta tags 29 | */ 30 | export function meta() { 31 | return [ 32 | { title: "Health Check" }, 33 | { name: "description", content: "Application health check endpoint" }, 34 | ]; 35 | } -------------------------------------------------------------------------------- /example-react-router-v7/service/app/routes/home.tsx: -------------------------------------------------------------------------------- 1 | import type { Route } from "./+types/home"; 2 | import { Welcome } from "../welcome/welcome"; 3 | 4 | export function meta({}: Route.MetaArgs) { 5 | return [ 6 | { title: "New React Router App" }, 7 | { name: "description", content: "Welcome to React Router!" }, 8 | ]; 9 | } 10 | 11 | export default function Home() { 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/welcome/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/welcome/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example-react-router-v7/service/app/welcome/welcome.tsx: -------------------------------------------------------------------------------- 1 | import logoDark from "./logo-dark.svg"; 2 | import logoLight from "./logo-light.svg"; 3 | 4 | export function Welcome() { 5 | return ( 6 |
7 |
8 |
9 |
10 | React Router 15 | React Router 20 |
21 |
22 |
23 | 43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | const resources = [ 50 | { 51 | href: "https://reactrouter.com/docs", 52 | text: "React Router Docs", 53 | icon: ( 54 | 62 | 67 | 68 | ), 69 | }, 70 | { 71 | href: "https://rmx.as/discord", 72 | text: "Join Discord", 73 | icon: ( 74 | 82 | 86 | 87 | ), 88 | }, 89 | ]; 90 | -------------------------------------------------------------------------------- /example-react-router-v7/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "service", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "react-router build", 7 | "dev": "react-router dev --force", 8 | "start": "PORT=8080 react-router-serve ./build/server/index.js", 9 | "typecheck": "react-router typegen && tsc" 10 | }, 11 | "dependencies": { 12 | "@react-router/node": "^7.1.5", 13 | "@react-router/serve": "^7.1.5", 14 | "isbot": "^5.1.17", 15 | "react": "^19.0.0", 16 | "react-dom": "^19.0.0", 17 | "react-router": "^7.1.5" 18 | }, 19 | "devDependencies": { 20 | "@react-router/dev": "^7.1.5", 21 | "@tailwindcss/vite": "^4.0.0", 22 | "@types/node": "^20", 23 | "@types/react": "^19.0.1", 24 | "@types/react-dom": "^19.0.1", 25 | "react-router-devtools": "^1.1.0", 26 | "tailwindcss": "^4.0.0", 27 | "typescript": "^5.7.2", 28 | "vite": "^5.4.11", 29 | "vite-tsconfig-paths": "^5.1.4" 30 | } 31 | } -------------------------------------------------------------------------------- /example-react-router-v7/service/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-react-router-v7/service/public/favicon.ico -------------------------------------------------------------------------------- /example-react-router-v7/service/react-router.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@react-router/dev/config"; 2 | 3 | export default { 4 | // Config options... 5 | // Server-side render by default, to enable SPA mode set this to `false` 6 | ssr: true, 7 | } satisfies Config; 8 | -------------------------------------------------------------------------------- /example-react-router-v7/service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*", 4 | "**/.server/**/*", 5 | "**/.client/**/*", 6 | ".react-router/types/**/*" 7 | ], 8 | "compilerOptions": { 9 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 10 | "types": ["node", "vite/client"], 11 | "target": "ES2022", 12 | "module": "ES2022", 13 | "moduleResolution": "bundler", 14 | "jsx": "react-jsx", 15 | "rootDirs": [".", "./.react-router/types"], 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": ["./app/*"] 19 | }, 20 | "esModuleInterop": true, 21 | "verbatimModuleSyntax": true, 22 | "noEmit": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-react-router-v7/service/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { reactRouter } from "@react-router/dev/vite"; 2 | import tailwindcss from "@tailwindcss/vite"; 3 | import { defineConfig } from "vite"; 4 | import tsconfigPaths from "vite-tsconfig-paths"; 5 | 6 | export default defineConfig({ 7 | plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], 8 | server: { 9 | hmr: { 10 | protocol: 'ws', 11 | clientPort: 5173 12 | } 13 | }, 14 | optimizeDeps: { 15 | include: [ 16 | 'react', 17 | 'react-dom', 18 | 'react-router', 19 | '@react-router/node' 20 | ] 21 | }, 22 | build: { 23 | commonjsOptions: { 24 | include: [/node_modules/], 25 | transformMixedEsModules: true 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /example-react/README.md: -------------------------------------------------------------------------------- 1 | # React Application Example for Serverless Container Framework 2 | 3 | [Serverless Container Framework (SCF)](https://serverless.com/containers/docs) streamlines the development and deployment of containerized applications on AWS Lambda and/or AWS Fargate ECS. 4 | 5 | This example demonstrates how to build and deploy a React application using SCF. The application is bundled using esbuild and optimized for production deployments on AWS Fargate ECS. 6 | 7 | ## Features 8 | 9 | - **React Framework:** 10 | Builds a client-side React application with a component-based architecture. 11 | - **Fast Bundling with esbuild:** 12 | Uses esbuild for rapid development builds and efficient bundling. 13 | - **Static Asset Serving:** 14 | Supports serving static assets and client-side routing. 15 | - **Flexible Compute Options:** 16 | Configured for AWS Fargate ECS to accommodate larger bundle sizes that might exceed AWS Lambda request/response limits. 17 | 18 | ## Prerequisites 19 | 20 | **Docker:** 21 | Install and start Docker Desktop. ([Get Docker](https://www.docker.com)) 22 | 23 | **Serverless Framework:** 24 | Install globally: 25 | ```bash 26 | npm i -g serverless 27 | ``` 28 | 29 | **Node.js & npm:** 30 | Ensure you have a recent Node.js LTS version installed. 31 | 32 | **AWS Credentials:** 33 | Set up your AWS credentials (via environment variables or profiles) for SCF deployment. 34 | 35 | ## Configuration 36 | 37 | The SCF configuration is defined in the `serverless.containers.yml` file at the project root: 38 | 39 | ```yaml 40 | name: react 41 | 42 | deployment: 43 | type: awsApi@1.0 44 | 45 | containers: 46 | service: 47 | src: ./service 48 | routing: 49 | pathPattern: /* 50 | pathHealthCheck: /health 51 | environment: 52 | HELLO: world 53 | compute: 54 | # awsLambda is not recommended for this React app. 55 | # The bundled app may exceed AWS Lambda's request/response size limits. 56 | type: awsFargateEcs 57 | ``` 58 | 59 | ## Project Structure 60 | 61 | A typical project structure for this React example: 62 | ``` 63 | example-react/ 64 | ├── serverless.containers.yml # SCF configuration file 65 | └── service/ 66 | ├── package.json # Project configuration and dependencies 67 | ├── public/ # Static assets (HTML, CSS, images, etc.) 68 | ├── server.js # Server entrypoint for serving the React app 69 | └── src/ 70 | ├── index.jsx # React application entrypoint 71 | └── (other components) # React components and logic 72 | ``` 73 | 74 | ## Development 75 | 76 | For local development, use Serverless Container Framework's development mode: 77 | ```bash 78 | serverless dev 79 | ``` 80 | This will automatically start the development environment with hot reloading and AWS-like routing. 81 | 82 | ## Deployment 83 | 84 | Deploy your React application to AWS by running: 85 | ```bash 86 | serverless deploy 87 | ``` 88 | SCF takes care of building the container image (using the provided Dockerfile) and provisioning the necessary resources. 89 | 90 | ## Cleanup 91 | 92 | To remove the deployed AWS resources, run: 93 | ```bash 94 | serverless remove --force --all 95 | ``` 96 | 97 | ## Additional Resources 98 | 99 | - [Serverless Container Framework Documentation](https://serverless.com/containers/docs) 100 | - [React Documentation](https://reactjs.org/docs/getting-started.html) 101 | - [esbuild Documentation](https://esbuild.github.io) 102 | - [Docker Documentation](https://docs.docker.com) 103 | - [AWS Fargate Documentation](https://aws.amazon.com/fargate) -------------------------------------------------------------------------------- /example-react/serverless.containers.yml: -------------------------------------------------------------------------------- 1 | name: react 2 | 3 | deployment: 4 | type: awsApi@1.0 5 | 6 | containers: 7 | service: 8 | src: ./service 9 | routing: 10 | pathPattern: /* 11 | pathHealthCheck: /health 12 | environment: 13 | HELLO: world 14 | compute: 15 | # awsLambda is not recommended for this react app. 16 | # Your bundled react app will grow to be larger than 17 | # the req/res size limit for AWS ALB and AWS Lambda. 18 | type: awsFargateEcs 19 | -------------------------------------------------------------------------------- /example-react/service/esbuild.config.js: -------------------------------------------------------------------------------- 1 | import { build } from 'esbuild'; 2 | import { promises as fs } from 'fs'; 3 | import { join } from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __dirname = fileURLToPath(new URL('.', import.meta.url)); 7 | 8 | /** 9 | * Builds the React application using esbuild and copies all static assets 10 | * to the build directory for production. 11 | * 12 | * @param {Object} options - Build configuration options. 13 | * @param {string} options.entry - The entry file for the React application. 14 | * @returns {Promise} Resolves when the build and copy operations are complete. 15 | */ 16 | async function buildApp({ entry = join(__dirname, 'src', 'index.jsx') } = {}) { 17 | try { 18 | // Ensure build directory exists 19 | await fs.mkdir(join(__dirname, 'build'), { recursive: true }); 20 | 21 | // Build the JavaScript bundle for the React app 22 | await build({ 23 | entryPoints: [entry], 24 | bundle: true, 25 | outfile: join(__dirname, 'build', 'bundle.js'), 26 | loader: { '.js': 'jsx', '.jsx': 'jsx' }, 27 | sourcemap: false, // Disable sourcemaps for production 28 | target: ['es2015'], 29 | minify: true, 30 | define: { 31 | 'process.env.NODE_ENV': '"production"' 32 | } 33 | }); 34 | console.log('JavaScript bundle built successfully.'); 35 | 36 | // Copy the entire public directory to build 37 | await copyPublicToBuild(); 38 | console.log('Public assets copied to build directory.'); 39 | } catch (error) { 40 | console.error('Build Error:', error); 41 | process.exit(1); 42 | } 43 | } 44 | 45 | /** 46 | * Copies all files from the public directory to the build directory. 47 | * 48 | * @returns {Promise} Resolves when all files have been copied. 49 | */ 50 | async function copyPublicToBuild() { 51 | const publicDir = join(__dirname, 'public'); 52 | const buildDir = join(__dirname, 'build'); 53 | 54 | async function copyRecursive(src, dest) { 55 | const entries = await fs.readdir(src, { withFileTypes: true }); 56 | await fs.mkdir(dest, { recursive: true }); 57 | 58 | for (const entry of entries) { 59 | const srcPath = join(src, entry.name); 60 | const destPath = join(dest, entry.name); 61 | 62 | if (entry.isDirectory()) { 63 | await copyRecursive(srcPath, destPath); 64 | } else { 65 | await fs.copyFile(srcPath, destPath); 66 | } 67 | } 68 | } 69 | 70 | await copyRecursive(publicDir, buildDir); 71 | } 72 | 73 | buildApp(); -------------------------------------------------------------------------------- /example-react/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-react", 3 | "version": "1.0.0", 4 | "description": "A simple React app deployed to containers using esbuild.", 5 | "type": "module", 6 | "main": "server.js", 7 | "scripts": { 8 | "build": "NODE_ENV=production node esbuild.config.js", 9 | "start": "NODE_ENV=production node server.js", 10 | "dev": "node dev.js" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-helmet-async": "^2.0.5", 16 | "react-router-dom": "^6.3.0" 17 | }, 18 | "devDependencies": { 19 | "chokidar": "^3.5.3", 20 | "esbuild": "^0.17.0" 21 | }, 22 | "keywords": [], 23 | "author": "", 24 | "license": "ISC" 25 | } 26 | -------------------------------------------------------------------------------- /example-react/service/public/css/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | width: 100%; 10 | height: 100%; 11 | font-family: "Roboto Mono", monospace; 12 | font-optical-sizing: auto; 13 | font-weight: 400; 14 | font-style: normal; 15 | } 16 | 17 | img { 18 | -webkit-user-drag: none; 19 | } 20 | 21 | body { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | overflow: hidden; /* Prevents scrolling issues due to pseudo-element */ 26 | background: #000000; 27 | color: #ffffff; 28 | } 29 | 30 | body::after { 31 | content: ""; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 100%; 36 | height: 100%; 37 | background-image: url("/images/background.png"); 38 | background-size: cover; 39 | background-repeat: no-repeat; 40 | background-position: center; 41 | background-attachment: fixed; 42 | opacity: 0; 43 | animation: fadeIn 0.75s ease-in forwards; 44 | z-index: -1; /* Places background behind content */ 45 | } 46 | 47 | a { 48 | color: #fd5750; 49 | text-decoration: none; 50 | } 51 | 52 | h1 { 53 | color: white; 54 | font-size: 2rem; 55 | font-weight: 500; 56 | text-align: center; 57 | margin: 20px 0; 58 | } 59 | 60 | .container { 61 | display: flex; 62 | flex-direction: column; 63 | align-items: center; 64 | width: 90%; /* Fluid width */ 65 | max-width: 1200px; /* Sets a max width for larger screens */ 66 | padding: 20px; 67 | text-align: center; 68 | } 69 | 70 | .logo { 71 | max-width: 400px; /* Controls logo size for responsiveness */ 72 | width: 100%; 73 | height: auto; 74 | margin-bottom: 40px; 75 | opacity: 0; 76 | transform: scale(0.75); /* Start scaled at 75% */ 77 | animation: fadeInZoom 2.5s ease-in-out forwards; /* 2.5s duration for zoom effect */ 78 | } 79 | 80 | .info { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | font-size: 1.2rem; 85 | color: rgba(255, 255, 255, 0.7); 86 | margin-top: 5px; 87 | opacity: 0; 88 | animation: fadeIn 2s ease-in-out 1.5s forwards; 89 | } 90 | 91 | /** 92 | * Animations 93 | */ 94 | @keyframes fadeIn { 95 | from { 96 | opacity: 0; 97 | } 98 | to { 99 | opacity: 1; 100 | } 101 | } 102 | 103 | @keyframes fadeInZoom { 104 | from { 105 | opacity: 0; 106 | transform: scale(0.85); /* Start at 85% */ 107 | } 108 | to { 109 | opacity: 1; 110 | transform: scale(1); /* End at 100% */ 111 | } 112 | } 113 | 114 | /** 115 | * Styles for the fixed top navigation bar used for routing. 116 | */ 117 | .nav-header { 118 | position: fixed; 119 | top: 0; 120 | width: 100%; 121 | padding: 10px 0; 122 | z-index: 1000; 123 | } 124 | 125 | .nav-ul { 126 | display: flex; 127 | justify-content: center; 128 | list-style: none; 129 | margin: 0; 130 | padding: 0; 131 | } 132 | 133 | .nav-li { 134 | margin: 0 15px; 135 | } 136 | 137 | .nav-link { 138 | color: #fff; 139 | text-decoration: none; 140 | } 141 | 142 | .nav-link:hover { 143 | text-decoration: underline; 144 | } 145 | 146 | /* Main content top padding to avoid content being hidden behind the fixed nav bar */ 147 | main { 148 | padding-top: 60px; 149 | } 150 | -------------------------------------------------------------------------------- /example-react/service/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-react/service/public/images/background.png -------------------------------------------------------------------------------- /example-react/service/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-react/service/public/images/favicon.png -------------------------------------------------------------------------------- /example-react/service/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless/containers/e80134de4a176f42fd2055292874d71ca1a05485/example-react/service/public/images/logo.png -------------------------------------------------------------------------------- /example-react/service/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import { promises as fs } from 'fs'; 3 | import { join, extname } from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __dirname = fileURLToPath(new URL('.', import.meta.url)); 7 | const PORT = process.env.PORT || 3000; 8 | const NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | // Define MIME types for different file extensions 11 | const contentTypes = { 12 | '.html': 'text/html', 13 | '.js': 'text/javascript', 14 | '.css': 'text/css', 15 | '.json': 'application/json', 16 | '.png': 'image/png', 17 | '.jpg': 'image/jpeg', 18 | '.gif': 'image/gif', 19 | '.svg': 'image/svg+xml', 20 | '.ico': 'image/x-icon' 21 | }; 22 | 23 | // Validate environment 24 | if (!['development', 'production'].includes(NODE_ENV)) { 25 | console.error('Error: NODE_ENV must be either "development" or "production"'); 26 | process.exit(1); 27 | } 28 | 29 | if (NODE_ENV !== 'production') { 30 | console.warn(`Warning: Running in ${NODE_ENV} mode`); 31 | } 32 | 33 | /** 34 | * Creates and returns an HTTP server that serves static files (built React app) 35 | * from the build directory. 36 | * 37 | * @param {Object} options - Server configuration options. 38 | * @param {number} options.port - Port number to listen on. 39 | * @returns {http.Server} HTTP server instance. 40 | */ 41 | const createServer = async ({ port = PORT } = {}) => { 42 | const server = http.createServer(async (req, res) => { 43 | try { 44 | // Normalize the file path 45 | const filePath = join(__dirname, "build", req.url === "/" ? "index.html" : req.url); 46 | const ext = extname(filePath); 47 | 48 | // If the request is for a static file (has extension), try to serve it directly 49 | if (ext) { 50 | try { 51 | const content = await fs.readFile(filePath); 52 | const contentType = contentTypes[ext] || 'text/html'; 53 | res.writeHead(200, { 'Content-Type': contentType }); 54 | return res.end(content, 'utf-8'); 55 | } catch (error) { 56 | if (error.code === 'ENOENT') { 57 | // Static file not found, fall through to serve index.html 58 | } else { 59 | throw error; 60 | } 61 | } 62 | } 63 | 64 | // For all other routes, serve index.html for client-side routing 65 | const content = await fs.readFile(join(__dirname, 'build', 'index.html')); 66 | res.writeHead(200, { 'Content-Type': 'text/html' }); 67 | res.end(content, 'utf-8'); 68 | 69 | } catch (error) { 70 | // Enhanced error logging 71 | console.error('Server Error Details:', { 72 | message: error.message, 73 | code: error.code, 74 | path: req.url, 75 | stack: error.stack 76 | }); 77 | 78 | // Send more detailed error response 79 | res.writeHead(500, { 'Content-Type': 'text/plain' }); 80 | res.end(`Server Error: ${error.message}`); 81 | } 82 | }); 83 | 84 | return server; 85 | }; 86 | 87 | // Create and start the server 88 | const server = await createServer(); 89 | server.listen(PORT, () => { 90 | console.log(`Server running in ${NODE_ENV} mode`); 91 | }); 92 | 93 | export default createServer; 94 | -------------------------------------------------------------------------------- /example-react/service/src/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * About component of the React application. 5 | * 6 | * @returns {JSX.Element} The rendered About component. 7 | */ 8 | const About = () => { 9 | return ( 10 |
11 |

About Page

12 |

This is a minimal React app with basic routing.

13 |
14 | ); 15 | }; 16 | 17 | export default About; -------------------------------------------------------------------------------- /example-react/service/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Routes, Route, Link } from 'react-router-dom'; 3 | import Home from './Home'; 4 | import About from './About'; 5 | import { Helmet } from 'react-helmet-async'; 6 | 7 | /** 8 | * Navigation bar component that displays links at the top of the page. 9 | * 10 | * @returns {JSX.Element} The rendered navigation bar. 11 | */ 12 | const NavBar = () => { 13 | return ( 14 |
15 | 25 |
26 | ); 27 | }; 28 | 29 | /** 30 | * Main application component with a fixed navigation bar and routing for content. 31 | * 32 | * @returns {JSX.Element} The rendered App component. 33 | */ 34 | const App = () => { 35 | React.useEffect(() => { 36 | document.title = "Serverless Container Framework - React App"; 37 | }, []); 38 | 39 | return ( 40 | <> 41 | 42 | Serverless Container Framework - React App 43 | 44 | 45 |
46 | 47 |
48 | 49 | } /> 50 | } /> 51 | 52 |
53 |
54 | 55 | ); 56 | }; 57 | 58 | export default App; -------------------------------------------------------------------------------- /example-react/service/src/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * Home component that mimics the server HTML response from the Express app. 5 | * It displays the container information and uses assets similar to the Express version. 6 | * 7 | * @returns {JSX.Element} The rendered Home component. 8 | */ 9 | const Home = () => { 10 | return ( 11 |
12 | Logo 13 |
14 | ); 15 | }; 16 | 17 | export default Home; -------------------------------------------------------------------------------- /example-react/service/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import { HelmetProvider } from 'react-helmet-async'; 5 | import App from './App'; 6 | 7 | /** 8 | * Entry point for the React application. 9 | * Initializes the app by rendering the root component inside a BrowserRouter. 10 | */ 11 | const container = document.getElementById('root'); 12 | const root = ReactDOM.createRoot(container); 13 | 14 | root.render( 15 | 16 | 17 | 18 | 19 | 20 | ); --------------------------------------------------------------------------------