├── .devcontainer ├── devcontainer.json ├── postAttach.sh └── postCreate.sh ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── README.md ├── babel.config.js ├── blog ├── 2022-12-19-welcome-to-lamby-v4.mdx ├── 2023-06-03-tailscale-extension-for-lambda-containers.mdx ├── 2023-06-17-the-elusive-lambda-console-a-specification-proposal.mdx ├── 2023-07-16-goodbye-cold-starts-hello-proactive-initilizations.mdx ├── authors.yml └── youtube.css ├── docs ├── activejob.mdx ├── anatomy.mdx ├── assets.mdx ├── cold-starts.mdx ├── cpu-architecture.mdx ├── custom-domain.mdx ├── database.mdx ├── environment.mdx ├── observability.mdx ├── quick-start.mdx ├── running-tasks.mdx └── webservers.mdx ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ ├── DocLink.js │ └── HomepageFeatures │ │ ├── index.js │ │ └── styles.module.css ├── css │ └── custom.css └── pages │ ├── index.js │ ├── index.module.css │ └── markdown-page.md └── static ├── .nojekyll ├── CNAME └── img ├── blog ├── console │ └── header.png ├── proactive-init │ └── lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png └── tailscale │ ├── header.png │ ├── live-development-proxy-detail.png │ └── live-development-proxy-overview.png ├── docs ├── aws-api-gateway-icon.png ├── aws-elb-icon.png ├── aws_sam_introduction.png ├── circle-ci-trigger-workflow-dark.png ├── circle-ci-trigger-workflow-light.png ├── cold-start-cloudwatch-insights-percentiles-dark.png ├── cold-start-cloudwatch-insights-percentiles.png ├── cold-start-concurrency-dark.png ├── cold-start-concurrency-vs-spilled-dark.png ├── cold-start-concurrency-vs-spilled.png ├── cold-start-concurrency.png ├── devcontainer-console-dark.png ├── devcontainer-console-light.png ├── devcontainer-open-dark.png ├── devcontainer-open-light.png ├── github-actions-deploy.png ├── github-white.png ├── lambda-console-cli-dark.png ├── lambda-console-cli-light.png ├── lambdakiq.png ├── lambdapunch.png ├── lamby-arch-hero.png ├── lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png ├── lamby-cloud-watch-metrics-cold-start-v-proactive-init-light.png ├── lamby-fullstack-serverless.png ├── lamby-rails.png ├── lamby-small.png ├── lamby.png ├── tailwindcss-dark.png ├── tailwindcss-light.png ├── you-are-on-rails-and-lambda-dark.png └── you-are-on-rails-and-lambda-light.png ├── favicon.ico ├── lamby-logo-orig.png ├── lamby-logo-small.png ├── lamby-rails-arch-dark.png ├── lamby-rails-arch.png ├── lamby-rails-containers.jpg └── lamby-rails-dark.jpg /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/javascript-node:18", 3 | "customizations": { 4 | "codespaces": { 5 | "openFiles": [ 6 | "README.md" 7 | ] 8 | }, 9 | "vscode": { 10 | "extensions": [ 11 | "unifiedjs.vscode-mdx" 12 | ] 13 | } 14 | }, 15 | "features": { 16 | "ghcr.io/devcontainers/features/common-utils": {}, 17 | "ghcr.io/devcontainers/features/sshd:latest": {} 18 | }, 19 | "forwardPorts": [3080], 20 | "portsAttributes": { 21 | "3080": { 22 | "label": "Site", 23 | "onAutoForward": "openPreview" 24 | } 25 | }, 26 | "postCreateCommand": ".devcontainer/postCreate.sh", 27 | "postAttachCommand": ".devcontainer/postAttach.sh" 28 | } 29 | -------------------------------------------------------------------------------- /.devcontainer/postAttach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | npm install 5 | npm start 6 | -------------------------------------------------------------------------------- /.devcontainer/postCreate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | git config --global --add safe.directory /workspaces/lamby-site 5 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build & Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - uses: actions/checkout@v3 14 | - run: npm install 15 | - run: npm run build 16 | - name: Deploy Site 17 | uses: peaceiris/actions-gh-pages@v3 18 | if: ${{ github.ref == 'refs/heads/master' }} 19 | with: 20 | github_token: ${{ secrets.GITHUB_TOKEN }} 21 | publish_dir: ./build 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | .vscode 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lamby Site 2 | 3 | Lamby: Simple Rails & AWS Lambda Integration using Rack. 4 | 5 | - Documentation and blog site for Lamby 6 | - Uses Docusaurus as our static site generator. 7 | - Leverages development containers & Codespaces. 8 | - Easily contribute by opening a pull request. 9 | 10 | **[Lamby: Simple Rails & AWS Lambda Integration using Rack.](https://lamby.cloud)** 11 | 12 | ## Contributing 13 | 14 | This project is built for [GitHub Codespcaes](https://github.com/features/codespaces) using the [Development Container](https://containers.dev) specification. Once you have the repo cloned and setup with a dev container using either Codespaces or [VS Code](#using-vs-code), run the following commands. This will install packages and run tests. 15 | 16 | ```shell 17 | npm start 18 | ``` 19 | 20 | #### Using VS Code 21 | 22 | If you have the [Visual Studio Code Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension installed you can easily clone this repo locally, use the "Open Folder in Container..." command. This allows you to use the integrated terminal for the commands above. 23 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /blog/2022-12-19-welcome-to-lamby-v4.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome-to-lamby-v4 3 | title: Welcome Lamby v4! 4 | authors: [kcollins] 5 | tags: [rails, lambda, containers] 6 | --- 7 | 8 | import DocLink from "../src/components/DocLink.js"; 9 | import ThemedImage from "@theme/ThemedImage"; 10 | import useBaseUrl from "@docusaurus/useBaseUrl"; 11 | 12 | I am incredibly happy to announcy Lamby v4 and a major update to our documentation website. Huge shout out to [Docusaurus](https://docusaurus.io) which has made in my opinion the best content-driven static site generator for open-source projects like our Lamby community. 13 | 14 | 15 | 16 | 23 | 24 | So what is new and will v4 break anything? Lots! Mostly new ways of thinking around the same basic architecture. Nothing should break either. Lamby v4's semver change is mostly marketing driven. Here is a short list of what is new. 25 | 26 | ## Updated Starter 27 | 28 | Want to see all this new stuff in action? Use our guide to deploy a new Rails application to AWS Lambda in 5min ⏱️. 29 | 30 | ## Bring Your Own Containers 31 | 32 | Lamby still works with the Zip packaging format, but all documentation on how to use it has been removed. Containers are here to stay and their value goes way past a simple packaging format. 33 | 34 | ```mermaid 35 | %%{init:{'flowchart':{'nodeSpacing': 20, 'rankSpacing': 20}}}%% 36 | flowchart LR 37 | %% Objects 38 | src-furl(Lambda Function URLs) 39 | src-apih(API Gateway HTTP API) 40 | src-apir(API Gateway REST API) 41 | src-alb(Application Load Balancer) 42 | invoke[/invoke/] 43 | lambda[Lambda Function] 44 | subgraph container["Container Image"] 45 | direction LR 46 | ric[RIC -> config/env.Lamby.cmd]:::rics 47 | app[Event -> Rack -> Rails::App]:::pink 48 | os[Operatring System & Packages]:::desc 49 | end 50 | %% Flow 51 | src-furl --> |Event| invoke 52 | src-apih --> |Event v1 or v2| invoke 53 | src-apir --> |Event| invoke 54 | src-alb --> |Event| invoke 55 | invoke --> lambda 56 | lambda --> ric 57 | %% Styles 58 | classDef node fill:#a99ff0,stroke:#fff,stroke-width:4px,color:#000; 59 | classDef pink fill:#fe4f8b,stroke:#fff,stroke-width:4px,color:#fff; 60 | classDef orange fill:#ed8235,stroke:#fff,stroke-width:4px,color:#fff; 61 | classDef cont fill:#c6fffd,stroke:#70d6d2,stroke-width:4px,color:black,font-size:12px; 62 | classDef rics fill:#a99ff0,stroke:#fff,stroke-width:4px,color:#000; 63 | classDef desc fill:white,stroke:#ccc,stroke-width:2px,color:black; 64 | %% ,font-size:12px 65 | class src-apir,src-apih pink 66 | class src-alb orange 67 | class container cont 68 | class ric rics 69 | ``` 70 | 71 | We now encourage bringing your own containers by using Lambda's Runtime Interface Client (RIC). The RIC allows us to use Docker's `CMD` to load Rails and invoke a function. In this case we are loading our Rails application through its config/environment.rb file (.rb extension is implied) and once that is done, calling the new `Lamby.cmd` as the Lambda handler. No more `app.rb` 72 | file needed! 73 | 74 | ```docker title="Dockerfile" 75 | FROM ruby:3.2-bullseye 76 | RUN gem install 'aws_lambda_ric' 77 | ENTRYPOINT [ "/usr/local/bundle/bin/aws_lambda_ric" ] 78 | CMD ["config/environment.Lamby.cmd"] 79 | ``` 80 | 81 | ## Secrets with Crypteia 82 | 83 | The [Crypteia](https://github.com/rails-lambda/crypteia) package is Rust Lambda Extension for any Runtime/Container to preload SSM Parameters as secure environment variables. It takes advantages of `LD_PRELOAD` to seamlessly fetch values from SSM when a process starts and then injects them as natively accesible Ruby `ENV` variables. Our guide's cookiecutter includes Crypteia already for you via a Docker `COPY` command into the Lambda Extension `/opt` directory. 84 | 85 | ```docker title="Dockerfile" 86 | FROM ruby:3.2-bullseye 87 | # highlight-next-line 88 | COPY --from=ghcr.io/rails-lambda/crypteia-extension-debian:1 /opt /opt 89 | ``` 90 | 91 | Usage is simply done by adding variables to your SAM template and accessing the values fetched from SSM like any other environment variable. Please read the Crypteia's [documentation](https://github.com/rails-lambda/crypteia) for full details. 92 | 93 | ```title="template.yaml" 94 | Globals: 95 | Environment: 96 | Variables: 97 | SECRET: x-crypteia-ssm:/myapp/SECRET 98 | ``` 99 | 100 | ```ruby 101 | ENV['SECRET'] # 1A2B3C4D5E6F 102 | ``` 103 | 104 | ## Development Containers 105 | 106 | Described in the guide, our Lamby starter makes use of the [Development Container](https://containers.dev) specification via a [`.devcontainer`](https://github.com/rails-lambda/lamby-cookiecutter/tree/master/%7B%7Bcookiecutter.project_name%7D%7D/.devcontainer) directory. Commonly used with Codespaces, dev containers can be used locally with any editor. 107 | 108 | Our dev container's `Dockerfile` uses the same base image as the one at the root of your project. This helps ensure your development experience, like installing system dependencies and Ruby gems with native extensions, aligns with the same process as your production image. 109 | 110 | We also leverage the devcontainer's `dockerComposeFile` capability to include a MySQL service as well. The Lamby starter also includes a range of [devcontainer features](https://containers.dev/features) which are installed within the Ubuntu development image. For example, Node, Docker in Docker, SSH, and the AWS CLI & SAM CLI. 111 | -------------------------------------------------------------------------------- /blog/2023-06-03-tailscale-extension-for-lambda-containers.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: tailscale-extension-for-lambda-containers 3 | title: Using Tailscale on Lambda for a Live Development Proxy 4 | authors: [kcollins] 5 | tags: [tailscale, websockets, lambda, extension, container] 6 | image: https://lamby.cloud/img/blog/tailscale/header.png 7 | description: Tailscale makes networking easy. Like really easy. It shines in situations where private networks do not allow inbound connections. 8 | --- 9 | 10 | import styles from "./youtube.css"; 11 | 12 | ![Using Tailscale on Lambda for a Live Development Proxy](https://lamby.cloud/img/blog/tailscale/header.png) 13 | 14 | ⚠️ DISCLAIMER: In no way am I advocating for the use of live proxies as a normal way to develop against cloud resources. However in some edge cases, such as developing a new system, live dev proxies or the general use of Tailscale in Lambda could be useful. 15 | 16 | 17 | 18 | ## 🐋 Tailscale on Lambda 19 | 20 | [Tailscale](https://tailscale.com) makes networking easy. Like really easy. It shines in situations where private networks do not allow inbound connections. Tailscale can connect your devices and development environments for easy access to remote resources, or allow those remote systems to access your home or office network devices. 21 | 22 | A few years ago Corey Quinn wrote a Tailscale [Lambda Extension](https://www.lastweekinaws.com/blog/corey-writes-open-source-code-for-lambda-and-tailscale/). It is great and helped a lot of folks. Today, I'd like to share a new project based on Corey's work that makes it even easier to use Tailscale in Lambda Container. Check it out here. 23 | 24 | **[🔗 Tailscale Lambda Extension for Containers](https://github.com/rails-lambda/tailscale-extension)** on GitHub 🐙 25 | 26 | This new version tries to improve upon Corey's work. Initialization is now stable, there are more configuration options, and we even have multi-platform Docker container packages for both `x86_64` and `arm64`. We even have Amazon Linux 2 and Debian/Ubuntu variants. Installation is really easy, simply add one line to your Dockerfile. For example: 27 | 28 | ```dockerfile 29 | FROM public.ecr.aws/lambda/ruby:3.2 30 | RUN yum install -y curl 31 | COPY --from=ghcr.io/rails-lambda/tailscale-extension-amzn:1 /opt /opt 32 | ``` 33 | 34 | Once your container starts, taking to any device within your tailnet can be done by using the local [SOCKS5](https://en.wikipedia.org/wiki/SOCKS) proxy. In the example below, we are using Ruby's [socksify](https://github.com/astro/socksify-ruby) gem. 35 | 36 | ```ruby 37 | require 'socksify/http' 38 | Net::HTTP.socks_proxy('localhost', 1055).start(...) do |http| 39 | # your http code here... 40 | end 41 | ``` 42 | 43 | ## 🔌 ActionCable on Lambda 44 | 45 | How did I use Tailscale for the [Rails on Lambda](https://lamby.cloud) work? A few months ago, I [started work](https://twitter.com/metaskills/status/1647714842550185985) on the last critical part of the Rails ecosystem which did not work on Lambda... [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) & WebSockets. Specifically, I wanted [Hotwire](https://hotwired.dev) to work. 46 | 47 | So far, everything is [working great](https://twitter.com/metaskills/status/1651067256242151424) with our new LambdaCable gem. Eventually it will be a drop-in adapter for ActionCable and join the ranks of other popular alternatives like [AnyCable](https://anycable.io). To bring the project to completion faster, I needed feedback loops that were much faster than deploying code to the cloud. I needed a development proxy! One where my Rails application would receive events from both Lambda's Function URLs and the WebSocket events from API Gateway. Illustrated below with a demo video. 48 | 49 | ![Architecture diagram of the use of a Lambda development proxy for WebSockets with API Gateway.](https://lamby.cloud/img/blog/tailscale/live-development-proxy-overview.png) 50 | 51 |
52 | 61 |
62 | 63 | If you are curious to learn more about how Rails & Lambda work together, check out our [Lamby](https://lamby.cloud) project. The architecture of Lambda Containers works so well with Rails since our framework distills everything from HTTP, Jobs, Events, & WebSocket connections down to Docker's `CMD` interface. The architecture above at the proxy layer was easy to build and connect up to our single delegate function, `Lamby.cmd`. Shown below: 64 | 65 | ![Architecture diagram of the use of a Lambda development proxy for WebSockets with API Gateway.](https://lamby.cloud/img/blog/tailscale/live-development-proxy-detail.png) 66 | 67 | For our Rails application on Lambda, here are the changes we made to leverage this. All outlined in our [WebSockets Demo Pull Request](https://github.com/rails-lambda/websocket-demo/pull/4). 68 | 69 | - Created a `.localdev` folder. Added a copy of our SAM template.yaml for all AWS Resources. 70 | - Made a simple `.localdev/Dockerfile` that included the Tailscale Extension along with basic proxy code. 71 | - Leveraged Lamby's [Local Development Proxy Sever](https://github.com/rails-lambda/lamby/pull/164). 72 | - Ensured our Devcontainers exposed port 3000 to all local network devices so Tailscale could detect the service. 73 | 74 | I hope you find reasons to learn more about Tailscale and how using a SOCKS5 proxy from Lambda could help your development or production needs. More so, I hope you like the new Lambda Extension project of ours making it easy for containerized applications to use. Drop us a comment if you do. 75 | 76 | **[🔗 Tailscale Lambda Extension for Containers](https://github.com/rails-lambda/tailscale-extension)** on GitHub 🐙 77 | -------------------------------------------------------------------------------- /blog/2023-06-17-the-elusive-lambda-console-a-specification-proposal.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: the-elusive-lambda-console-a-specification-proposal 3 | title: The Elusive Lambda Console; A Specification Proposal. 4 | authors: [kcollins] 5 | tags: [rails, lambda, console, specification, runner, tasks, interaction] 6 | image: img/blog/console/header.png 7 | --- 8 | 9 | import Header from '@site/static/img/blog/console/header.png'; 10 | import ThemedImage from "@theme/ThemedImage"; 11 | import useBaseUrl from "@docusaurus/useBaseUrl"; 12 | 13 | 14 | 15 | 16 | 17 | After years of smashing Cloud & Rails together, I've come up with an idea. Better than an idea, a working specification! One where us [Rails & Lambda](https://lamby.cloud) enthusiasts can once again "console into" our "servers" and execute CLI tasks like migrations or interact via our beloved IRB friend, the Rails console. Today, I would like to present, the [Lambda Console](https://github.com/rails-lambda/lambda-console) project. An open specification proposal for any AWS Lambda runtime to adopt. 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | ## Lambda Console 32 | 33 | ```shell 34 | npm install -g lambda-console-cli 35 | ``` 36 | 37 | The Lambda Console is a CLI written in Node.js that will interactively create an AWS SDK session for you to invoke your Lambda functions with two types of modes. 38 | 39 | 1. CLI Runner 40 | 2. Interactive Commands 41 | 42 | Think of the CLI Runner as a bash prompt. You can run any process command or interact with the filesystem or environment. For Rails users, running rake tasks or DB migrations. These tasks assume the Lambda task root as the present working directory. 43 | 44 | Interactive commands however are evaluated in the context of your running application. For Ruby and Rails applications, this simulates IRB (Interactive Ruby Shell). For [Lamby](https://lamby.cloud) users, this mode simulates the Rails console. Making it easy for users to query their DB or poke their models and code. 45 | 46 | ## The Proposal 47 | 48 | There is nothing about the [Lambda Console](https://github.com/rails-lambda/lambda-console) that is coupled to Ruby or Rails. The idea is simple, as a Lambda community, could we do the following? 49 | 50 | 1. Finalize a Lambda Console request/response specification. 51 | 2. Create more runtime-specific language implementations. 52 | 2. Build an amazing CLI client for any runtime. 53 | 54 | Here is what we have today. The request specification, a simple [event structure](https://github.com/rails-lambda/lambda-console#event-structure) that is only a few dozen lines of JSON schema. 55 | 56 | ```json 57 | { "X_LAMBDA_CONSOLE": { "run": "cat /etc/os-release" } } 58 | ``` 59 | 60 | ```json 61 | { "X_LAMBDA_CONSOLE": { "interact": "User.find(1)" } } 62 | ``` 63 | 64 | Any Lambda runtime code or framework could implement the handling of these event in their own language-specific pakages. You can find the Ruby implementation of these in the Lambda Console's first reference implementations. 65 | 66 | * Ruby: The [lambda-console-ruby](https://github.com/rails-lambda/lambda-console-ruby) gem for any Ruby Lambda. 67 | * Rails: Integrated into the [Lamby](https://github.com/rails-lambda/lamby) v5.0.0 for Rails on Lambda. 68 | 69 | ## The Possibilities 70 | 71 | What I really want is an amazing CLI client. The current Lambda Console CLI was hacked together in a few days using some amazing Node.js tools that make building interactive CLIs so so easy. But I've never done this before. If this type of tooling sounds interesting to you and you like Node.js, let me know! It would be amazing to see implementation packages for these for Node, PHP, Python, and other frameworks using these languages. Here are some ideas on where I could see this going. 72 | 73 | **Live STDOUT & STDERR:** We could take advantage of Lambda's new [Response Streaming](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/) and send output buffers as they happen. 74 | 75 | **Pseudo TTY:** Is there a way to better simulate a real TTY session? Could this even include ANSI colors? 76 | 77 | **Quality of Life Improvements:** Everything from, Allowing the CLI tool to switch modes without restarting it; Creating a command buffer to up arrow navigate history; Prettier UI. 78 | 79 | **Formal Response JSON Schema:** As the features grow, should the response JSON be standardized? For example, if the client wanted to syntax highlight interactive language commands, how would it know what language was being used? We could have a `X_LAMBDA_CONSOLE_LANG` response header. 80 | 81 | What else would you like to see in a Lambda Console client? 82 | 83 | -------------------------------------------------------------------------------- /blog/2023-07-16-goodbye-cold-starts-hello-proactive-initilizations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Goodbye Cold Starts, Hello Proactive Initialization 3 | authors: [kcollins] 4 | tags: [rails, lambda, cold-starts, initialization] 5 | image: img/blog/proactive-init/lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png 6 | --- 7 | 8 | import ThemedImage from "@theme/ThemedImage"; 9 | import useBaseUrl from "@docusaurus/useBaseUrl"; 10 | 11 | As described in [AJ Stuyvenberg's](https://twitter.com/astuyve) post on the topic [Understanding AWS Lambda Proactive Initialization](https://aaronstuyvenberg.com/posts/understanding-proactive-initialization), AWS Lambda may have solved some of your cold start issues for you since March 2023. Stated in an excerpt [from AWS' docs](https://aaronstuyvenberg.com/posts/understanding-proactive-initialization): 12 | 13 | > For functions using unreserved (on-demand) concurrency, Lambda occasionally pre-initializes execution environments to reduce the number of cold start invocations. For example, Lambda might initialize a new execution environment to replace an execution environment that is about to be shut down. If a pre-initialized execution environment becomes available while Lambda is initializing a new execution environment to process an invocation, Lambda can use the pre-initialized execution environment. 14 | 15 | 16 | 17 | This means the [Monitoring with CloudWatch](#monitoring-with-cloudwatch) is just half the picture. But how much is your application potentially benefiting from proactive inits? Since [Lamby v5.1.0](https://github.com/rails-lambda/lamby/pull/169), you can now find out easily using CloudWatch Metrics. To turn metrics on, enable the config like so: 18 | 19 | ```rails title="config/environments/production.rb" 20 | config.lamby.cold_start_metrics = true 21 | ``` 22 | 23 | Lamby will now publish [CloudWatch Embedded Metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html) in the `Lamby` namespace with a custom dimension for each application's name. Captured metrics include counts for Cold Starts vs. Proactive Initializations. Here is an example running sum of 3 days of data for a large Rails application in the `us-east-1` region. 24 | 25 | 36 | 37 | This data shows the vast majority of your initialized Lambda Containers are proactively initialized. Hence, no cold starts are felt by end users or consumers of your function. If you need to customize the name of your Rails application in the CloudWatch Metrics dimension, you can do so using this config. 38 | 39 | ```rails title="config/environments/production.rb" 40 | config.lamby.metrics_app_name = 'MyServiceName' 41 | ``` 42 | -------------------------------------------------------------------------------- /blog/authors.yml: -------------------------------------------------------------------------------- 1 | 2 | kcollins: 3 | name: Ken Collins 4 | title: Principal Engineer & Cloud Architect 5 | url: https://dev.to/metaskills 6 | image_url: https://github.com/metaskills.png 7 | -------------------------------------------------------------------------------- /blog/youtube.css: -------------------------------------------------------------------------------- 1 | .video-container { 2 | position: relative; 3 | padding-bottom: 56.25%; 4 | height: 0; 5 | margin-bottom: 2rem; 6 | } 7 | .video-container iframe { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | -------------------------------------------------------------------------------- /docs/activejob.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: activejob 3 | title: ActiveJob & Background Processing 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | # ActiveJob & Background Processing 8 | 9 | import DocLink from "../src/components/DocLink.js"; 10 | 11 | ## Lambdakiq 12 | 13 | 14 | Lambdakiq - ActiveJob on SQS & Lambda 24 | 25 | 26 | Using ActiveJob on AWS Lambda is a reimagination of the problem for Rails. Instead of starting up long running process that polls for work, we instead use the event-driven architecture of AWS Lambda to our advantage using a gem named [Lambdakiq](https://github.com/rails-lambda/lambdakiq) which is mostly a drop-in replacement for [Sidekiq](https://github.com/mperham/sidekiq). 27 | 28 | It allows you to leverage AWS' managed infrastructure to the fullest extent. Gone are the days of managing pods and long polling processes. Instead AWS delivers messages directly to your Rails' job functions and scales it up and down as needed. Observability is built in using AWS CloudWatch Metrics, Dashboards, and Alarms. Key Features: 29 | 30 | - Distinct web & jobs Lambda functions. 31 | - AWS fully managed polling. Event-driven. 32 | - Maximum 12 retries. Per job configurable. 33 | - Mirror Sidekiq's retry [backoff](https://github.com/mperham/sidekiq/wiki/Error-Handling#automatic-job-retry) timing. 34 | - Last retry is at 11 hours 30 minutes. 35 | - Supports ActiveJob's wait/delay. Up to 15 minutes. 36 | - Dead messages are stored for up to 14 days. 37 | 38 | Learn more on GitHub: https://github.com/rails-lambda/lambdakiq 39 | 40 | ## LambdaPunch 41 | 42 | 43 | Async Processing Using Lambda Extensions 53 | 54 | 55 | You may need lightweight background job processing similiar to how [SuckerPunch](https://github.com/brandonhilkert/sucker_punch) gem works. The only way to do this for Lambda is to use the [LambdaPunnch](https://github.com/rails-lambda/lambda_punch) gem. LambdaPunch is a [Lambda Extensions](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html) that works with the Lambda invoke model. This solution is required if you are using New Relic as described in our guide. 56 | 57 | Learn more on GitHub: https://github.com/rails-lambda/lambda_punch 58 | -------------------------------------------------------------------------------- /docs/anatomy.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: anatomy 3 | title: How Lamby Works 4 | toc_max_heading_level: 3 5 | --- 6 | 7 | import DocLink from "../src/components/DocLink.js"; 8 | import ThemedImage from "@theme/ThemedImage"; 9 | import useBaseUrl from "@docusaurus/useBaseUrl"; 10 | 11 | # How Lamby Works 12 | 13 | The quickest way to see Lamby in action is to create a new Rails app via our cookiecutter project template using our guide. This guide will instead outline what is happening within that starter project allowing you to cherry-pick which files and/or practices from our [cookiecutter](https://github.com/rails-lambda/lamby-cookiecutter) project can be applied to your own application(s). 14 | 15 | :::note 16 | If you copy any template files without using the cookiecutter, remember to customize the `{% include ... %}` template sections with your own app name and remove the curly bracket sections like these `{{ "..." }}` from various string literals. 17 | ::: 18 | 19 | ## Architecture 20 | 21 | Lamby is a Rack adapter that converts AWS Lambda integration events into native [Rack Environment](https://github.com/rack/rack/blob/master/SPEC.rdoc) objects which are sent directly to your application. Lamby can automatically do this when using either Lambda Function URLs, API Gateway HTTP API v1/v2 payloads, API Gateway REST API, or even Application Load Balancer (ALB) integrations. 22 | 23 | ```mermaid 24 | %%{init:{'flowchart':{'nodeSpacing': 20, 'rankSpacing': 20}}}%% 25 | flowchart LR 26 | %% Objects 27 | src-furl(Lambda Function URLs) 28 | src-apih(API Gateway HTTP API) 29 | src-apir(API Gateway REST API) 30 | src-alb(Application Load Balancer) 31 | invoke[/invoke/] 32 | lambda[Lambda Function] 33 | subgraph container["Container Image"] 34 | direction LR 35 | ric[RIC -> config/env.Lamby.cmd]:::rics 36 | app[Event -> Rack -> Rails::App]:::pink 37 | os[Operating System & Packages]:::desc 38 | end 39 | %% Flow 40 | src-furl --> |Event| invoke 41 | src-apih --> |Event v1 or v2| invoke 42 | src-apir --> |Event| invoke 43 | src-alb --> |Event| invoke 44 | invoke --> lambda 45 | lambda --> ric 46 | %% Styles 47 | classDef node fill:#a99ff0,stroke:#fff,stroke-width:4px,color:#000; 48 | classDef pink fill:#fe4f8b,stroke:#fff,stroke-width:4px,color:#fff; 49 | classDef orange fill:#ed8235,stroke:#fff,stroke-width:4px,color:#fff; 50 | classDef cont fill:#c6fffd,stroke:#70d6d2,stroke-width:4px,color:black,font-size:12px; 51 | classDef rics fill:#a99ff0,stroke:#fff,stroke-width:4px,color:#000; 52 | classDef desc fill:white,stroke:#ccc,stroke-width:2px,color:black; 53 | %% ,font-size:12px 54 | class src-apir,src-apih pink 55 | class src-alb orange 56 | class container cont 57 | class ric rics 58 | ``` 59 | 60 | Since Rails applications are Rack applications, Lamby removes the need for a companion [Rack Web Server](https://github.com/rack/rack#supported-web-servers) like Passenger or Puma to be running within your container. Essentially AWS integrations become your web server and scaling is managed by the Lambda service spinning up new container instances, one for each request or down to zero if needed. Lambda instances live for several minutes or more. A small pool of concurrent fuctions can handle a large amount of traffic. 61 | 62 | ## Install Lamby 63 | 64 | Start by adding the Lamby gem to your `Gemfile`. It remains inert until it detects special environment variables present when run on AWS Lambda. When activated, it mostly does a few simple things like ensuring Rails logs to standard out. 65 | 66 | ```ruby 67 | gem 'lamby' 68 | ``` 69 | 70 | ## Runtime Container 71 | 72 | You will need some [`Dockerfile`](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/Dockerfile) to build your Lambda container image. Referencing architecture diagram above mentions something called a RIC (Rick). The RIC is short for the Lambda [Runtime Interface Client](https://github.com/aws/aws-lambda-ruby-runtime-interface-client). It is a small interface packaged as a Ruby gem that acts as the `ENTRYPOINT` for any [OCI](https://opencontainers.org) continer image to run on the AWS Lambda platform. Below you can see that we are using an [Official Ruby](https://hub.docker.com/_/ruby) Ubuntu variant base image, installing the RIC and setting it as the entrypoint. 73 | 74 | ```docker title="Dockerfile" 75 | FROM ruby:3.2-bullseye 76 | 77 | RUN gem install 'aws_lambda_ric' 78 | ENTRYPOINT [ "/usr/local/bundle/bin/aws_lambda_ric" ] 79 | CMD ["config/environment.Lamby.cmd"] 80 | ``` 81 | 82 | The RIC allows us to use Docker's `CMD` to load Rails and invoke a function. In this case we are loading our Rails application through its `config/environment.rb` file (.rb extension is implied) and once that is done, calling the `Lamby.cmd` as the Lambda handler. 83 | 84 | :::note 85 | Our [cookiecutter](https://github.com/rails-lambda/lamby-cookiecutter) project defaults to building a Linux image targeting the `arm64` architecture vs the traditional `x86_64`. However, this is easily changed to accomodate your needs. Check out the section for more details. 86 | ::: 87 | 88 | ## SAM CloudFormation File 89 | 90 | The [`template.yaml`](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/template.yaml) file at the root of your project describes your [Serverless Application](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). Don't worry, we have done some heavy lifting for you. Here is the [Serverless Function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) Resource we start you off with and a brief explanation: 91 | 92 | ```yaml title="template.yaml" 93 | RailsLambda: 94 | Type: AWS::Serverless::Function 95 | Metadata: 96 | DockerContext: . 97 | Dockerfile: Dockerfile 98 | DockerTag: web 99 | Properties: 100 | AutoPublishAlias: live 101 | FunctionUrlConfig: 102 | AuthType: NONE 103 | DeploymentPreference: 104 | Type: AllAtOnce 105 | MemorySize: 1792 106 | PackageType: Image 107 | Timeout: 30 108 | ``` 109 | 110 | - Your Rails function will have a `MemorySize` of 1,792 MB of RAM and 1 vCPU. This is the sweet spot for Rails speed and cost optimization. Remember, you're not running a web server in a single function nor scaling by memory. 111 | - The `FunctionUrlConfig` has been configured to be a public HTTP proxy. You can change this to IAM authentication or swap out to other web server integrations like API Gateway if you need their features. Details in other guides. 112 | - The maximum amount of `Timeout` for an HTTP integration is 30 seconds. 113 | 114 | AWS SAM Introduction 119 | 120 | As your application grows you may end up adding Resources like EventBridge Rules, SQS, S3 Buckets, and IAM Policies. Please take some time to learn how SAM & CloudFormation work. 121 | 122 | - [What Is the AWS Serverless Application Model (AWS SAM)?](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) 123 | - [Quick Intro & Tech Spec for SAM File](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md) 124 | - [What is AWS CloudFormation?](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) 125 | 126 | ## Development Container 127 | 128 | Described in the guide, our Lamby starter makes use of the [Development Container](https://containers.dev) specification via a [`.devcontainer`](https://github.com/rails-lambda/lamby-cookiecutter/tree/master/%7B%7Bcookiecutter.project_name%7D%7D/.devcontainer) directory. Commonly used with Codespaces, dev containers can be used locally with any editor. 129 | 130 | Our dev container's `Dockerfile` uses the same base image as the one at the root of your project. This helps ensure your development experience, like installing system dependencies and Ruby gems with native extensions, aligns with the same process as your production image. 131 | 132 | We also leverage the devcontainer's `dockerComposeFile` capability to include a MySQL service as well. The Lamby starter also includes a range of [devcontainer features](https://containers.dev/features) which are installed within the Ubuntu development image. For example, Node, Docker in Docker, SSH, and the AWS CLI & SAM CLI. 133 | 134 | :::note 135 | Technically you do not need to adopt these devcontainer patterns, but it is really nice to be able to use this container to ensure your CI/CD process is reproducable locally using VS Code or the [Dev Container CLI](https://github.com/devcontainers/cli). More details in the following CI/CD section. 136 | ::: 137 | 138 | ## Deployment & CI/CD 139 | 140 | So how does that CloudFormation file and container image get created within AWS? We use the AWS SAM CLI's `build`, `package`, and `deploy` commands in a single [`bin/deploy`](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/bin/deploy) file. This file also does the following. Feel free to customize your deploy files as needed: 141 | 142 | - Finds/Creates an ECR repository with the same name as your project. 143 | - Sets various ENV variables if not set already. For example, easy to deploy to multiple regions by setting `AWS_REGION`. 144 | - Install gems into local vendor/bundle for deployment via a Docker `COPY`. 145 | 146 | If you used our guide, you likely made your first deploy using VS Code's integrated terminal within the development container. This is critically important since your Ruby gems with native extensions are built within the context of the Ruby Ubuntu image being built and copied to ECR for Lambda to use. 147 | 148 | When automating deployments, the system must have permission to create the needed resources and IAM Roles with permission(s) for your application to work. Most hobby users have admin level access to their own AWS account. For more security minded folks, consider creating a [least privilege user](https://docs.aws.amazon.com/lambda/latest/dg/access-control-identity-based.html) for your deployments with [OpenID Connect](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) identity providers. We found that the `AWSCodeDeployFullAccess` managed policy is often overlooked. 149 | 150 | ### CircleCI 151 | 152 | If `arm64` is your target platform in production, CircleCI make it easy to to do so using their [Arm Execution Environment](https://circleci.com/docs/using-arm/). Our starter includes a CircleCI `config.yml` file that runs tests on each commit or deploy by manually triggering a workflow. It even uses the [Devcontainer CLI](https://github.com/devcontainers/cli) to ensure your CI/CD matches your development experience. Changing between `arm64` and `x86_64` is described in our guide. 153 | 154 | - [Test & Deploy CircleCI Template](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/.circleci/config.yml) 155 | 156 | Deploying requires manually triggering the workflow. Simply select a branch then click "Trigger Workflow" and pass a string parameter called "workflow" with a value of "deploy". Feel free to change this workflow to suite your needs. 157 | 158 | 165 | 166 | ### GitHub Actions 167 | 168 | You can automate both the test and deploy process using our provided GitHub Actions which also leverage the [Dev Container Build and Ruby CI](https://github.com/devcontainers/ci) project. 169 | 170 | - [Deploy GitHub Action Template](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/.github/workflows/deploy.yml) 171 | - [Test GitHub Action Template](https://github.com/rails-lambda/lamby-cookiecutter/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/.github/workflows/test.yml) 172 | 173 | 1. Within your project's GitHub repository [add two Encrypted Secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) using the credentials values above with the environment names of `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 174 | 2. If needed, change the `aws-region` in your `.github/workflows/deploy.yml` file from `us-east-1` to your own region. 175 | 3. Trigger a deploy by navigating to the Deploy workflow and clicking "Run workflow". 176 | 177 | Lambda & Rails deploy with GitHub Actions 182 | -------------------------------------------------------------------------------- /docs/assets.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: assets 3 | title: JavaScript & Assets 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | # JavaScript & Assets 8 | 9 | import DocLink from "../src/components/DocLink.js" 10 | import ThemedImage from '@theme/ThemedImage' 11 | import useBaseUrl from "@docusaurus/useBaseUrl" 12 | 13 | Assets require a to link properly when using API Gateway. For Function URLs, the work out of the box. Both API Gateway and Function URLs can benefit from a CloudFront CDN to cache `/assets` and avoid hitting your backend function on each request. 14 | 15 | ## Serving Static Assets 16 | 17 | Our cookiecutter project leverages Rails' built in ability to serve static assets. We do this by setting this environment variable in your `Dockerfile`. 18 | 19 | ```docker title="Dockerfile" 20 | ENV RAILS_SERVE_STATIC_FILES=1 21 | ``` 22 | 23 | We also add this configuration to your `config/environments/production.rb` file. In this case we are setting the cache control to 30 days, which you can change. The `X-Lamby-Base64` header signals to the Lamby rack adapter that the content requires base64 binary encoding. 24 | 25 | ```ruby title="config/environments/production.rb" 26 | config.public_file_server.headers = { 27 | 'Cache-Control' => "public, max-age=#{30.days.seconds.to_i}", 28 | 'X-Lamby-Base64' => '1' 29 | } 30 | ``` 31 | 32 | ## Adding CloudFront 33 | 34 | [CloudFront](https://aws.amazon.com/cloudfront/) is an amazing CDN and is pretty easy to setup with Rails. Simply point CloudFront to your Rails app and allow the origin to set the cache headers. Because we set the `public_file_server` headers above, everything should work out perfectly. Assuming you have setup a via CloudFront, here is how to setup an behavior for your `/assets` path. From your CloudFront distribution 35 | 36 | - Click the "Behaviors" tab 37 | - Click "Create Behavior" button 38 | - Path Pattern: `/assets/*` 39 | - Select your API Gateway or Function URL origin. 40 | - Compress objects automatically: Yes 41 | - Viewer protocol policy: Redirect HTTP to HTTPS 42 | - Allowed HTTP Methods: GET, HEAD 43 | - Restrict viewer access: No 44 | - 🔘 Cache policy and origin request policy (recommended) 45 | - Cache policy: CachingOptimized 46 | - Origin request policy: None 47 | 48 | ## JavaScript Ready 49 | 50 | Our cookiecutter project is ready to hit the ground running with all the latest Rails defaults for JavaScript & CSS development. We do this by adding Node.js to the development container which is also used to build your production image. See our guide for details. 51 | 52 | For example, we can add the [TailwindCSS Rails](https://github.com/rails/tailwindcss-rails) gem, run the `./bin/rails tailwindcss:install` command, and edit the temporary starter index page like so. Once redeployed, we should see our Hello TailwindCSS page working correctly. 53 | 54 | ```ruby title="Gemfile" 55 | gem 'tailwindcss-rails' 56 | ``` 57 | 58 | ```html title="app/views/application/index.html.erb" 59 |

Hello TailwindCSS

65 | ``` 66 | 67 | 74 | 75 | -------------------------------------------------------------------------------- /docs/cold-starts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: cold-starts 3 | toc_max_heading_level: 2 4 | --- 5 | 6 | # Cold Starts 7 | 8 | import DocLink from "../src/components/DocLink.js"; 9 | import ThemedImage from "@theme/ThemedImage"; 10 | import useBaseUrl from "@docusaurus/useBaseUrl"; 11 | 12 | Cold starts (or init times) are an [incredibly addictive](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html#runtimes-lifecycle) topic. In many cases they can be ignored as an optimization to perform when the time and data suggests action. In practice, the more traffic your function handles the less likely cold starts are an issue since they statistically disappear under the [99th percentile](https://aws.amazon.com/blogs/aws/amazon-cloudwatch-update-percentile-statistics-and-new-dashboard-widgets/). However in rare cases, you may want to optimize for them. This guide can help you make decisions on how to go about it. It also descibes how AWS may be doing this for you already with [Proactive Initialization](#proactive-initialization). 13 | 14 | :::info 15 | Modest sized Rails applications generally boot within 3 to 5 seconds. This happens exactly once for the duration of the function's lifecycle which could last for 30 minutes or more and service a huge amount of traffic with no latency. 16 | ::: 17 | 18 | ## Monitoring with CloudWatch 19 | 20 | You can not optimize what you do not measure. Thankfully, AWS Lambda logs initialization time of your function to CloudWatch logs which you can query using [CloudWatch Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html). 21 | 22 | This query below will give you a nice percentile breakdown for your application's init duration which is the code outside the handler method. Feel free to change the bin bucket from 1 hour to whatever time helps you. For example, using `1d` (1 day) over a longer duration (weeks) allows you to see statistical trends. In general, your `p50` should be under 5 seconds. 23 | 24 | ```coffee 25 | fields @initDuration 26 | | filter ispresent(@initDuration) 27 | | stats pct(@initDuration, 5) as p5, 28 | pct(@initDuration, 50) as p50, 29 | pct(@initDuration, 95) as p95, 30 | pct(@initDuration, 99) as p99 31 | by bin(1h) 32 | ``` 33 | 34 | 45 | 46 | :::info 47 | See the [Proactive Initialization](#proactive-initialization) section for more details on how to use Lamby's new CloudWatch Metrics to measure both cold starts and proactive initialization. 48 | ::: 49 | 50 | ## Proactive Initialization 51 | 52 | As described in [AJ Stuyvenberg's](https://twitter.com/astuyve) post on the topic [Understanding AWS Lambda Proactive Initialization](https://aaronstuyvenberg.com/posts/understanding-proactive-initialization), AWS Lambda may have solved some of your cold start issues for you since March 2023. Stated in an excerpt [from AWS' docs](https://aaronstuyvenberg.com/posts/understanding-proactive-initialization): 53 | 54 | > For functions using unreserved (on-demand) concurrency, Lambda occasionally pre-initializes execution environments to reduce the number of cold start invocations. For example, Lambda might initialize a new execution environment to replace an execution environment that is about to be shut down. If a pre-initialized execution environment becomes available while Lambda is initializing a new execution environment to process an invocation, Lambda can use the pre-initialized execution environment. 55 | 56 | This means the [Monitoring with CloudWatch](#monitoring-with-cloudwatch) is just half the picture. But how much is your application potentially benefiting from proactive inits? Since [Lamby v5.1.0](https://github.com/rails-lambda/lamby/pull/169), you can now find out easily using CloudWatch Metrics. To turn metrics on, enable the config like so: 57 | 58 | ```rails title="config/environments/production.rb" 59 | config.lamby.cold_start_metrics = true 60 | ``` 61 | 62 | Lamby will now publish [CloudWatch Embedded Metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format.html) in the `Lamby` namespace with a custom dimension for each application's name. Captured metrics include counts for Cold Starts vs. Proactive Initializations. Here is an example running sum of 3 days of data for a large Rails application in the `us-east-1` region. 63 | 64 | 71 | 72 | This data shows the vast majority of your initialized Lambda Containers are proactively initialized. Hence, no cold starts are felt by end users or consumers of your function. If you need to customize the name of your Rails application in the CloudWatch Metrics dimension, you can do so using this config. 73 | 74 | ```rails title="config/environments/production.rb" 75 | config.lamby.metrics_app_name = 'MyServiceName' 76 | ``` 77 | 78 | ## Bootsnap by Shopify 79 | 80 | Reducing your Rails applications boot time should be your first optimization option against true cold starts. [Bootsnap](https://github.com/Shopify/bootsnap) has been developed by Shopify to speed up Rails boot time for production environments using a mix of compile and load path caches. When complete, your deployed container will have everything it needs to boot faster! 81 | 82 | How much faster? Generally 1 to 3 seconds depending on your Lambda application. Adding Bootsnap to your Rails Lambda application is straightforward. First, add the gem to your production group in your `Gemfile`. 83 | 84 | ```ruby title="Gemfile" 85 | group :production do 86 | gem 'bootsnap' 87 | end 88 | ``` 89 | 90 | Next, we need to add the Bootsnap caches with your deployed container. Add these lines to your project's `Dockerfile` after your `COPY . .` declaration. It will run two commands. The first is the standard Bootsnap precompile which builds both the Ruby ISeq & YAML caches. The second line loads your application into memory and thus automatically creates the `$LOAD_PATH` cache. 91 | 92 | ```dockerfile title="Dockerfile" 93 | ENV BOOTSNAP_CACHE_DIR=/var/task/tmp/cache 94 | RUN bundle exec bootsnap precompile --gemfile . \ 95 | && bundle exec ruby config/environment.rb 96 | ``` 97 | 98 | Afterward you should be able to verify that Bootsnap's caches are working. Measure your cold starts using a 1 day stats duration for better long term visibility. 99 | 100 | ## Other Cold Start Factors 101 | 102 | Most of these should be considered before using [Provisioned Concurrency](#provisioned-concurrency). Also note, that [Proactive Initialization](#proactive-initialization) may be masking some of these optimizations for you already. That said, consider the following: 103 | 104 | **Client Connect Timeouts** - Your Lambda application may be used by clients who have a low [http open timeout](https://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html#open_timeout-attribute-method). If this is the case, you may have to increase client timeouts, leverage provisioned concurrency, and/or reduce initialization time. 105 | 106 | **Update Ruby** - New versions of Ruby typically boot and run faster. Since our project uses custom Ruby Ubuntu with Lambda containers, updating Ruby should be as easy as changing a few lines of code. 107 | 108 | **Memory & vCPU** - It has been proposed that increased Memory/vCPU could reduce cold starts. We have not seen any evidence of this. For example, we recommend that Rails functions use `1792` for its `MemorySize` equal to 1 vCPU. Any lower would sacrifice response times. Tests showed that increasing this to `3008` equal to 2 vCPUs did nothing for a basic Rails application but cost more. However, if your function does concurrent work doing initialization, consider testing different values here. 109 | 110 | **Lazy DB/Resource Connections** - Rails is really good at lazy loading database connections. This is important to keep the "Init" phase of the [Lambda execution lifecycle](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html#runtimes-lifecycle) quick and under 10s. This allows the first "Invoke" to connect to other resources. To keep init duration low, make sure your application does not eagerly connect to resources. Both ActiveRecord and Memcached w/Dalli are lazy loaded by default. 111 | 112 | **ActiveRecord Schema Cache** - Commonly called Rails' best kept performance feature, the [schema cache](https://kirshatrov.com/2016/12/13/schema-cache/) can help reduce first request response time after Rails is initialized. So it should not help the init time but it could very easily help the first invoke times. 113 | 114 | **Reduce Image Size** - Sort of related to your Ruby version, always make sure that your ECR image is as small as possible. Lambda Containers supports up to 10GB for your image. There is no data on how much this could effect cold starts. So please [share your stories](https://github.com/rails-lambda/lamby/discussions). 115 | 116 | ## Provisioned Concurrency 117 | 118 | :::caution 119 | Provisioned concurrency comes with additional execution costs. Now that we have [Proactive Initialization](#proactive-initialization) it may never be needed. 120 | ::: 121 | 122 | AWS provides an option called [Provisioned Concurrency](https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html) (PC) which allows you to warm instances prior to receiving requests. This lets you execute Lambda functions with super low latency and no cold starts. Besides setting a static PC value, there are two fundamental methods for scaling with Provisioned Concurrency. Please use the [Concurrency CloudWatch Metrics](#concurrency-cloudwatch-metrics) section to help you make a determination on what method is right for you. 123 | 124 | ### Requirements 125 | 126 | Our cookiecutter includes both an `AutoPublishAlias` and an all at once `DeploymentPreference`. The publish alias is needed for provisioned concurrency. You can read about both in AWS "[Deploying serverless applications gradually](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/automating-updates-to-serverless-apps.html)" guide. The code snippets below assume your function's logical resource is `RailsLambda` and you have an alias named `live`. 127 | 128 | ### Auto Scaling 129 | 130 | Here we are creating an [`AWS::AutoScaling::ScalingPolicy`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-policy.html) and a [`AWS::ApplicationAutoScaling::ScalableTarget`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalabletarget.html) which effectively creates a managed CloudWatch Rule that monitors your application to scale it up and down as needed. In this example we set a maximum of `40` and minimal of `5` provisioned instances. We have a `TargetValue` of `0.4` which is a percentage of provisioned concurrency to trigger the CloudWatch Rules via the `ProvisionedConcurrencyUtilization` metric. In this case, lower equals a more aggressive scaling strategy. 131 | 132 | ```yaml title="template.yaml" 133 | Resources: 134 | RailsLambda: 135 | # ... 136 | Properties: 137 | ProvisionedConcurrencyConfig: 138 | ProvisionedConcurrentExecutions: 5 139 | 140 | RailsScalableTarget: 141 | Type: AWS::ApplicationAutoScaling::ScalableTarget 142 | Properties: 143 | MaxCapacity: 40 144 | MinCapacity: 5 145 | ResourceId: !Sub function:${RailsLambda}:live 146 | RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/lambda.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency 147 | ScalableDimension: lambda:function:ProvisionedConcurrency 148 | ServiceNamespace: lambda 149 | DependsOn: RailsLambdaAliaslive 150 | 151 | RailsScalingPolicy: 152 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 153 | Properties: 154 | PolicyName: utilization 155 | PolicyType: TargetTrackingScaling 156 | ScalingTargetId: !Ref RailsScalableTarget 157 | TargetTrackingScalingPolicyConfiguration: 158 | TargetValue: 0.4 159 | PredefinedMetricSpecification: 160 | PredefinedMetricType: LambdaProvisionedConcurrencyUtilization 161 | ``` 162 | 163 | Please read this related article. [Lambda Provisioned Concurrency AutoScaling is Awesome. Make sure you understand how it works!](https://georgemao.medium.com/understanding-lambda-provisioned-concurrency-autoscaling-735eb14040cf) It goes into great detail on how short traffic bursts (common for most of us) can be missed by the standard CloudWatch Alarms and possible remediation to scale up. 164 | 165 | ### Using a Schedule 166 | 167 | In this example we have measured via CloudWatch Metrics (image above) that our concurrent executions never really goes past `40` instances during daytime peak usage. In this case to totally remove cold starts from a small percentage of requests we can draw a big virtual box around the curves above to always keep `40` instances warm during our peak times starting at 6am EST and going back down to `0` Provisioned Concurrency at 11PM EST. Here is how we would do that with a Provisioned Concurrency schedule. 168 | 169 | ```yaml title="template.yaml" 170 | Resources: 171 | RailsScalableTarget: 172 | Type: AWS::ApplicationAutoScaling::ScalableTarget 173 | Properties: 174 | MaxCapacity: 0 175 | MinCapacity: 0 176 | ResourceId: !Sub function:${RailsLambda}:live 177 | RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/lambdaapplication-autoscaling. amazonaws.com/AWSServiceRoleForApplicationAutoScaling_LambdaConcurrency 178 | ScalableDimension: lambda:function:ProvisionedConcurrency 179 | ServiceNamespace: lambda 180 | ScheduledActions: 181 | - ScalableTargetAction: 182 | MaxCapacity: 0 183 | MinCapacity: 0 184 | ScheduledActionName: ScaleDown 185 | Schedule: "cron(0 3 * * ? *)" 186 | - ScalableTargetAction: 187 | MaxCapacity: 40 188 | MinCapacity: 40 189 | ScheduledActionName: ScaleUp 190 | Schedule: "cron(0 10 * * ? *)" 191 | DependsOn: RailsLambdaAliaslive 192 | ``` 193 | 194 | ### Concurrency CloudWatch Metrics 195 | 196 | The graphs below were made using the following managed AWS Lambda CloudWatch Metrics. Please make sure to use your deploy alias of `:live` when targeting your functions resource in these reports. 197 | 198 | - `ConcurrentExecutions` 199 | - `ProvisionedConcurrentExecutions` 200 | - `ProvisionedConcurrencySpilloverInvocations` 201 | 202 | This chart shows that a static `ProvisionedConcurrentExecutions` of `5` can handle most invocations for the first 3 days. Later, for the remaining 4 days, auto scaling was added with a `TargetValue` of `0.4`. Because of the workload's spiky nature, the Invocations look almost 100% provisioned. However, the concurrent executions show otherwise. 203 | 204 | 211 | 212 | Here is a 7 day view from the 4 day mark above. The `TargetValue` is still set to `0.4`. It illustrates how the default CloudWatch Rule for `ProvisionedConcurrencyUtilization` metrics over a 3 minute span are not quick enough to scale PC. It is possible to use a `TargetValue` of `0.1` to force the PC lines to meet the blue. But your cost at this point would be unrealistically high. 213 | 214 | 221 | 222 | ## Gradual Deployments 223 | 224 | As mentioned in the [Provisioned Concurrency](#provisioned-concurrency) section we use a simple `DeploymentPreference` value called `AllAtOnce`. When a deploy happens, Lambda will need to download your new ECR image before your application is initialized. In certain high traffic scenarios along with a potentially slow loading application, deploys can be a thundering herd effect causing your concurrency to spike and a small percentage of users having longer response times. 225 | 226 | Please see AWS' "[Deploying serverless applications gradually](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/automating-updates-to-serverless-apps.html)" guide for full details. However, one way to soften this would be to roll out your new code in 10 minutes total via the `Linear10PercentEvery1Minute` deployment preference. This will automatically create a [AWS CodeDeploy](https://aws.amazon.com/codedeploy/) application and deployments for you. So cool! 227 | -------------------------------------------------------------------------------- /docs/cpu-architecture.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: cpu 3 | title: CPU Architecture 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | import DocLink from "../src/components/DocLink.js"; 8 | 9 | # CPU Architecture 10 | 11 | Our [cookiecutter](https://github.com/rails-lambda/lamby-cookiecutter) project defaults to building a Linux container image targeting the `arm64` architecture vs the traditional `x86_64` cpu type. Applications that use arm64 (AWS Graviton2 processor) can achieve significantly better price and performance than the equivalent workloads running an on x86_64 architecture. 12 | 13 | Deploying arm64 applications is still a relatively new process and requires a few special considerations from local development to your CI/CD tooling. AWS Lambda makes this easy using the [Architectures](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-architectures) setting of the `AWS::Lambda::Function` CloudFormation resource. However, here are a few things you should know. 14 | 15 | ## Docker Images 16 | 17 | Most base Docker images are now build for [multiple platforms](https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/). Consider the following `Dockerfile`: 18 | 19 | ```dockerfile 20 | FROM ruby:3.2-bullseye 21 | ``` 22 | 23 | How does Docker know which platform to use? The anwser is to use the default platform of the host. If you are on a M1 or M2 Mac, arm64 would be the platform used. Which platforms are in a specific base image? We can find out using the `docker manifest` command. For example: 24 | 25 | ```shell 26 | $ docker manifest inspect ruby:3.2 | grep arch 27 | "architecture": "amd64", 28 | "architecture": "arm64", 29 | $ docker manifest inspect | grep arch 30 | "architecture": "amd64", 31 | "architecture": "arm64", 32 | ``` 33 | 34 | All the images in our starter project are multi-platform. This means any host can be used for development. Your computer, Codespaces, etc will use the proper platform image variants. 35 | 36 | ## Deployment Gotchas 37 | 38 | Though there are numerous ways to deploy containers using techniques such as emulation. However, we recommend you following one simple rule. Matching your “Development Host OS/Arch” to that of your target “Deployment Host OS/Arch” provides the least development friction. Use a CI/CD platform that matches your deployment target. 39 | 40 | :::caution 41 | Currently GitHub Actions does not support native arm64 runners. They are [working to add](https://github.com/actions/runner-images/issues/5631) this feature. 42 | ::: 43 | 44 | Our guide has your first deploy happening from your local machine. Since we default to `arm64` this should work fine if you are on a Mac with Apple Silicon. But what if you are on a Windows or Linux system with an `x86_64` architecture? Your function will not work since your application's system dependences (like mysq2) will be compiled for the wrong architecture. Depending on your needs, you may have to switch back to `x86_64` as described below. 45 | 46 | For more information on deployments, see our guide. 47 | 48 | ## Switching to x86_64 49 | 50 | Based off the current state of our [cookiecutter](https://github.com/rails-lambda/lamby-cookiecutter) project, here are the changes required to switch to a `x86_64` deployment target. First, change your CircleCI workflows default machine from `arm.large`` to a standard large. 51 | 52 | ```diff title=".circleci/config.yml" 53 | default-machine: &default-machine 54 | machine: 55 | image: ubuntu-2204:current 56 | docker_layer_caching: true 57 | - resource_class: arm.large 58 | + resource_class: large 59 | ``` 60 | 61 | Now open up your AWS SAM serverless `template.yaml` file, find the `Globals` section and change your Architecture property from `arm64` to `x86_64`. 62 | 63 | ```diff title="template.yaml" 64 | Globals: 65 | Function: 66 | Architectures: 67 | - - arm64 68 | + - x86_64 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/custom-domain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: custom-domain 3 | title: Custom Domain Names 4 | toc_max_heading_level: 3 5 | --- 6 | 7 | # Custom Domain Names 8 | 9 | import DocLink from "../src/components/DocLink.js"; 10 | 11 | ## Function URL 12 | 13 | If you are following our latest pattern, then you are using Lambda's free Function URLs (FURL) which allows to work out of the box. Using a custom domain name with a FURL is as easy as adding CloudFront. To see your FURL in the AWS Console, open the Lambda section -> Click Your Function Name -> Open Versions Tab -> Open Configuration Tab -> Click alias: live. Your Function URL will appear in the upper right. Ex: `uniquestring.lambda-url.us-east-1.on.aws`. Custom Domain Name Steps: 14 | 15 | - [Secure Certificate with ACM](#secure-certificate-with-acm) 16 | - [Simple CloudFront Distribution](#simple-cloudfront-distribution) 17 | - [Create a Route53 Record](#create-a-route53-record) 18 | 19 | ## API Gateway 20 | 21 | For API Gateway Lamby users, their Custom Domain Name featue is the only way to get working correctly by removing the stage path. You can optionally add a CloudFront distribution above this for edge caching. Custom Domain Name Steps: 22 | 23 | - [Secure Certificate with ACM](#secure-certificate-with-acm) 24 | - [API Gateway Custom Domain Names](#api-gateway-custom-domain-names) 25 | - [Simple CloudFront Distribution](#simple-cloudfront-distribution) 26 | - [Create a Route53 Record](#create-a-route53-record) 27 | 28 | ## Individual Steps 29 | 30 | ### Secure Certificate with ACM 31 | 32 | We are going to use [AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) to secure your HTTPS traffic under your custom domain. Again, this assumes your domain is setup in Route53 since you will need to validate the certificate and AWS makes that super easy with DNS. 33 | 34 | - AWS Console -> Certificate Manager 35 | - Click "Request a certificate" button. 36 | - Select "Request a public certificate", and "Request a certificate" button. 37 | - Domain name. Ex: `*.example.com` 38 | - Click "Next" 39 | - Select "DNS validation", and "Review". 40 | - Click "Confirm and request" button. 41 | - Click the tiny disclosure triangle beside your domain name. 42 | - Click the "Create record in Route 53" button then "Create" again in modal. 43 | - Click "Continue" 44 | 45 | Verification will take about 3 minutes. From the Certificate Manager dashboard, you can wait and/or hit the 🔄 button and the Status will change from "Pending validation" to "Issued". 46 | 47 | ### Simple CloudFront Distribution 48 | 49 | Basic reference steps for creating a CloudFront distribution. If you are editing an existing CloudFront distribution, some of these settings might be in your default behavior vs the distribution. 50 | 51 | - Origin: 52 | - Origin Domain: Function URL or API Gateway Custom Domain Name Endpoint Config 53 | - Protocol: HTTPS only 54 | - Minimum Origin SSL Protocol: TLSv1.2 55 | - Origin Path: /production (⚠️ Ignore for FURLs. API Gateway stage name.) 56 | - Add Custom Header: X-Forwarded-Host myapp.example.com 57 | - Default Cache Behavior: 58 | - Compress Objects Automatically: Yes 59 | - Viewer Protocol Policy: Redirect HTTP to HTTPS 60 | - Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE 61 | - Cached HTTP Methods: ✔️ OPTIONS 62 | - Cache key and origin requests: 63 | - 🔘 Legacy cache settings: 64 | - Headers: Include the following headers 65 | - Accept 66 | - Query Strings: All 67 | - Cookies: All 68 | - 🔘 Use origin cache headers 69 | - Settings: 70 | - Price Class: Use only North America and Europe 71 | - Alternate domain name (CNAME): myapp.example.com 72 | - Custom SSL Certificate (select \*.example.com from ACM steps) 73 | 74 | This process takes a while to fully deploy. Once done you will have a CloudFront domain name looking something like `dxxxxxxxxxxxxx.cloudfront.net`. You can now Create a Route53 Record alias for `myapp.example.com` to this CloudFront distribution domain name. 75 | 76 | Feel free to create an additional behavior for the `/assets` path using the `CachingOptimized` cache policy and `None` for the Origin request policy. This will ensure the asset pipeline files are edge-cached and compressed. 77 | 78 | ### Create a Route53 Record 79 | 80 | From here all we need is a DNS entry in Route53 that points to our origin. Typically this would be to your CloudFront distribution. Like the one you may have created for your [Function URLs](#function-url) or your [API Gateway](#api-gateway] custom domain name. 81 | 82 | - AWS Console -> Route 53 -> Hosted zones 83 | - Click on your domain 84 | - Click "Create record" 85 | - Click "Switch to wizard" if not selected already. 86 | - Select "Simple routing" 87 | - Click "Next" 88 | - Click "Define simple record" 89 | - Record name. Ex: `myapp` 90 | - Record type: `A - Routes traffic to an IPv4 address and some AWS resources` 91 | - Value/Route traffic to: (either or) 92 | - Alias to CloudFront distribution 93 | - Endpoint: `dxxxxxxxxxxxxx.cloudfront.net` 94 | - Alias to API Gateway API 95 | - Choose Region: Ex: `us-east-1` 96 | - Choose endpoint: Should autofill, Ex: `d-xxxxxxxxxx.execute-api.us-east-1.amazonaws.com` 97 | - Evaluate target health: `No` 98 | - Click "Define simple record" 99 | - Click "Create records" 100 | 101 | ### API Gateway Custom Domain Names 102 | 103 | Any with API Gateway will need to leverage its Custom Domain Name feature. The only exception would be if you are using an Application Load Balancer without REST API. When completed, your final endpoint would look like this `d-byp3km86t3.execute-api.us-east-1.amazonaws.com` and would then become an CloudFront origin. 104 | 105 | - AWS Console -> API Gateway 106 | - Click "Custom domain names" in the left panel. 107 | - Click "Create" button 108 | - Enter domain name. Ex: `myapp.example.com` 109 | - Use default `TLS 1.2 (recommended)`. 110 | - Endpoint type `Regional`. 111 | - ACM certificate. Select wildcard matching domain from above. 112 | - Click "Create domain name" 113 | 114 | After this has been created, the mappings tab should be selected. From here we need to create an API Mapping to point to your specific API Gateway and stage/path. Assuming it is selected: 115 | 116 | - Click the "API mappings" tab. 117 | - You should see "No API mappings have been configured..." message 118 | - Click "Configure API mappings" button. 119 | - Click "Add new mapping" button. 120 | - Select your API: Ex: `myapp (HTTP - 511n0spvi9)`. 121 | - Select your Stage: Ex: `production`. 122 | - If you see `Stage` and `production` ignore Stage. Known REST bug. 123 | - Leave `Path` empty. 124 | - Click the "Save" button. 125 | -------------------------------------------------------------------------------- /docs/database.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: database 3 | title: Database & VPCs 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | # Database & VPCs 8 | 9 | import DocLink from "../src/components/DocLink.js"; 10 | 11 | Any database supported by Ruby or Rails can be used with Lambda assuming your VPC/Network allows those connections. This guide will not get into the details on how to setup or use various databases options within AWS itself like RDS, Aurora, or DynamoDB. However, we will address a few high level topics along with some conventions in our project. 12 | 13 | ## Our Cookiecutter 14 | 15 | Our project does not create a database but it does have a MySQL service attached to the dev container to faciliate quickly iterating toward using one. The two key files' snippets are below. If you decide to use switch to a different database like PostgreSQL, make adjustments to these files and your `Gemfile` as needed. 16 | 17 | ```yaml title="config/database.yml" 18 | default: &default 19 | adapter: mysql2 20 | username: root 21 | password: <%= ENV["MYSQL_ROOT_PASSWORD"] %> 22 | host: <%= ENV.fetch("MYSQL_HOST") { "localhost" } %> 23 | ``` 24 | 25 | ```yaml title=".devcontainer/docker-compose.yml" 26 | services: 27 | app: 28 | environment: 29 | - MYSQL_HOST=mysql 30 | - MYSQL_ROOT_PASSWORD=root 31 | ``` 32 | 33 | ## VPC Configuration 34 | 35 | Most Rails applications within AWS are deployed to a private subnet(s) within a VPC which allows you to have direct network access to your relational database. For most folks, this is [the default VPC](https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html) which means finding your subnet ids and security groups are fairly easy. Once you have those, add this [VpcConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) to your project's `template.yaml` file within the existing globals section. 36 | 37 | ```yaml title="template.yaml" 38 | Globals: 39 | Function: 40 | VpcConfig: 41 | SubnetIds: 42 | - subnet-09792e6cd06dd59ad 43 | - subnet-0501f3136415021da 44 | SecurityGroupIds: 45 | - sg-07be99aff5fb14557 46 | ``` 47 | 48 | Adding it here will ensure every function within your stack has a common VPC setting. Using a `VpcConfig` should automatically add the `AWSLambdaVPCAccessExecutionRole` managed policy to your Lambda's execution role. If not, you can manually add it to your [`Policies`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-policies) section. 49 | 50 | ```yaml title="template.yaml" 51 | RailsLambda: 52 | Properties: 53 | Policies: 54 | # highlight-next-line 55 | - AWSLambdaVPCAccessExecutionRole 56 | ``` 57 | 58 | ## Database Migrations 59 | 60 | Please see the guide on how to use Lamby's task runner for migrations or other on-demand tasks like Rake. 61 | 62 | ## Using DynamoDB 63 | 64 | In some cases Rails with DynamoDB is an excellent choice. If this sounds right for you, I highly recommend using the [Aws::Record](https://github.com/aws/aws-sdk-ruby-record) gem which leverages the `aws-sdk-dynamodb` in a very Rails like ActiveModel way. Please [share your stories](https://github.com/rails-lambda/lamby/issues/new) with us. 65 | -------------------------------------------------------------------------------- /docs/environment.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: environment 3 | title: ENV Variables & Secrets 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | import DocLink from "../src/components/DocLink.js"; 8 | 9 | # Environment Configuration & Secrets 10 | 11 | Most Rails applications require over a dozen environment variables to configure itself along with other popular gems used. Most notable is ActiveRecord's `DATABASE_URL`. There are numerous ways to configure environment variables ranging from "quick and dirty" by adding secrets to your git repo (⚠️) all the way to a strict "separation of config" from code using countless methods to achieve a proper [Twelve-Factor](https://12factor.net/config) application. We want to cover a few topics that may help you pick and choose what works best for you. 12 | 13 | ## Configuration 14 | 15 | You can add simple configurations to your all of your function's environment using SAM's [global section](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html). Configurations like these are ones that you feel safe comitting to your git repo. 16 | 17 | ```yaml title="template.yaml" 18 | Globals: 19 | Environment: 20 | Variables: 21 | SOME_SERVICE_URL: https://prod.some-service.com/api 22 | ``` 23 | 24 | If you deploy to multiple environments, you can even have these be dynamic by leveraging CloudFormation's [mappings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html). Here is an example that builds on our `RailsEnv` parameter. 25 | 26 | ```yaml title="template.yaml" 27 | Mappings: 28 | SomeService: 29 | staging: 30 | Url: https://staging.some-service.com/api 31 | production: 32 | Url: https://prod.some-service.com/api 33 | # ... 34 | Globals: 35 | Environment: 36 | Variables: 37 | SOME_SERVICE_URL: !FindInMap [SomeService, !Ref RailsEnv, Url] 38 | ``` 39 | 40 | ## Secrets with Crypteia 41 | 42 | The [Crypteia](https://github.com/rails-lambda/crypteia) package is Rust Lambda Extension for any Runtime/Container to preload SSM Parameters as secure environment variables. It takes advantages of `LD_PRELOAD` to seamlessly fetch values from SSM when a process starts and then injects them as natively accesible Ruby `ENV` variables. Our guide's cookiecutter includes Crypteia already for you via a Docker `COPY` command into the Lambda Extension `/opt` directory. 43 | 44 | ```docker title="Dockerfile" 45 | FROM ruby:3.2-bullseye 46 | # highlight-next-line 47 | COPY --from=ghcr.io/rails-lambda/crypteia-extension-debian:1 /opt /opt 48 | ``` 49 | 50 | Usage is simply done by adding variables to your SAM template and accessing the values fetched from SSM like any other environment variable. Please read the Crypteia's [documentation](https://github.com/rails-lambda/crypteia) for full details on how to add [IAM Permissions](https://github.com/rails-lambda/crypteia#iam-permissions) to read SSM Parameters. 51 | 52 | ```title="template.yaml" 53 | Globals: 54 | Environment: 55 | Variables: 56 | SECRET: x-crypteia-ssm:/myapp/SECRET 57 | ``` 58 | 59 | ```ruby 60 | ENV['SECRET'] # 1A2B3C4D5E6F 61 | ``` 62 | 63 | ## About SECRET_KEY_BASE 64 | 65 | Our project disabled Rails encrypted credentials in favor of a more simple `SECRET_KEY_BASE` setting. The starter project places a temporary value for this environment variable in the `config/initializers/secret_key_base.rb` file. Please remove the `ENV['SECRET_KEY_BASE'] = '0123...'` line and use Crypteia as described above. 66 | 67 | ## Modern IAM Role Usage 68 | 69 | If your application uses other AWS resources like EventBridge or S3, you may be using environment variables like `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. **Avoid this pattern.** Instead, please add explicit IAM policies within your `template.yaml` file. They will be attached to your Lambda's [Execution Role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html) and inherently give your Lambda the [needed permissions](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-permissions.html). AWS is constantly making IAM permissions more approachable. There are two high level interfaces within SAM to connect your application to cloud resources. Newest first: 70 | 71 | - [AWS SAM Connectors](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/managing-permissions-connectors.html): Are an AWS SAM abstract resource type, identified as `AWS::Serverless::Connector`, that can be defined in your AWS SAM templates to grant Read and Write access of data and events from a supported AWS resource to another. 72 | - [AWS SAM Policy Templates](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html): Are pre-defined sets of permissions that you can add to your AWS SAM templates to manage access and permissions between your AWS Lambda functions, AWS Step Functions state machines and the resources they interact with. 73 | 74 | If needed, you can use the lower level [`Policies`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-policies) property of your `AWS::Serverless::Function` resource to attach any inline policies to your application's IAM Role. 75 | -------------------------------------------------------------------------------- /docs/observability.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: observability 3 | title: Logging & Observability 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | import DocLink from "../src/components/DocLink.js"; 8 | 9 | # Logging & Observability 10 | 11 | One of the greatest things of AWS Lambda is that you get all the benfits of CloudWatch logging built into the platform. Logging is just a simple Ruby `puts` command away. Here are a few amazing things to help you succeed with good logging and observability patterns in AWS with CloudWatch. 12 | 13 | ## STDOUT is a Must! 14 | 15 | Lambda is a read-only file system. The Lamby gem will set the `RAILS_LOG_TO_STDOUT` environment variable on your behalf. It also freedom patches the core Ruby `Logger` class to force STDOUT. That said, be on the lookout for any rogue disk-based logging you may have to address. Older Rails applications may have to use a pattern like this. 16 | 17 | ```ruby title="config/environments/production.rb" 18 | logger = ActiveSupport::Logger.new(STDOUT) 19 | logger.formatter = ActiveSupport::Logger::SimpleFormatter.new 20 | config.logger = logger 21 | config.log_level = :info 22 | ``` 23 | 24 | ## Using Lograge 25 | 26 | Our installs and configures the [Lograge](https://github.com/roidrage/lograge) gem to reduce CloudWatch data costs while easily allowing CloudWatch Insights to parse and query your logs. If your project is not using Lograge, please consider adding it as we do. 27 | 28 | ```ruby title="Gemfile" 29 | gem 'lograge' 30 | ``` 31 | 32 | ```ruby title="config/environments/production.rb" 33 | config.lograge.enabled = true 34 | config.lograge.formatter = Lograge::Formatters::Json.new 35 | config.lograge.custom_payload do |controller| 36 | { requestid: controller.request.request_id } 37 | end 38 | ``` 39 | 40 | ## CloudWatch Log Insights 41 | 42 | CloudWatch Logs Insights enables you to interactively search and analyze your log data in Amazon CloudWatch Logs. You can perform queries to help you quickly and effectively respond to operational issues. If an issue occurs, you can use CloudWatch Logs Insights to identify potential causes and validate deployed fixes. 43 | 44 | [🎥 YouTube: Analyze Log Data with CloudWatch Logs Insights](https://www.youtube.com/watch?v=2s2xcwm8QrM) 45 | 46 | ## CloudWatch Embedded Metrics 47 | 48 | The [CloudWatch Embedded Metric Format](https://aws.amazon.com/blogs/mt/enhancing-workload-observability-using-amazon-cloudwatch-embedded-metric-format/) enables CloudWatch to ingest complex high-cardinality application data in the form of logs and easily generate actionable metrics and alarms from them. By sending your logs in the new Embedded Metric Format, you can now easily create custom metrics without having to instrument or maintain separate code, while gaining powerful analytical capabilities on your log data. You can get started with embedded metrics by using our [rails-lambda/aws-embedded-metrics](https://github.com/rails-lambda/aws-embedded-metrics) Ruby gem. The following Lamby-friendly libraries use this format: 49 | 50 | - [Crypteia](https://github.com/rails-lambda/crypteia): SSM Parameters as secure ENV variables. 51 | - [Lambdakiq](https://github.com/rails-lambda/lambdakiq): ActiveJob on SQS & Lambda. 52 | 53 | ## New Relic 54 | 55 | Some older Application Performance Monitor (APM) gems can be used with Lambda but you must flush their data after each request in a way that does not impact response performance. You can do this with the [LambdaPunch](https://github.com/rails-lambda/lambda_punch). 56 | 57 | ```ruby 58 | config.lambda.handled_proc = Proc.new do |_event, context| 59 | LambdaPunch.push { NewRelic::Agent.agent.flush_pipe_data } 60 | LambdaPunch.handled!(context) 61 | end 62 | ``` 63 | 64 | 65 | Async Processing Using Lambda Extensions 70 | 71 | -------------------------------------------------------------------------------- /docs/quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: quick-start 3 | toc_max_heading_level: 2 4 | --- 5 | 6 | import DocLink from "../src/components/DocLink.js"; 7 | import ThemedImage from "@theme/ThemedImage"; 8 | import useBaseUrl from "@docusaurus/useBaseUrl"; 9 | 10 | # Quick Start 11 | 12 | ### Deploy a new Rails APP to Lambda in 5 minutes! 13 | 14 | Lamby can be used with Rails v5 application or higher. The quickest way to learn how Rails works on Lambda is to deploy a new application to your AWS account using our [cookiecutter project](https://github.com/rails-lambda/lamby-cookiecutter) template. 15 | 16 | :::note 17 | Before you get started, make sure that you have Docker installed, an AWS account, and Visual Studio Code open. 🚢 [Install Docker](https://www.docker.com) ⛅️ [AWS Account](https://aws.amazon.com/free) 📝 [Install VS Code](https://code.visualstudio.com) 18 | ::: 19 | 20 | ## Rails Project Template 21 | 22 | We created a [rails-lambda/lamby-cookiecutter](https://github.com/rails-lambda/lamby-cookiecutter) repository which allows you to initialize a new project from a GitHub template, commonly called a [cookiecutter](https://github.com/cookiecutter/cookiecutter). Run this terminal command to create a new Rails project with Lamby already installed. 23 | 24 | ```shell 25 | docker run \ 26 | --rm \ 27 | --interactive \ 28 | --volume "${PWD}:/var/task" \ 29 | ghcr.io/rails-lambda/lamby-cookiecutter \ 30 | "gh:rails-lambda/lamby-cookiecutter" 31 | ``` 32 | 33 | You will be prompted for a project name. Choose something short, no spaces, and with underscores for word breaks. Example: `new_service`. 34 | 35 | ``` 36 | project_name [my_awesome_lambda]: 37 | ``` 38 | 39 | ## Development Container 40 | 41 | :::caution 42 | Is your local computer an `x86_64` system such as an older Intel Mac or Windows? Our starter defaults to an `arm64` deployment target. If needed, see our guide on how to switch to `x86_64`. 43 | ::: 44 | 45 | Your new Rails project with Lamby leverages [GitHub Codespaces](https://github.com/features/codespaces) which itself is built atop of the [Development Container](https://containers.dev) specification. In short, this means your project's containers are easy to use by any editor, even outside of Codespaces. 46 | 47 | VS Code makes this incredibly easy. Within a new window, open the command pallet and type "dev container open" and select that action to `Open Folder in Container...`. When prompted, select the project folder created in the previous step. 48 | 49 | 56 | 57 | When the dev container's build is complete, VS Code will display the project folder within the container. This container uses the same base Docker image as the one we are going to deploy to AWS. Unlike the production image, this continer comes with all sorts of build utilties, including the AWS & SAM CLI which we are going to use in the next step to deploy your Rails application to AWS Lambda. 58 | 59 | ## Deploy to Lambda 60 | 61 | Open the integrated terminal by typing `View: Toggle Terminal` in the command pallet. This VS Code terminal is within your development container, an official Ruby Ubuntu image. 62 | 63 | 70 | 71 | First, configure the AWS CLI with your AWS access key and secret. 72 | 73 | ```shell 74 | aws configure 75 | ``` 76 | 77 | Now we can run the deploy script which uses the AWS SAM CLI. 78 | 79 | ```shell 80 | ./bin/deploy 81 | ``` 82 | 83 | :::caution 84 | Deploy scripts are best run via automated CI/CD system such as GitHub Actions. Please see our full deployment section 85 | ::: 86 | 87 | ## Yay! Your're on Rails! 88 | 89 | At the end of the deploy process above, you will see SAM print the outputs for the CloudFormation template being deployed. This includes your Lambda Function URL, a free web server proxy to your Lambda container running Rails. 90 | 91 | ``` 92 | CloudFormation outputs from deployed stack 93 | ------------------------------------------------------------------------------------------- 94 | Outputs 95 | ------------------------------------------------------------------------------------------- 96 | Key RailsLambdaUrl 97 | Description Lambda Function URL 98 | Value https://b4hsncwngvxg6rv67b64r545ly0jrwnk.lambda-url.us-east-1.on.aws/ 99 | ------------------------------------------------------------------------------------------- 100 | 101 | Successfully created/updated stack - new-service-production in us-east-1 102 | ``` 103 | 104 | Open your browser and go to the URL. You should see the familiar welcome to Rails screen. 105 | 106 | 113 | 114 | ## What Just Happened? 115 | 116 | You just deployed a new Rails application to AWS Lambda containers using a basic Ruby Ubuntu Docker base image. Every part of your application is wrapped up neatly in a single CloudFormation stack. This stack has everything you need for a server-side API and/or a client JavaScript application hosted on AWS Lambda. Please take some time to explore how Lamby works in the next sections. 117 | -------------------------------------------------------------------------------- /docs/running-tasks.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: running-tasks 3 | title: Running Tasks & Console 4 | toc_max_heading_level: 3 5 | --- 6 | 7 | import ThemedImage from "@theme/ThemedImage"; 8 | import useBaseUrl from "@docusaurus/useBaseUrl"; 9 | 10 | It can be common for Rails engineers to fire up the [Rails console](https://guides.rubyonrails.org/command_line.html#bin-rails-console) for some quick debugging or to run code like a Rake task. That said, console'ing into a Lambda function (typically done via SSH) is not possible and requires an event-driven & stateless solution. For this, we have the [Lambda Console](https://github.com/rails-lambda/lambda-console) tool. 11 | 12 | 13 | 20 | 21 | 22 | 💁‍♂️ https://github.com/rails-lambda/lambda-console 23 | 24 | Lamby leverages the Lambda Console using our Ruby implementation of the spec via the [lambda-console-ruby](https://github.com/rails-lambda/lambda-console-ruby) gem. Here is a quick overview on how to use it for common Rails tasks. Please see the [Lambda Console](https://github.com/rails-lambda/lambda-console) project for complete documentation on the CLI installation and usage. 25 | 26 | :::caution 27 | To use the Lambda Console, please make sure you are using Lamby v5.0.0 or higher. 28 | ::: 29 | 30 | ## Common Considerations 31 | 32 | Here are some common considerations when using the [Lambda Console](https://github.com/rails-lambda/lambda-console) to run tasks or interactive commands. 33 | 34 | ### Function Timeout 35 | 36 | Each `run` or `interact` event sent will need to respond within your function's timeout. Since HTTP interactions via most AWS services are limited to 30s, so too is your function's default timeout set to that. If your task takes longer than this, consider temporarily increasing the value in your Cloud Formation template or duplicating your function (copy paste) to a new Lambda Function resource dedicated for running console tasks. A Lambda function can have a maximum of 15m execution time. Just remember that API Gateway integration will always be limited to 30s under the function's timeout. So these timeouts can operate independently. 37 | 38 | ### IAM Security & Permissions 39 | 40 | The [Lambda Console](https://github.com/rails-lambda/lambda-console) leverages AWS SDKs to send invoke events to your function(s). This means you are in full control of the security of your function and whom can invoke it with the following IAM actions for your user or role: 41 | 42 | - `lambda:ListFunctions` 43 | - `lambda:InvokeFunction` 44 | 45 | ### Customizing Runner Patterns 46 | 47 | By default, Lamby v5 and higher allows any command to be run. If you want to enforce which commands can be run at the application layer, please use the Lamby config in your `production.rb` environment file. 48 | 49 | ```ruby 50 | config.lamby.runner_patterns.clear 51 | config.lamby.runner_patterns.push %r{\A/bin/foo.*} 52 | ``` 53 | 54 | Here are are clearning/removing the deafault expression pattern of `/.*/` in favor of one that allows any `/bin/foo` command to be run. 55 | 56 | -------------------------------------------------------------------------------- /docs/webservers.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: webservers 3 | title: Web Proxy Integrations 4 | toc_max_heading_level: 2 5 | --- 6 | 7 | # Web Proxy Integrations 8 | 9 | import DocLink from "../src/components/DocLink.js" 10 | 11 | We recommend using Lambda Function URLs which are free, work with JavaScript & CSS assets out of the box, and are easy to map to a custom domain name. However, here are some SAM YAML snippets if you would like to use an alternate web server integration for your application. Remember, Lamby automatically detects which integration you are using. 12 | 13 | :::note 14 | The code snippets below are shown in diff format when compared to the latest files in the cookiecutter project template. 15 | ::: 16 | 17 | ## API Gateway HTTP API 18 | 19 | The [HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) is the most modern integration after Function URLs. It is also really easy to add into our template file. 20 | 21 | ```diff 22 | --- template.yaml 23 | +++ template.yaml 24 | @@ -28,14 +28,22 @@ Resources: 25 | DockerTag: web 26 | Properties: 27 | AutoPublishAlias: live 28 | - FunctionUrlConfig: 29 | - AuthType: NONE 30 | DeploymentPreference: 31 | Type: AllAtOnce 32 | + Events: 33 | + HttpApiProxy: 34 | + Type: HttpApi 35 | + Properties: 36 | + ApiId: !Ref RailsHttpApi 37 | MemorySize: 1792 38 | PackageType: Image 39 | Timeout: 30 40 | 41 | + RailsHttpApi: 42 | + Type: AWS::Serverless::HttpApi 43 | + Properties: 44 | + StageName: !Ref RailsEnv 45 | ``` 46 | 47 | 48 | ## API Gateway REST API 49 | 50 | The [REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) is a little more verbose, but it essentially sets up a simple proxy that Lamby can use. The integration `uri` lines use the `:live` alias since our starter defaults to using an `AutoPublishAlias: live` 51 | 52 | ## Application Load Balancer 53 | 54 | Using Lambda's [ALB Integration](https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html) is a great way to setup your application on a private VPC. However, they limit response payloads to less than 1MB vs. the 6MB limit for API Gateway. Using [Rack::Deflater](https://gist.github.com/metaskills/363e39e2e8cbccf280b5d5804b384bd2) can help with this if needed. These resources make use of the VPC subnets and security groups mentioned in the guide. 55 | 56 | ```diff 57 | --- template.yaml 58 | +++ template.yaml 59 | @@ -28,14 +28,52 @@ Resources: 60 | DockerTag: web 61 | Properties: 62 | AutoPublishAlias: live 63 | - FunctionUrlConfig: 64 | - AuthType: NONE 65 | DeploymentPreference: 66 | Type: AllAtOnce 67 | MemorySize: 1792 68 | PackageType: Image 69 | Timeout: 30 70 | 71 | + RailsLoadBalancer: 72 | + Type: AWS::ElasticLoadBalancingV2::LoadBalancer 73 | + Properties: 74 | + Scheme: internal 75 | + SubnetIds: 76 | + - subnet-09792e6cd06dd59ad 77 | + - subnet-0501f3136415021da 78 | + SecurityGroupIds: 79 | + - sg-07be99aff5fb14557 80 | + 81 | + RailsLoadBalancerHttpsListener: 82 | + Type: AWS::ElasticLoadBalancingV2::Listener 83 | + Properties: 84 | + Certificates: 85 | + - CertificateArn: arn:aws:acm:us-east-1:123456789012:certificate/38613b58-c21e-11eb-8529-0242ac130003 86 | + DefaultActions: 87 | + - TargetGroupArn: !Ref RailsLoadBalancerTargetGroup 88 | + Type: forward 89 | + LoadBalancerArn: !Ref RailsLoadBalancer 90 | + Port: 443 91 | + Protocol: HTTPS 92 | + 93 | + RailsLoadBalancerTargetGroup: 94 | + Type: AWS::ElasticLoadBalancingV2::TargetGroup 95 | + DependsOn: RailsLambdaInvokePermission 96 | + Properties: 97 | + TargetType: lambda 98 | + TargetGroupAttributes: 99 | + - Key: lambda.multi_value_headers.enabled 100 | + Value: true 101 | + Targets: 102 | + - Id: !GetAtt RailsLambda.Arn 103 | + 104 | + RailsLambdaInvokePermission: 105 | + Type: AWS::Lambda::Permission 106 | + Properties: 107 | + FunctionName: !GetAtt RailsLambda.Arn 108 | + Action: "lambda:InvokeFunction" 109 | + Principal: elasticloadbalancing.amazonaws.com 110 | ``` 111 | -------------------------------------------------------------------------------- /docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const path = require("path"); 3 | const lightCodeTheme = require("prism-react-renderer/themes/github"); 4 | const darkCodeTheme = require("prism-react-renderer/themes/dracula"); 5 | 6 | /** @type {import('@docusaurus/types').Config} */ 7 | const config = { 8 | title: "Lamby - Simple Rails & AWS Lambda Integration using Rack", 9 | url: "https://lamby.cloud", 10 | baseUrl: "/", 11 | onBrokenLinks: "throw", 12 | onBrokenMarkdownLinks: "warn", 13 | favicon: "img/favicon.ico", 14 | projectName: "lamby-site", 15 | trailingSlash: false, 16 | organizationName: "rails-lambda", 17 | deploymentBranch: "gh-pages", 18 | i18n: { 19 | defaultLocale: "en", 20 | locales: ["en"], 21 | }, 22 | presets: [ 23 | [ 24 | "classic", 25 | /** @type {import('@docusaurus/preset-classic').Options} */ 26 | ({ 27 | docs: { 28 | sidebarPath: require.resolve("./sidebars.js"), 29 | editUrl: "https://github.com/rails-lambda/lamby-site/tree/master", 30 | }, 31 | blog: { 32 | showReadingTime: true, 33 | }, 34 | theme: { 35 | customCss: require.resolve("./src/css/custom.css"), 36 | }, 37 | }), 38 | ], 39 | ], 40 | markdown: { 41 | mermaid: true, 42 | }, 43 | themes: ["@docusaurus/theme-mermaid"], 44 | themeConfig: 45 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 46 | ({ 47 | navbar: { 48 | title: "Lamby", 49 | logo: { 50 | alt: "Lamby Logo", 51 | src: "img/lamby-logo-small.png", 52 | }, 53 | items: [ 54 | { 55 | href: "/docs/quick-start", 56 | label: "Quick Start", 57 | position: "left", 58 | }, 59 | { 60 | type: "doc", 61 | docId: "anatomy", 62 | position: "left", 63 | label: "Documentation", 64 | }, 65 | { 66 | to: "/blog", 67 | label: "Updates Blog", 68 | position: "left", 69 | }, 70 | { 71 | href: "https://github.com/rails-lambda/lamby", 72 | label: "GitHub", 73 | position: "right", 74 | }, 75 | ], 76 | }, 77 | footer: { 78 | style: "light", 79 | links: [ 80 | { 81 | title: "Guides", 82 | items: [ 83 | { 84 | label: "Quick Start", 85 | to: "/docs/quick-start", 86 | }, 87 | { 88 | label: "Documentation", 89 | to: "/docs/anatomy", 90 | }, 91 | ], 92 | }, 93 | { 94 | title: "Community", 95 | items: [ 96 | { 97 | label: "Twitter @CustomInkTech", 98 | href: "https://twitter.com/custominktech", 99 | }, 100 | { 101 | label: "Technology Blog on Dev.to", 102 | href: "https://dev.to/customink", 103 | }, 104 | ], 105 | }, 106 | { 107 | title: "More", 108 | items: [ 109 | { 110 | label: "Lamby Blog", 111 | to: "/blog", 112 | }, 113 | { 114 | label: "GitHub Project", 115 | href: "https://github.com/rails-lambda/lamby", 116 | }, 117 | ], 118 | }, 119 | ], 120 | copyright: `${new Date().getFullYear()} - Made with ❤️ by Custom Ink | Tech`, 121 | }, 122 | prism: { 123 | theme: lightCodeTheme, 124 | darkTheme: darkCodeTheme, 125 | additionalLanguages: ["ruby", "docker", "yaml"], 126 | }, 127 | metadata: [ 128 | { 129 | name: "keywords", 130 | content: "rails, rack, lambda, serverless, containers", 131 | }, 132 | ], 133 | colorMode: { 134 | defaultMode: "light", 135 | disableSwitch: false, 136 | respectPrefersColorScheme: true, 137 | }, 138 | }), 139 | }; 140 | 141 | module.exports = config; 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lamby", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start --host 0.0.0.0 --port 3080 --no-open", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.2.0", 18 | "@docusaurus/preset-classic": "2.2.0", 19 | "@docusaurus/theme-mermaid": "^2.2.0", 20 | "@mdx-js/react": "^1.6.22", 21 | "clsx": "^1.2.1", 22 | "prism-react-renderer": "^1.3.5", 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "2.2.0" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.5%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "engines": { 42 | "node": ">=16.14" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sidebars.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 3 | const sidebars = { 4 | docsSidebar: [ 5 | { type: 'doc', id: 'quick-start' }, 6 | { type: 'doc', id: 'anatomy' }, 7 | { type: 'doc', id: 'cpu' }, 8 | { type: 'doc', id: 'environment' }, 9 | { type: 'doc', id: 'database' }, 10 | { type: 'doc', id: 'assets' }, 11 | { type: 'doc', id: 'observability' }, 12 | { type: 'doc', id: 'activejob' }, 13 | { type: 'doc', id: 'running-tasks' }, 14 | { type: 'doc', id: 'custom-domain' }, 15 | { type: 'doc', id: 'webservers' }, 16 | { type: 'doc', id: 'cold-starts' } 17 | ] 18 | }; 19 | module.exports = sidebars; 20 | -------------------------------------------------------------------------------- /src/components/DocLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DocNames = { 4 | anatomy: "How Lamby Works", 5 | cpu: "CPU Architecture", 6 | environment: "ENV Variables & Secrets", 7 | assets: "JavaScript & Assets", 8 | deploy: "Build & Deploy", 9 | "custom-domain": "Custom Domain Names", 10 | activejob: "ActiveJob & Background Processing", 11 | observability: "Logging & Observability", 12 | database: "Database & VPCs", 13 | webservers: "Web Proxy Integrations", 14 | }; 15 | 16 | const titleize = function (str) { 17 | return str 18 | .replace(/(_|-)/g, " ") 19 | .split(" ") 20 | .map((w) => w.charAt(0).toUpperCase() + w.toLowerCase().slice(1)) 21 | .join(" "); 22 | }; 23 | 24 | export default function DocLink({ id, name, anchor }) { 25 | const aLink = name || DocNames[id] || titleize(id); 26 | if (anchor) { 27 | return {aLink}; 28 | } else { 29 | return {aLink}; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Event-Based with Rack', 8 | description: ( 9 | <> 10 | No webserver needed! Lamby is as a Rack adapter that converts any AWS Lambda integration into Rack objects that are sent directly to your app. Lamby supports Function URLs, API Gateway (HTTP or REST), and Application Load Balancer (ALB) integrations. Background jobs and other events are supported. 11 | 12 | ), 13 | }, 14 | { 15 | title: 'Container-First Principles', 16 | description: ( 17 | <> 18 | Any containerized Rails application can run on AWS Lambda. Compute can rapidly scale to meet any demand and back down to zero for cost savings. Images have access to 10 GB size limits, 10 GB of memory, & as many as 6 vCPUs. Lamby even promotes the use of the same containers for development. 19 | 20 | ), 21 | }, 22 | { 23 | title: 'Easy IaC to CI/CD', 24 | description: ( 25 | <> 26 | Infrastructure as Code (IaC) will now be front and center in your Rails project folder by using the AWS Serverless Application Model (SAM). Easily create serverless Resources like S3 Buckets, EventBridge Rules, IAM Roles and more. Leverage SAM's CLI to create/update AWS Resources and your container images. 27 | 28 | ), 29 | }, 30 | ]; 31 | 32 | function Feature({Svg, title, description}) { 33 | return ( 34 |
35 |
36 |

{title}

37 |

{description}

38 |
39 |
40 | ); 41 | } 42 | 43 | export default function HomepageFeatures() { 44 | return ( 45 |
46 |
47 |
48 | {FeatureList.map((props, idx) => ( 49 | 50 | ))} 51 |
52 |
53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /src/css/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ifm-color-primary: #00a3d7; 3 | --ifm-color-primary-dark: #0093c2; 4 | --ifm-color-primary-darker: #008bb7; 5 | --ifm-color-primary-darkest: #007297; 6 | --ifm-color-primary-light: #00b3ed; 7 | --ifm-color-primary-lighter: #00bbf7; 8 | --ifm-color-primary-lightest: #19c7ff; 9 | --ifm-code-font-size: 95%; 10 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 11 | } 12 | 13 | [data-theme='dark'] { 14 | --ifm-color-primary: #00c7fc; 15 | --ifm-color-primary-dark: #00b3e3; 16 | --ifm-color-primary-darker: #00a9d6; 17 | --ifm-color-primary-darkest: #008bb0; 18 | --ifm-color-primary-light: #16ceff; 19 | --ifm-color-primary-lighter: #23d1ff; 20 | --ifm-color-primary-lightest: #49d9ff; 21 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 22 | } 23 | 24 | .markdown > img { 25 | margin-bottom: 1rem; 26 | } 27 | 28 | .footer__copyright { 29 | margin-top: 3rem; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 7 | import styles from './index.module.css'; 8 | 9 | function HomepageHeroImage({name}) { 10 | const {siteConfig} = useDocusaurusContext(); 11 | return ( 12 |
13 |
14 |
15 | ); 16 | } 17 | 18 | function HomepageHeader() { 19 | const {siteConfig} = useDocusaurusContext(); 20 | return ( 21 |
22 |
23 |

24 | Simple Rails & AWS Lambda Integration using Rack 25 |

26 |

Event-driven and deeply integrated within AWS, Lambda allows your Rails architecture to be completely reimagined atop fully managed infrastructure resources like Aurora, SQS, S3, CloudWatch, IAM, and much more. Using Lamby can help your engineering teams learn to "program the cloud".

27 |
28 | 29 | Rails on Lambda in 5min ⏱️ 30 | 31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | export default function Home() { 38 | const {siteConfig} = useDocusaurusContext(); 39 | return ( 40 | 43 | 44 | 45 | 46 |
47 | 48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | .heroImageWhimsical { 2 | padding: 16.6vw 0; 3 | background-image: url("/static/img/lamby-rails-containers.jpg"); 4 | background-size: cover; 5 | background-repeat: no-repeat; 6 | background-position: center; 7 | } 8 | 9 | [data-theme='dark'] .heroImageWhimsical { 10 | background-image: url("/static/img/lamby-rails-dark.jpg"); 11 | } 12 | 13 | .heroImageArch { 14 | padding: 15vw 0; 15 | background-image: url("/static/img/lamby-rails-arch.png"); 16 | background-size: cover; 17 | background-repeat: no-repeat; 18 | background-position: center; 19 | } 20 | 21 | [data-theme='dark'] .heroImageArch { 22 | background-image: url("/static/img/lamby-rails-arch-dark.png"); 23 | } 24 | 25 | .heroBanner { 26 | padding: 2rem 0 3rem 0; 27 | text-align: center; 28 | position: relative; 29 | overflow: hidden; 30 | } 31 | 32 | [data-theme='dark'] .heroBanner { 33 | color: white; 34 | background-color: #002634; 35 | } 36 | 37 | @media screen and (min-width: 996px) { 38 | .heroTitle { 39 | padding: 0 20%; 40 | } 41 | } 42 | 43 | .buttons { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | } 48 | -------------------------------------------------------------------------------- /src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/.nojekyll -------------------------------------------------------------------------------- /static/CNAME: -------------------------------------------------------------------------------- 1 | lamby.cloud 2 | -------------------------------------------------------------------------------- /static/img/blog/console/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/blog/console/header.png -------------------------------------------------------------------------------- /static/img/blog/proactive-init/lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/blog/proactive-init/lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png -------------------------------------------------------------------------------- /static/img/blog/tailscale/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/blog/tailscale/header.png -------------------------------------------------------------------------------- /static/img/blog/tailscale/live-development-proxy-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/blog/tailscale/live-development-proxy-detail.png -------------------------------------------------------------------------------- /static/img/blog/tailscale/live-development-proxy-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/blog/tailscale/live-development-proxy-overview.png -------------------------------------------------------------------------------- /static/img/docs/aws-api-gateway-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/aws-api-gateway-icon.png -------------------------------------------------------------------------------- /static/img/docs/aws-elb-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/aws-elb-icon.png -------------------------------------------------------------------------------- /static/img/docs/aws_sam_introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/aws_sam_introduction.png -------------------------------------------------------------------------------- /static/img/docs/circle-ci-trigger-workflow-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/circle-ci-trigger-workflow-dark.png -------------------------------------------------------------------------------- /static/img/docs/circle-ci-trigger-workflow-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/circle-ci-trigger-workflow-light.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-cloudwatch-insights-percentiles-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-cloudwatch-insights-percentiles-dark.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-cloudwatch-insights-percentiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-cloudwatch-insights-percentiles.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-concurrency-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-concurrency-dark.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-concurrency-vs-spilled-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-concurrency-vs-spilled-dark.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-concurrency-vs-spilled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-concurrency-vs-spilled.png -------------------------------------------------------------------------------- /static/img/docs/cold-start-concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/cold-start-concurrency.png -------------------------------------------------------------------------------- /static/img/docs/devcontainer-console-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/devcontainer-console-dark.png -------------------------------------------------------------------------------- /static/img/docs/devcontainer-console-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/devcontainer-console-light.png -------------------------------------------------------------------------------- /static/img/docs/devcontainer-open-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/devcontainer-open-dark.png -------------------------------------------------------------------------------- /static/img/docs/devcontainer-open-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/devcontainer-open-light.png -------------------------------------------------------------------------------- /static/img/docs/github-actions-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/github-actions-deploy.png -------------------------------------------------------------------------------- /static/img/docs/github-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/github-white.png -------------------------------------------------------------------------------- /static/img/docs/lambda-console-cli-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lambda-console-cli-dark.png -------------------------------------------------------------------------------- /static/img/docs/lambda-console-cli-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lambda-console-cli-light.png -------------------------------------------------------------------------------- /static/img/docs/lambdakiq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lambdakiq.png -------------------------------------------------------------------------------- /static/img/docs/lambdapunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lambdapunch.png -------------------------------------------------------------------------------- /static/img/docs/lamby-arch-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-arch-hero.png -------------------------------------------------------------------------------- /static/img/docs/lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-cloud-watch-metrics-cold-start-v-proactive-init-dark.png -------------------------------------------------------------------------------- /static/img/docs/lamby-cloud-watch-metrics-cold-start-v-proactive-init-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-cloud-watch-metrics-cold-start-v-proactive-init-light.png -------------------------------------------------------------------------------- /static/img/docs/lamby-fullstack-serverless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-fullstack-serverless.png -------------------------------------------------------------------------------- /static/img/docs/lamby-rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-rails.png -------------------------------------------------------------------------------- /static/img/docs/lamby-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby-small.png -------------------------------------------------------------------------------- /static/img/docs/lamby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/lamby.png -------------------------------------------------------------------------------- /static/img/docs/tailwindcss-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/tailwindcss-dark.png -------------------------------------------------------------------------------- /static/img/docs/tailwindcss-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/tailwindcss-light.png -------------------------------------------------------------------------------- /static/img/docs/you-are-on-rails-and-lambda-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/you-are-on-rails-and-lambda-dark.png -------------------------------------------------------------------------------- /static/img/docs/you-are-on-rails-and-lambda-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/docs/you-are-on-rails-and-lambda-light.png -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/lamby-logo-orig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-logo-orig.png -------------------------------------------------------------------------------- /static/img/lamby-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-logo-small.png -------------------------------------------------------------------------------- /static/img/lamby-rails-arch-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-rails-arch-dark.png -------------------------------------------------------------------------------- /static/img/lamby-rails-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-rails-arch.png -------------------------------------------------------------------------------- /static/img/lamby-rails-containers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-rails-containers.jpg -------------------------------------------------------------------------------- /static/img/lamby-rails-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails-lambda/lamby-site/95a01a0821b517907e995c9c4b65b10094de5698/static/img/lamby-rails-dark.jpg --------------------------------------------------------------------------------