@limlabs/limo
Version:
Infrastructure as Code generator
325 lines (215 loc) • 16.1 kB
Markdown
# Limo
Limo is an infrastructure-as-code (IaC) generator for full-stack applications.Inspired by [@shadcn/ui](https://ui.shadcn.com/), this tool aims to set up a well-crafted, maintainable infrastructure workflow inside of your project.
## NextJS Quickstart
To initialize an existing Next.js app with limo, follow these steps:
```bash
# install latest version of pulumi if you don't already have it
curl -fsSL https://get.pulumi.com | sh
# 1. enter your next project directory
cd my-app
# 2. run the init command
npx @limlabs/limo init -n my-app
# 3. Deploy your new fullstack application!
cd infrastructure/your-project && pulumi up
```
Note that you may need to install additional tools, such as the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), when using providers that require cloud-specific authentication.
## Why Limo?
Limo helps you get started on the right foot with your infrastructure, then gets out of the way.
Limo is ideal for situations where:
- You know full stack development in a language like Typescript or PHP, but are new to DevOps
- You've tried one-liners like `sst`, or plugins; they don't work, and you have no idea why
- Your team handles its DevOps and you need to be able to troubleshoot / fix your own infrastructure
For cases where shared dependencies are essential, limo supports extension via custom project and framework definitions (coming soon), giving the best of both worlds: the benefits of limo's organization and workflow, applied to your pre-existing components/modules.
## How It Works
Unlike other shared IaC tools, Limo is not a library. You cannot install it into your project with NPM or Homebrew.
Instead, Limgen defines a standard layout and components for you to use with [Pulumi](https://www.pulumi.com/product/infrastructure-as-code/), a popular open-source IaC framework. The `limo` CLI generates working code into your repository by trying to detect what will work best, based on the tools you're using (such as Docker or NextJS), and configuring your existing files to work automatically with your new infrastructure.
When more info is needed, provides an intuitive, interactive prompt to generate the right configuration for your project, based on your use case.
The result is infrastructure that's easy to inspect, troubleshoot, track, review, and customize to meet your applications' privacy, security, and scale requirements over time.
## Examples
Ligmen contains a number of examples designed to showcase its utility for common scenarios, such as needing a database or blob storage.
Check out the [examples](./examples/) folder for a list of several officially-maintained and tested use cases. Each `Readme.md` should contain instructions on how to get the project up and running.
## Concepts
### ResourceGroup
A Pulumi infrastructure [project](https://www.pulumi.com/docs/iac/concepts/projects/) with its own lifecycle, managed by Limo.
In Pulumi, each resourceGroup has one or more "stacks", which is essentially its own environment.
Each resourceGroup folder has the following layout:
```
resourceGroup/
index.ts # The entry point where pulumi resources are declared
Pulumi.yaml # The Pulumi configuration file generated when running pulumi init or limo init
# ... other resourceGroup-specific files
```
#### ResourceGroup Types
Each resourceGroup has a **type**. A resourceGroup type is an archetype for a specific arrangement of Pulumi resources. In practical terms, these types are used to help quickly bootstrap for common / repeating use cases. These include static sites or Docker services deployed to a specific platform such as AWS.
**Coming soon**
There are two categories of resourceGroup type: built-in and custom. Both follow the same principles but are separated by one core difference: built-in types are included with limo and maintained by the limo core team, whereas custom types are user-defined.
#### Built-in Types
The current resourceGroup types can be found in [`src/resourceGroups/`](./src/resourceGroups/).
Built-in types can be used by passing them as options to `limo init`. For example:
```bash
npx @limlabs/limo init --template fullstack-aws # or '@limlabs/limo init -t fullstack-aws'
```
This will create a new resourceGroup of type `fullstack-aws`, which contains all of the resources needed to deploy a Docker-based application to AWS.
Currently supported templates:
### fullstack-aws
Supported frameworks: NextJS, Tanstack
A template for deploying Docker-based applications on AWS. Includes:
- App deployment using AWS Fargate
- CDN using CloudFront
- Load balancing with Application Load Balancer
- VPC networking
- Optional S3 storage
- Optional RDS Postgres database
Configuration options:
- **port** (number, default: 3000) - Port to expose from your application
- **networkType** (choice: "none" | "public" | "private", default: "private") - Network configuration type
- **includeStorage** (choice: "none" | "public" | "private", default: "public") - S3 storage access level
- **includeDb** (boolean, default: false) - Whether to include a RDS Postgres database
### staticsite-aws
Supported frameworks: NextJS
A template for deploying static websites on AWS. Includes:
- S3 storage for static files
- CDN using CloudFront
Configuration options:
- **outputDir** (string, default: "out") - Build output directory containing static files
<a href="#single-vs-multiple-resourcegroups"></a>
#### Single vs. Multiple ResourceGroups
Multiple resourceGroups are often used to split the lifecycle of the infrastructure. For instance, one may choose to update the database servers at a different interval from the application itself:
```
infrastructure/
resourceGroups/
database/ # manage the database-related resource lifecycle
app/ # manage the application without checking database properties
```
The decision of when to use single vs. multiple resourceGroups is largely up to the user. Each resourceGroup generated by `limo init` currently is designed to work independently of the others, though this may evolve in the future.
Keep in mind that while splitting resourceGroups later is possible, it's kind of painful. So after experimenting with the infrastructure components you want to use, head to the whiteboard and plan out how you want to manage your infrastructure long-term before going to production.
### Framework
A **framework** in Limo refers to a set of development platform-specific constraints applied to one or more project types. Frameworks allow projects to be customized and configured automatically to work with popular, community-adopted tools like NextJS or Laravel.
One common task for frameworks is to modify framework-specific files, such as `next.config.ts`, or to generate standard files known to be compatible with that framework, such as a Dockerfile template.
A list of built-in frameworks is available [here](./src/frameworks/)
### Workspace
In Limo, workspaces are conventionally defined by the contents of the `infrastructure` folder. Workspaces contain the following layout:
```
infrastructure/
components/ # where infrastructure components go
resourceGroups/ # where resource groups are defined
utils/ # where utilities for IaC components go
```
Workspaces can contain multiple resourceGroups. While each resourceGroup is maintained separately, it can often be helpful to share code and utilities across resourceGroups within the same repository.
Currently, workspaces are primarily useful for two reasons:
1. Introduces a set of conventions to keep infrastructure organized and consistent
1. Some users may want to update different groups of infrastructure at different times. A common use case for this is to define a shared resource used by multiple application stacks, such as a database server or load balancer. For more information, see the [Single vs. Multiple ResourceGroups](#single-vs-multiple-resourcegroups) section in this documentation.
#### Workspace Location
Workspaces are meant to be subfolders of the project they are managing. For example. if you are building a NextJS application, the folder layout could be similar to the following:
```
<projectRoot>
app/ # or src/app
infrastructure/
package.json
tsconfig.json
... other NextJS files
```
## CLI Reference
To run the `limo` CLI in interactive mode, use the following syntax:
`npx limo <subcommand> [options]`
Note: `limo` accepts all interactive optionsas CLI parameters, allowing interactive mode to be skipped. This can be useful for automation and scripting.
### Commands
#### `init`
```bash
npx @limlabs/limo init [options]
```
Initializes a new project from (eventually) one of several project types. Right now the only supported project type is `fullstack-aws`. Creates a workspace if it doesn't already exist.
##### Options
- **directory** - the working directory where limo will execute. Defaults to `process.cwd()`
- **name** - the name of the resourceGroup. This will create a folder in `${directory}/infrastructure/<name>` where the `Pulumi.yaml` and `index.ts` files are located, and where `pulumi` can be invoked to manage a stack
- **template** - The template to use for initialization. Templates define the structure and configuration of your infrastructure.
- **name** - Maps to the resourceGroup type, such as `fullstack-aws`
- **args** - Key-value pairs used to configure the template
- **inputs** - Dynamic inputs that can be used to parameterize migrations and templates
- **framework** - The framework to apply to the initialization. Frameworks have different behaviors that modify files in `${directory}`, and in the workspace (`${directory}/infrastructure`). For example, the `nextjs` framework modifies the `next.config.ts` file to use the appropriate [output](https://nextjs.org/docs/app/api-reference/next-config-js/output) flag. Defaults to auto-detecting based on well-known conventions for usage within each respective ecosystem.
##### Template-Specific Options
The following options only apply to their respective templates, provided interactively or via the `-t` / `--template` argument.
**`fullstack-aws` Options**
- **port** - the port to listen on. If a `Dockerfile` is detected in `${directory}`, it will default to using the value of the Docker [`EXPOSE` instruction](https://docs.docker.com/reference/dockerfile/#expose).
- **includeStorage** - whether to include object storage or not. When set to `true`, an S3 bucket will be created that is available from your application's URL under the root path `/storage`. Defaults to prompt for confirmation.
- **storageAccess** - when `includeStorage` is set to `true`, determines whether S3 bucket can be access from the public internet or not. If set to `public`, the contents of the S3 bucket will be available via the URL `<outputs.cdnHostname>/<S3Key>`, where `outputs.cdnHostname` is the value obtained from running `pulumi stack output` in the `infrastructure/projects/<projectName>` folder. Defaults to prompt for confirmation, skipped if `includeStorage` is `false`.
- **includeDb** - whether to include a database or not. If set to `true`, includes the resources to create an [RDS Postgres](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.AuroraPostgreSQL.html) server in AWS. Currenty only RDS Postgres is supported, but more cloud providers and datbase types will be added soon. Default to prompt for confirmation.
- **networkType** - The type of network to create.
- When set to `public`, only public subnets will be created, and load balancer, ECS Service and database will be routable over the public internet. This is primarily useful for getting started, and for development with non-sensitive data.
- When set to `private`, both private and public subnets will be created. In `private` mode, only the Load Balancer is on the public network; the app and database are restricted to private subnets, which are not accessible from the public internet. Instead, one must use a tunnel, which will be generated automatically alongside the app if a `includeDb=true`.
- Defaults to prompting for confirmation.
**`staticsite-aws` Options**
- **outputDir** - the root directory where build output for the static site is located. Defaults to `out`
#### `add`
```
npx @limlabs/limo add <component> [options]
```
Adds a new component to the `infrastructure/components` folder. Components are usually imported into one or more projects inside the `infrastructure/projects` folder and used alongside other resources from `@pulumi/aws`, `@pulumi/awsx` and others directly.
##### Arguments
- `<component>` - the name of a component to add to your workspace. Supported components can be found [here](./src/components/)
#### `env-pull`
```
npx @limlabs/limo env-pull [options]
```
Pulls known environment variables from a project and writes them to `.env` locally.
##### Options
- **resourceGroupName** - The name of the limo-created resourceGroup to pull environment variables from. Defaults to prompting based on available resourceGroups in the `infrastructure/resourceGroups` folder.
- **stack** - The name of the Pulumi stack to pull environment variables from. Defaults to prompting from Pulumi to obtain select the environment
- **directory** - Directory for the base infrastructure folder. Defaults to current working directory.
## Local Development
### Prerequisites
1. [Pulumi](https://www.pulumi.com/docs/iac/download-install/)
1. [NVM](https://github.com/nvm-sh/nvm)
1. [pnpm](https://pnpm.io/installation)
### Setup
1. Clone the repo
1. `nvm install 22`
1. `nvm use 22`
1. `pnpm install`
### Running Commands
`pnpm dev <subcommand>` will run a command and output in `<repoRoot>/infrastructure`, which is gitignored.
Unlike using the linked or installed version, this does not require running `pnpm build` first.
### Run the unit tests
`pnpm test`
### Update the examples
This command will update the files in each of the `examples/<name>/infrastructure` workspaces by invoking the CLI:
`scripts/update-examples.sh`
Updating the examples can be useful as a sort of "integration test" to make sure that the application executes for each intended use case.
## TODOS
MVP Roadmap:
- [LIMGEN-1: Contributor Code of Conduct](https://linear.app/liminal-sh/issue/LIMGEN-1/contributor-code-of-conduct)
- [LIMGEN-2: Pull request checks](https://linear.app/liminal-sh/issue/LIMGEN-2/pull-request-checks)
- [LIMGEN-3: Automatically detect / select whether to include storage or db](https://linear.app/liminal-sh/issue/LIMGEN-3/automatically-detect-select-whether-to-include-storage-or-db)
- [LIMGEN-4: Documentation foundations](https://linear.app/liminal-sh/issue/LIMGEN-4/documentation-foundations)
- [LIMGEN-5: Tanstack storage example (with S3 integration)](https://linear.app/liminal-sh/issue/LIMGEN-5/tanstack-storage-example-with-s3-integration)
- [LIMGEN-6: Pipeline file generation](https://linear.app/liminal-sh/issue/LIMGEN-6/pipeline-file-generation)
- [LIMGEN-7: Hostname configuration](https://linear.app/liminal-sh/issue/LIMGEN-7/hostname-configuration)
- [LIMGEN-8: Vue 3 static site example](https://linear.app/liminal-sh/issue/LIMGEN-8/vue-3-static-site-example)
- Example - Laravel
- Example - .Net
- Support multiple frameworks at once
- Needed for Laravel
- E2E tests
- Only copy dependencies that are needed
- Lock dep versions
- Finalize networking privacy user experience / options
Future:
- Redis
- Django
- Extensibility - allow custom-defined frameworks, projects, workspaces using a standard lifecycle appropriate to each resource type.
- Foundation concept - platform
- Multiple language support
- DB Provider - Prisma
- DB Provider - Supabase
- DB Provider - Vercel
- DB Provider - Fly.io
- App Provider - AWS Lambda
- App Provider - Fly.io
- App Provider - Netlify
- App Provider - Vercel
- App Provider - Azure ACS
- App Provider - Kubernetes
- Storage Provider - Vercel
- Storage Provider - Tigris
- CDN Provider - Cloudflare
- Use AST parsing to modify Next config