diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a4fc37a..4f30d36 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -67,7 +67,7 @@ jobs: - name: "Setting deployment" id: deploymentUrl run: | - url="https://"$(lttle deploy --eval "git.ref == 'main'? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") + url="https://"$(lttle deploy --eval "git.ref == 'main'? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") name="https://"$(lttle deploy --eval "git.ref == 'main'? git.ref : preview") echo "url=$url" >> $GITHUB_OUTPUT echo "name=$name" >> $GITHUB_OUTPUT diff --git a/deploy-docs.sh b/deploy-docs.sh index 30ec2a5..0bcdba1 100755 --- a/deploy-docs.sh +++ b/deploy-docs.sh @@ -16,7 +16,7 @@ fi namespace="docs-"$(lttle deploy --eval "git.ref == 'main' ? 'main' : env.GITHUB_HEAD_REF.toSlug() + '-branch'") tag=$(lttle deploy --eval "git.ref == 'main' ? 'latest' : env.GITHUB_HEAD_REF.toSlug() + '-branch'") -docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") -typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.aifrim.com' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.cloud' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") deployDocs "$namespace" "$tag" "$docsURL" "$typesenseFQDN" diff --git a/deploy-typesense.sh b/deploy-typesense.sh index 7296277..e1072c7 100755 --- a/deploy-typesense.sh +++ b/deploy-typesense.sh @@ -16,7 +16,7 @@ fi namespace="docs-"$(lttle deploy --eval "git.ref == 'main' ? 'main' : env.GITHUB_HEAD_REF.toSlug() + '-branch'"); tag=$(lttle deploy --eval "git.ref == 'main' ? 'latest' : env.GITHUB_HEAD_REF.toSlug() + '-branch'") -docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") -typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.aifrim.com' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.cloud' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") deployTypeSense "$namespace" "$tag" "$docsURL" "$typesenseFQDN" \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 92d5342..a99c4a9 100755 --- a/deploy.sh +++ b/deploy.sh @@ -18,8 +18,8 @@ lttle deploy --eval "" --debug-context namespace="docs-"$(lttle deploy --eval "git.ref == 'main' ? 'main' : env.GITHUB_HEAD_REF.toSlug() + '-branch'"); tag=$(lttle deploy --eval "git.ref == 'main' ? 'latest' : env.GITHUB_HEAD_REF.toSlug() + '-branch'") -docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'"); -typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.aifrim.com' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'"); +typesenseFQDN=$(lttle deploy --eval "git.ref == 'main' ? 'docs-search.lttle.cloud' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") deployDocs "$namespace" "$tag" "$docsURL" "$typesenseFQDN" deployTypeSense "$namespace" "$tag" "$docsURL" diff --git a/docker/typesense-scraper.production.config.json b/docker/typesense-scraper.production.config.json index 6ec96de..3750b13 100644 --- a/docker/typesense-scraper.production.config.json +++ b/docker/typesense-scraper.production.config.json @@ -2,7 +2,7 @@ "index_name": "lttle-cloud-docs", "start_urls": [ { - "url": "https://docs.lttle.aifrim.com/" + "url": "https://docs.lttle.cloud/" } ], "selectors": { diff --git a/docs/building-and-deploying/building.md b/docs/building-and-deploying/building.md index b0d94c2..de2296f 100644 --- a/docs/building-and-deploying/building.md +++ b/docs/building-and-deploying/building.md @@ -4,8 +4,381 @@ sidebar_position: 1 # Building -:::note Work in Progress +lttle.cloud provides automatic image building for your applications using [Nixpacks](https://nixpacks.com), eliminating the need to manually create and manage Docker images. When you specify a `build` configuration in your machine or app resources, the CLI automatically builds and pushes container images to lttle.cloud's registry during deployment. -This section is a work in progress. +## How Building Works -::: +When you run `lttle deploy`, the CLI: + +1. **Detects Resources**: Scans your deployment files for machines or apps with `build` configurations +2. **Builds Images**: Uses Nixpacks or Docker to build container images locally +3. **Pushes Images**: Automatically pushes built images to lttle.cloud's private registry +4. **Updates Resources**: Replaces the `build` configuration with the pushed image reference +5. **Deploys**: Proceeds with normal deployment using the built images + +## Build Configuration + +The `build` field supports three modes: + +### Auto Build + +The simplest option - automatically detects your application type and builds accordingly: + +```yaml +machine: + name: my-app + build: auto + resources: + cpu: 1 + memory: 256 +``` + +### Options Build + +Provides more control over the build process: + +```yaml +machine: + name: my-app + build: + options: + dir: ./backend # Build directory (default: ".") + name: my-backend # Image name (default: auto-generated) + tag: v1.0.0 # Image tag (default: "latest") + image: custom/name # Full image reference (overrides name/tag) + resources: + cpu: 1 + memory: 256 +``` + +| Property | Type | Default | Description | +|:---------|:---------|:--------|:-----------------------------------------------| +| `dir` | `string` | `"."` | Directory to build from | +| `name` | `string` | *auto* | Image name (generates UUID if not specified) | +| `tag` | `string` | `"latest"` | Image tag | +| `image` | `string` | *auto* | Full image reference (overrides all other options) | + +### Docker Build + +For projects with existing Dockerfiles: + +```yaml +machine: + name: my-app + build: + docker: + context: . # Build context (default: ".") + dockerfile: Dockerfile.prod # Dockerfile path (default: "Dockerfile") + name: my-docker-app # Image name (default: auto-generated) + tag: production # Image tag (default: "latest") + image: myregistry.com/my-app:v1.0.0 # Full image reference (overrides name/tag) + args: # Build arguments + NODE_ENV: production + API_VERSION: v2 + resources: + cpu: 1 + memory: 256 +``` + +| Property | Type | Default | Description | +|:-------------|:---------|:---------------|:------------------------------------------| +| `context` | `string` | `"."` | Docker build context directory | +| `dockerfile` | `string` | `"Dockerfile"` | Path to Dockerfile | +| `name` | `string` | *auto* | Image name (generates UUID if not specified) | +| `tag` | `string` | `"latest"` | Image tag | +| `image` | `string` | *auto* | Full image reference (overrides all other options) | +| `args` | `object` | *none* | Docker build arguments | + +## Nixpacks Integration + +lttle.cloud uses [Nixpacks](https://nixpacks.com) for automatic builds, which provides zero-configuration support for many languages and frameworks. + +### Supported Languages + +Nixpacks automatically detects and builds applications for: + +- **Node.js** - Detects `package.json`, supports npm/yarn/pnpm +- **Python** - Detects `requirements.txt`, `Pipfile`, `pyproject.toml` +- **Go** - Detects `go.mod`, builds static binaries +- **Rust** - Detects `Cargo.toml`, optimized release builds +- **Java** - Detects Maven (`pom.xml`) or Gradle (`build.gradle`) +- **Ruby** - Detects `Gemfile`, supports Bundler +- **PHP** - Detects `composer.json`, includes common extensions +- **Clojure** - Detects `project.clj` or `build.clj` [Learn more](https://nixpacks.com/docs/providers/clojure) +- **C#/.NET** - Detects `.csproj`, `.sln` files +- **Elixir** - Detects `mix.exs`, supports Phoenix +- **And many more** - See [Nixpacks Language Support](https://nixpacks.com/docs/providers) for the complete list + +### Language-Specific Examples + +#### Node.js Application + +```yaml +app: + name: web-app + build: auto + resources: + cpu: 1 + memory: 512 + expose: + web: + port: 3000 + external: + protocol: https +``` + +Nixpacks will: +- Detect `package.json` +- Install dependencies with npm/yarn/pnpm +- Build the application if a build script exists +- Start with `npm start` or the `start` script + +#### Python Application + +```yaml +machine: + name: api-server + build: auto + resources: + cpu: 1 + memory: 256 + environment: + PORT: "8000" +``` + +Nixpacks will: +- Detect `requirements.txt`, `Pipfile`, or `pyproject.toml` +- Install Python and dependencies +- Start with detected entry point or `python main.py` + +#### Go Application + +```yaml +machine: + name: go-service + build: + options: + dir: ./cmd/server + resources: + cpu: 1 + memory: 128 +``` + +Nixpacks will: +- Detect `go.mod` +- Build a static binary +- Create minimal container image + +### Nixpacks Configuration + +You can customize Nixpacks behavior with a `nixpacks.toml` file: + +```toml +# nixpacks.toml +[variables] +NODE_VERSION = "18" +NPM_CONFIG_PRODUCTION = "false" + +[phases.setup] +nixPkgs = ["nodejs-18_x", "yarn"] + +[phases.build] +cmds = [ + "yarn install --frozen-lockfile", + "yarn build" +] + +[start] +cmd = "yarn start" +``` + +Place this file in your project root or build directory. The CLI will automatically detect and use it. + +## Build Process + +### Local Building + +When you run `lttle deploy`, builds happen locally on your machine: + +```bash +$ lttle deploy + +→ Building image for default/my-app +→ Auto-build using providers: node +→ Pushing image for default/my-app → registry.lttle.cloud/tenant/my-app:abc123 +→ Successfully built and pushed image for default/my-app +→ Successfully deployed app: default/my-app +``` + +### Build Output + +Use `--debug-build` to see detailed build information: + +```bash +$ lttle deploy --debug-build + +→ Building image for default/my-app +→ Generated image reference: registry.lttle.cloud/tenant/my-app:abc123 +→ Building image for path: ./ +→ Auto-build using providers: node +→ Build summary: + setup │ apt-get update && apt-get install -y nodejs npm + install │ npm ci + build │ npm run build + start │ npm start +→ Generated docker file: + FROM ubuntu:jammy + # ... dockerfile content ... +→ Docker build output: + # ... build logs ... +→ Successfully built and pushed image for default/my-app +``` + +### Build Cache + +Builds are cached by default for faster subsequent deployments. Disable caching with: + +```bash +lttle deploy --no-build-cache +``` + +## Common Build Patterns + +### Monorepo Applications + +Build specific applications within a monorepo: + +```yaml +# Frontend app +app: + name: frontend + build: + options: + dir: ./apps/web + resources: + cpu: 1 + memory: 512 + +--- +# Backend API +app: + name: backend + build: + options: + dir: ./apps/api + resources: + cpu: 1 + memory: 256 +``` + +### Multi-Stage Docker Builds + +Use Docker builds for complex scenarios: + +```yaml +machine: + name: production-app + build: + docker: + dockerfile: Dockerfile.prod + args: + ENVIRONMENT: production + BUILD_VERSION: "1.2.3" + resources: + cpu: 2 + memory: 1024 +``` + +```dockerfile +# Dockerfile.prod +FROM node:18-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production + +FROM node:18-alpine AS runtime +ARG ENVIRONMENT +ARG BUILD_VERSION +ENV NODE_ENV=${ENVIRONMENT} +ENV VERSION=${BUILD_VERSION} +WORKDIR /app +COPY --from=builder /app/node_modules ./node_modules +COPY . . +EXPOSE 3000 +CMD ["npm", "start"] +``` + +### Custom Build Commands + +Override the default start command: + +```yaml +machine: + name: custom-app + build: auto + command: + - node + - --experimental-modules + - server.mjs + resources: + cpu: 1 + memory: 256 +``` + +## Troubleshooting + +### Build Failures + +If your build fails: + +1. **Check supported languages**: Ensure your project is [supported by Nixpacks](https://nixpacks.com/docs/providers) +2. **Use debug mode**: Run with `--debug-build` to see detailed logs +3. **Verify build directory**: Ensure the `dir` option points to the correct location +4. **Check dependencies**: Make sure all required files (`package.json`, `requirements.txt`, etc.) are present + +### Common Issues + +**No compatible providers found**: +``` +No compatible providers found for auto-build. Check the documentation for auto-build supported targets. +``` +- Ensure your project has the required files for language detection +- Consider using Docker build mode instead + +**Dockerfile not found**: +``` +Dockerfile not found +``` +- Verify the `dockerfile` path in Docker build configuration +- Ensure the Dockerfile exists in the specified location + +**Build context issues**: +- Make sure the `context` directory exists and contains necessary files +- Check that paths are relative to your deployment file location + +### Getting Help + +For build-related issues: +- Check the [Nixpacks documentation](https://nixpacks.com/docs) for language-specific guidance +- Use `--debug-build` flag to get detailed build logs +- Verify your project structure matches the expected patterns for your language + +## Best Practices + +### Image Optimization + +- **Use `.dockerignore`**: Exclude unnecessary files from build context +- **Minimize dependencies**: Only install required packages +- **Use specific versions**: Pin language and dependency versions for reproducible builds + +### Build Configuration + +- **Environment-specific builds**: Use different build configurations for development/production +- **Consistent naming**: Use descriptive names and tags for images +- **Version tagging**: Tag images with version numbers for better tracking + +### Security + +- **Minimal base images**: Nixpacks uses optimized base images +- **No secrets in builds**: Use environment variables for runtime secrets, not build arguments +- **Regular updates**: Keep dependencies updated for security patches diff --git a/docs/getting-started/deploying-a-static-site.md b/docs/getting-started/deploying-a-static-site.md index 1f6b215..0a98cf3 100644 --- a/docs/getting-started/deploying-a-static-site.md +++ b/docs/getting-started/deploying-a-static-site.md @@ -4,8 +4,257 @@ sidebar_position: 3 # Deploying a static site -:::note Work in Progress +This guide walks you through deploying a simple static website on lttle.cloud, from basic setup to adding flash mode for cost-efficient serverless hosting. -This section is a work in progress. +## Prerequisites -::: +- lttle CLI installed and configured ([Installation guide](./installing-the-cli.md)) +- Basic familiarity with YAML configuration + +## Getting Started + +Let's start by creating a simple static site using a pre-built nginx image, then enhance it with lttle.cloud's features. + +### Create your project + +First, create a new directory for your project: + +```bash +mkdir my-static-site +cd my-static-site +``` + +### Create the deployment configuration + +Create a `lttle.yaml` file with a basic app configuration: + +```yaml title="lttle.yaml" +app: + name: hello-lttle + image: nginx:latest + resources: + cpu: 1 + memory: 128 + expose: + public: + port: 80 + external: + protocol: https +``` + +This creates an app that: +- Uses the official nginx image from Docker Hub +- Allocates 1 CPU core and 128MB of memory +- Runs nginx with its default configuration (serves nginx welcome page) +- Exposes port 80 externally via HTTPS with an auto-generated domain + +The `expose` configuration: +- **public** - Names this service endpoint (you can have multiple endpoints) +- **port: 80** - The port nginx listens on inside the container +- **external** - Makes the service accessible from the internet +- **protocol: https** - Automatically provides SSL/TLS encryption + +lttle.cloud will automatically generate a domain like `hello-lttle--public--yourtenantname.eu.lttle.host` for your app. + +### Deploy your site + +Deploy the app to lttle.cloud: + +```bash +lttle deploy +``` + +You should see output similar to: +``` +→ Successfully deployed app: default/hello-lttle +``` + +### Check deployment status + +Monitor your machine's status with: + +```bash +lttle machine ls +``` + +Wait for the machine to reach the `ready` state: +``` +NAME NAMESPACE IMAGE STATUS CPU MEMORY +hello-lttle default nginx:latest ready 1 128 +``` + +### Access your site + +Get the URL for your deployed app: + +```bash +lttle app get hello-lttle +``` + +This will show your app details including any exposed services. Open the provided URL in your browser to see the nginx welcome page. + +## Adding Flash Mode + +Now let's enhance your site with flash mode, which automatically suspends the machine when not in use, saving costs while providing instant wake-up on incoming requests. + +### Update your configuration + +Modify your `lttle.yaml` to include flash mode: + +```yaml title="lttle.yaml" +app: + name: hello-lttle + image: nginx:latest + resources: + cpu: 1 + memory: 128 + expose: + public: + port: 80 + external: + protocol: https + mode: + flash: + strategy: + listen-on-port: 80 + timeout: 2 +``` + +The configuration now includes: + +**Flash mode settings:** +- **strategy: listen-on-port: 80** - Suspends the machine after nginx starts listening on port 80 +- **timeout: 2** - Suspends the machine 2 seconds after the last connection ends + +**Service exposure:** +- **public** service endpoint exposes port 80 via HTTPS +- Automatically provides SSL/TLS encryption and domain generation + +For more details about flash mode, see [Machines > Mode](../resources/machines.mdx#mode). + +### Redeploy with flash mode + +Deploy the updated configuration: + +```bash +lttle deploy +``` + +### Observe flash mode behavior + +Check the machine status: + +```bash +lttle machine ls +``` + +You'll see the machine go through these states: +1. `ready` - Machine is running +2. `suspending` - Creating a snapshot +3. `suspended` - Machine is suspended + +Now open your site URL in the browser. The machine will automatically wake up from suspended state to serve your request! + +## Adding Custom Content + +Let's replace the default nginx page with custom content and use lttle.cloud's automatic building. + +### Create your content + +Create an `index.html` file in your project directory: + +```html title="index.html" + + + + Hello lttle.cloud + + +

Hello, lttle.cloud!

+

This is my static site running on lttle.cloud

+ + +``` + +### Switch to automatic building + +Update your `lttle.yaml` to use automatic building instead of the pre-built nginx image: + +```yaml title="lttle.yaml" +app: + name: hello-lttle + build: auto + resources: + cpu: 1 + memory: 128 + expose: + public: + port: 80 + external: + protocol: https + mode: + flash: + strategy: + listen-on-port: 80 + timeout: 2 +``` + +The `build: auto` configuration tells lttle.cloud to: +1. Automatically detect your project type (static files) +2. Build an appropriate container image automatically +3. Push the image to lttle.cloud's registry +4. Deploy using the built image + +For more information about building, see [Building & Deploying > Building](../building-and-deploying/building.md). + +### Deploy your custom site + +Deploy with automatic building: + +```bash +lttle deploy +``` + +You'll see build output: +``` +→ Building image for default/hello-lttle +→ Auto-build using providers: staticfile +→ Pushing image for default/hello-lttle → registry.lttle.cloud/tenant/hello-lttle:abc123 +→ Successfully built and pushed image for default/hello-lttle +→ Successfully deployed app: default/hello-lttle +``` + +### Test your custom site + +Check the machine status: + +```bash +lttle machine ls +``` + +Once the machine reaches `suspended` state, open your site URL in the browser. You should now see your custom "Hello, lttle.cloud!" page instead of the nginx welcome page. + +## What's Next? + +You've successfully deployed a static site with: +- ✅ Automatic image building from static files +- ✅ Flash mode for cost-efficient serverless hosting +- ✅ Custom content served by nginx + +### Explore More Features + +- **Add a custom domain**: Set a custom `host` in your app's [expose external](../resources/apps.mdx#expose-external) configuration +- **Add HTTPS**: Set up [certificates](../resources/certificates.mdx) for secure connections +- **Build from source**: Learn about [advanced building options](../building-and-deploying/building.md) + +### Project Structure + +Your final project structure should look like: + +``` +my-static-site/ +├── lttle.yaml +└── index.html +``` + +The `lttle.yaml` file defines your infrastructure, while `index.html` contains your site content. lttle.cloud automatically builds everything into a deployable container image. diff --git a/docs/getting-started/deploying-an-existing-app.md b/docs/getting-started/deploying-an-existing-app.md new file mode 100644 index 0000000..749c192 --- /dev/null +++ b/docs/getting-started/deploying-an-existing-app.md @@ -0,0 +1,106 @@ +--- +sidebar_position: 4 +--- + +# Deploying an existing app + +This guide shows you how to deploy an existing application to lttle.cloud using the automatic initialization tool. + +## Prerequisites + +- lttle CLI installed and configured ([Installation guide](./installing-the-cli.md)) +- An existing application or project directory + +## Quick Start + +### Navigate to your application + +Go to your existing application directory: + +```bash +cd my-existing-app +``` + +### Initialize lttle.cloud configuration + +Run the initialization command: + +```bash +lttle gadget init +``` + +The `gadget init` command will: +- Automatically detect your application type and framework +- Generate appropriate lttle.cloud configuration files +- Provide deployment instructions specific to your project + +### Follow the initialization output + +The command will analyze your project and provide tailored instructions. Common scenarios include: + +**For web applications:** +- Detects package managers (npm, yarn, pnpm) +- Identifies frameworks (Next.js, React, Vue, etc.) +- Generates app configuration with appropriate build settings + +**For API servers:** +- Detects runtime (Node.js, Python, Go, etc.) +- Configures appropriate ports and health checks +- Sets up service exposure + +**For static sites:** +- Identifies static content +- Configures nginx or appropriate server +- Sets up automatic building + +### Deploy your application + +After `gadget init` completes, deploy using: + +```bash +lttle deploy +``` + +## What `gadget init` creates + +The initialization process typically generates: + +- **`lttle.yaml`** - Main deployment configuration +- **`.lttle/`** - Directory with additional configuration files (if needed) +- **Build configuration** - Automatic detection of build requirements + +## Customizing the configuration + +After initialization, you can modify the generated `lttle.yaml` to: + +- Adjust resource allocation (CPU, memory) +- Configure environment variables +- Set up custom domains +- Add flash mode for cost efficiency +- Configure dependencies + +For detailed configuration options, see: +- [Apps configuration](../resources/apps.mdx) +- [Building options](../building-and-deploying/building.md) + +## Troubleshooting + +**No compatible framework detected:** +- The tool may not recognize your specific setup +- Manually create a `lttle.yaml` file based on the [apps documentation](../resources/apps.mdx) + +**Build errors during deployment:** +- Check the generated build configuration +- See [Building troubleshooting](../building-and-deploying/building.md#troubleshooting) + +**Port configuration issues:** +- Verify your application listens on the correct port +- Update the `expose` configuration in `lttle.yaml` + +## What's next? + +Once your app is deployed: +- **Monitor status**: Use `lttle machine ls` to check deployment status +- **View logs**: Use `lttle machine logs ` for debugging +- **Scale resources**: Adjust CPU/memory in your configuration +- **Add custom domain**: Configure a [custom domain](../resources/apps.mdx#using-custom-domains) diff --git a/docs/index.md b/docs/index.md index 073c46b..ab0bc0d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Influenced by the [Unikraft Cloud](https://unikraft.cloud/), the way it achieves lttle.cloud is not just a cloud platform — it is a fully open source AGPL-3.0 project. This means that developers can inspect, modify, and enhance the platform to suit their needs. -While lttle.cloud can be used as a managed platform, it is also designed to be self-hosted. This means you can deploy the entire stack on your own infrastructure — wether on bare metal, private cloud or on other public clouds — with the same capabilities as the managed offering. +While lttle.cloud can be used as a managed platform, it is also self-hostable. This means you can deploy the entire stack on your own infrastructure — wether on bare metal, private cloud or on other public clouds — with the same capabilities as the managed offering. ## OCI-compliant @@ -36,4 +36,4 @@ One of the main differentiators of lttle.cloud is its ability to start machines ## Cost-effectiveness -lttle.cloud is designed to be cost-efficient. By optimizing resource allocation and minimizing overhead, it ensures that you only pay for what you use. By using lttle.clouds snapshotting features you get to pay for the exact resources your application consumes, without any hidden costs. +lttle.cloud is cost-efficient. By optimizing resource allocation and minimizing overhead, it ensures that you only pay for what you use. By using lttle.clouds snapshotting features you get to pay for the exact resources your application consumes, without any hidden costs. diff --git a/docs/resources/apps.mdx b/docs/resources/apps.mdx index e21aded..f94aa60 100644 --- a/docs/resources/apps.mdx +++ b/docs/resources/apps.mdx @@ -1,11 +1,509 @@ --- sidebar_position: 1 --- +import Mermaid from '@theme/Mermaid'; +import ResourceNameValidation from '../common/resource-name-validation.md'; # Apps -:::note Work in Progress +Apps provide a simplified way to deploy and manage applications on lttle.cloud by orchestrating a single machine with optional service exposure. They offer a streamlined developer experience by combining machine and service configuration into a single resource, automatically managing the underlying infrastructure. -This section is a work in progress. +Think of Apps as a convenient wrapper around machines that handles service creation and domain allocation for you, making it easier to deploy web applications and APIs. + +If you want to know how to deploy an app, check [Building & Deploying > Building](../building-and-deploying/building.md) and [Building & Deploying > Deploying](../building-and-deploying/deploying.md). + +## Configuration + +| Properties | Type | Required | Default | +| :-------------------------------------- | :---------------------------------: | :------: | :-------: | +| `namespace` | `string` | | `default` | +| [`name`](#name) | `string` | ✓ | | +| [`tags`](#tags) | `array` | | | +| [`image`](#image) | `string` | ✓* | | +| [`build`](#build) | `object` | | | +| [`resources`](#resources) | `object` | ✓ | | +| [`expose`](#expose) | `object` | | | +| [`mode`](#mode) | `object` \| `regular` | | `regular` | +| [`restart-policy`](#restart-policy) | `always` \| `on-failure` \| `never` | | `always` | +| [`environment`](#environment-variables) | `object` | | | +| [`volumes`](#volumes) | `array` | | | +| [`command`](#command) | `array` | | | +| [`depends-on`](#depends-on) | `array` | | | + +:::note Image vs Build + +Either `image` or `build` is required. If `build` is specified, the image will be built automatically during deployment and `image` is not needed. + +::: + +If you want to know more about namespaces, check [Building & Deploying > Namespaces](../building-and-deploying/namespaces.md) + +### Name + +The name is the unique identifier for your app within a namespace. It is used as the name for the underlying machine and to generate service names. + + + +```yaml title="webapp.lttle.yaml" +app: + name: web-frontend +``` + +### Tags + +Tags are used to organize and categorize resources. They can be any valid UTF-8 string and can be used to filter resources in the [Web Console](../monitoring/web-console.md). Tags applied to the app are automatically inherited by the managed machine and services. + +```yaml title="webapp.lttle.yaml" +app: + tags: + - frontend + - production + - web +``` + +### Image + +The OCI-compliant container image to run. This can be any valid image from a container registry. + +```yaml title="webapp.lttle.yaml" +app: + image: nginx:1.21 +``` + +### Build + +The build configuration allows you to automatically build container images during deployment instead of providing a pre-built image. This uses [Nixpacks](https://nixpacks.com) for automatic language detection and building, or Docker for custom builds. + +For detailed information about building, see [Building & Deploying > Building](../building-and-deploying/building.md). + +#### Auto Build + +The simplest option - automatically detects your application type: + +```yaml title="webapp.lttle.yaml" +app: + build: auto +``` + +#### Build Options + +Provides more control over the build process: + +```yaml title="webapp.lttle.yaml" +app: + build: + options: + dir: ./src # Build directory (default: ".") + name: my-web-app # Image name (default: auto-generated) + tag: v2.1.0 # Image tag (default: "latest") + image: myregistry.com/my-web-app:v2.1.0 # Full image reference (overrides everything else) +``` + +#### Docker Build + +For projects with existing Dockerfiles: + +```yaml title="webapp.lttle.yaml" +app: + build: + docker: + context: . # Build context (default: ".") + dockerfile: Dockerfile.prod # Dockerfile path (default: "Dockerfile") + name: my-web-app # Image name (default: auto-generated) + tag: production # Image tag (default: "latest") + image: myregistry.com/my-web-app:v1.0.0 # Full image reference (overrides everything else) + args: # Build arguments + NODE_ENV: production + API_URL: https://api.example.com +``` + +:::info + +When using `build`, the CLI will automatically build and push the image during deployment, then replace the `build` configuration with the resulting image reference. + +::: + +### Resources + +The compute resources allocated to the machine. This follows the same structure as [machine resources](./machines.mdx#resources). + +| Properties | Type | Required | +| :------------------ | :-------------------------------------- | :------: | +| [`cpu`](#cpu) | `int` (min: `1` max: `24`) | ✓ | +| [`memory`](#memory) | `int` mebibytes (min: `64` max: `1024`) | ✓ | + +```yaml title="webapp.lttle.yaml" +app: + resources: + cpu: 2 + memory: 512 +``` + +### Expose + +The expose configuration defines how your app is made accessible through services. Each key in the expose object creates a separate service. + +```yaml title="webapp.lttle.yaml" +app: + expose: + web: + port: 8080 + external: + protocol: https + api: + port: 3000 + internal: {} +``` + +Each expose entry has the following structure: + +| Properties | Type | Required | +| :-------------------------------------- | :---------------------------------: | :------: | +| [`port`](#expose-port) | `int` | ✓ | +| [`external`](#expose-external) | `object` | | +| [`internal`](#expose-internal) | `object` | | +| [`connection-tracking`](#connection-tracking) | `object` | | + +:::info + +Each expose entry must have either `external` or `internal` configured, but not both. + +::: + +#### Expose Port + +The port number that your application listens on inside the container. + +#### Expose External + +Configuration for external (internet-facing) services: + +| Properties | Type | Required | Default | +| :---------- | :---------------------------------: | :------: | :-------: | +| `protocol` | `http` \| `https` \| `tls` | ✓ | | +| `host` | `string` | | *auto-generated* | +| `port` | `int` | | *protocol default
(or fallback on container port)* | + +```yaml title="webapp.lttle.yaml" +app: + expose: + web: + port: 8080 + external: + protocol: https + host: myapp.example.com + port: 443 +``` + +### Using Custom Domains + +For development and testing, lttle.cloud automatically generates domains using the pattern `{app-name}--{expose-key}--{tenant}.eu.lttle.host` with free wildcard SSL certificates. + +For production workloads, you can specify custom domains using the `host` property: + +```yaml title="webapp.lttle.yaml" +app: + expose: + web: + port: 80 + external: + protocol: https + host: www.mycompany.com +``` + +#### DNS Configuration + +To use a custom domain, configure your DNS records to point to lttle.cloud: + +| Region | IP Address | +| :------------ | :-------------- | +| `eu` (Europe) | `46.105.65.138` | + +Create an `A` record in your DNS provider pointing your domain to the appropriate IP address. + +#### SSL/TLS Certificates + +When using custom domains with `https` or `tls` protocol, you need to create a [certificate resource](./certificates.mdx) with the same domain: + +```yaml title="webapp.lttle.yaml" +# Your app configuration +app: + name: my-app + expose: + web: + port: 80 + external: + protocol: https + host: www.mycompany.com +--- +# Certificate for your custom domain +certificate: + name: my-app-cert + domains: + - www.mycompany.com + issuer: + auto: + provider: letsencrypt +``` + +This automatically provisions and manages SSL/TLS certificates for your custom domain. + +#### Expose Internal + +Configuration for internal (tenant-only) services: + +| Properties | Type | Required | Default | +| :---------- | :---------------------------------: | :------: | :-----: | +| `port` | `int` | | *container port* | + +```yaml title="webapp.lttle.yaml" +app: + expose: + api: + port: 3000 + internal: + port: 3000 +``` + +### Mode + +The execution mode for the machine. This follows the same structure as [machine mode](./machines.mdx#mode). + +```yaml title="webapp.lttle.yaml" +app: + mode: regular +``` + +For flash mode configuration: + +```yaml title="webapp.lttle.yaml" +app: + mode: + flash: + strategy: first-listen + timeout: 300 +``` + +### Restart Policy + +How the machine should behave when it exits. This follows the same options as [machine restart policy](./machines.mdx#restart-policy). + +| Policy | Description | +| ------------ | ----------------------------------------------------------------------- | +| `always` | Always restart the machine if it exits | +| `on-failure` | Restart the machine only if it exits with a non-zero exit code. | +| `never` | Do not restart the machine if it exits event with a non-zero exit code. | + +```yaml title="webapp.lttle.yaml" +app: + restart-policy: always +``` + +### Environment Variables + +Environment variables for the application. This follows the same structure as [machine environment](./machines.mdx#environment-variables). + +```yaml title="webapp.lttle.yaml" +app: + environment: + NODE_ENV: production + API_URL: https://api.example.com + PORT: "8080" +``` + +### Volumes + +Volume mounts for persistent storage. This follows the same structure as [machine volumes](./machines.mdx#volumes). + +```yaml title="webapp.lttle.yaml" +app: + volumes: + - name: app-data + path: /data + - name: logs + path: /var/log/app +``` + +### Command + +The command to execute in the container. This follows the same structure as [machine command](./machines.mdx#command). + +```yaml title="webapp.lttle.yaml" +app: + command: + - node + - server.js +``` + +### Depends On + +Dependencies on other machines. This follows the same structure as [machine dependencies](./machines.mdx#depends-on). + +```yaml title="webapp.lttle.yaml" +app: + depends-on: + - name: database + - name: redis + namespace: cache +``` + +## Complete Example + +Here's a complete example of an app configuration: + +```yaml title="webapp.lttle.yaml" +app: + name: web-frontend + namespace: production + tags: + - frontend + - web + image: nginx:1.21 + resources: + cpu: 2 + memory: 512 + environment: + NODE_ENV: production + PORT: "8080" + command: + - nginx + - -g + - daemon off; + mode: regular + restart-policy: always + expose: + web: + port: 8080 + external: + protocol: https + host: myapp.example.com + api: + port: 3000 + internal: {} + volumes: + - name: app-data + path: /data + depends-on: + - name: database + namespace: data +``` + +## How Apps Work + +Apps simplify application deployment by automatically managing machines and services for you. When you create an app, the controller: + +1. **Creates a Machine**: Uses your app configuration to create a single machine with the specified image, resources, and settings +2. **Generates Services**: For each entry in the `expose` configuration, creates a corresponding service to handle traffic routing +3. **Manages Lifecycle**: Automatically updates the machine and services when you change the app configuration +4. **Handles Cleanup**: When you delete the app, automatically removes the associated machine and services + +### Machine Integration + +The app controller creates a machine with the same name as the app, using these mappings: + +- `app.image` → `machine.image` +- `app.resources` → `machine.resources` +- `app.environment` → `machine.environment` +- `app.command` → `machine.command` +- `app.volumes` → `machine.volumes` +- `app.mode` → `machine.mode` +- `app.restart-policy` → `machine.restart-policy` +- `app.depends-on` → `machine.depends-on` +- `app.tags` → `machine.tags` (with additional ownership tag) + +### Service Generation + +For each key in the `expose` configuration, the app controller creates a service named `{app-name}-{expose-key}`. For example: + +```yaml +app: + name: my-app + expose: + web: + port: 8080 + external: + protocol: https + api: + port: 3000 + internal: {} +``` + +This creates two services: +- `my-app-web` (external HTTPS service) +- `my-app-api` (internal service) + +Both services target the `my-app` machine on their respective ports. + +### Domain Allocation + +For external services, if no `host` is specified, the system automatically generates a domain using the pattern: +``` +{app-name}--{expose-key}--{tenant}.eu.lttle.host +``` + +### Configuration Updates + +When you update an app: +- Machine configuration changes trigger a machine update (which may restart the machine depending on what changed) +- Service configuration changes update the corresponding services +- Adding/removing expose entries creates/deletes the corresponding services + +## Best Practices + +### Expose Configuration + +Use descriptive names for your expose entries: + +```yaml +app: + expose: + web: # Main website + port: 80 + external: + protocol: https + admin: # Admin interface + port: 8080 + external: + protocol: https + host: admin.myapp.com + metrics: # Internal metrics + port: 9090 +``` + +### Resource Planning + +Configure appropriate resources based on your application needs: + +```yaml +app: + resources: + cpu: 1 # 1 vCPU cores + memory: 256 # 256MB RAM +``` + +### Environment Management + +Use environment variables to configure your application: + +```yaml +app: + environment: + NODE_ENV: production + DATABASE_URL: postgresql://user:pass@db:5432/myapp + REDIS_URL: redis://cache:6379 +``` + +### Health Considerations + +Since apps create a single machine, ensure your application: + +- Starts up reliably and quickly +- Handles graceful shutdowns properly +- Uses appropriate restart policies for your use case +- Exposes health check endpoints when using flash mode + +:::info + +Apps are ideal for simple applications that don't require complex orchestration. For more advanced scenarios requiring multiple machines, custom networking, or complex dependencies, consider using machines and services directly. ::: diff --git a/docs/resources/machines.mdx b/docs/resources/machines.mdx index 05c1a88..4c3aa73 100644 --- a/docs/resources/machines.mdx +++ b/docs/resources/machines.mdx @@ -17,7 +17,8 @@ If you want to know how to deploy a machine you can check [Building & Deploying | `namespace` | `string` | | `default` | | [`name`](#name) | `string` | ✓ | | | [`tags`](#tags) | `array` | | | -| [`image`](#image) | `string` | ✓ | | +| [`image`](#image) | `string` | ✓* | | +| [`build`](#build) | `object` | | | | [`depends-on`](#depends-on) | `array` | | | | [`resources`](#resources) | `object` | ✓ | | | [`mode`](#mode) | `object` \| `regular` | ✓ | | @@ -26,6 +27,12 @@ If you want to know how to deploy a machine you can check [Building & Deploying | [`volume`](#volumes) | `object` | | | | [`command`](#command) | `array` | | | +:::note Image vs Build + +Either `image` or `build` is required. If `build` is specified, the image will be built automatically during deployment and `image` is not needed. + +::: + If you want to know more about namespaces, check [Building & Deploying > Namespaces](../building-and-deploying/namespaces.md) ### Name @@ -60,6 +67,58 @@ machine: image: future/terminator:800 ``` +### Build + +The build configuration allows you to automatically build container images during deployment instead of providing a pre-built image. This uses [Nixpacks](https://nixpacks.com) for automatic language detection and building, or Docker for custom builds. + +For detailed information about building, see [Building & Deploying > Building](../building-and-deploying/building.md). + +#### Auto Build + +The simplest option - automatically detects your application type: + +```yaml title="robot.lttle.yaml" +machine: + build: auto +``` + +#### Build Options + +Provides more control over the build process: + +```yaml title="robot.lttle.yaml" +machine: + build: + options: + dir: ./backend # Build directory (default: ".") + name: my-backend # Image name (default: auto-generated) + tag: v1.0.0 # Image tag (default: "latest") + image: myregistry.com/my-app:v1.0.0 # Full image reference (overrides everything else) +``` + +#### Docker Build + +For projects with existing Dockerfiles: + +```yaml title="robot.lttle.yaml" +machine: + build: + docker: + context: . # Build context (default: ".") + dockerfile: Dockerfile.prod # Dockerfile path (default: "Dockerfile") + name: my-docker-app # Image name (default: auto-generated) + tag: production # Image tag (default: "latest") + image: myregistry.com/my-app:v1.0.0 # Full image reference (overrides everything else) + args: # Build arguments + NODE_ENV: production +``` + +:::info + +When using `build`, the CLI will automatically build and push the image during deployment, then replace the `build` configuration with the resulting image reference. + +::: + ### Depends on The `depends-on` property defines the dependencies of the machine. It is specified as an array of objects, each representing how to identify a machine. diff --git a/docs/resources/services.mdx b/docs/resources/services.mdx index a6e7ba0..eae18e9 100644 --- a/docs/resources/services.mdx +++ b/docs/resources/services.mdx @@ -18,7 +18,6 @@ If you want to know how to deploy a a service [Building and Deploying > Deplo | [`tags`](#tags) | `array` | | | [`bind`](#bindings) | `object` | ✓ | | [`target`](#target) | `object` | ✓ | -| [`certificate`](#certificate) | `string` | | If you want to know more about namespaces, check [Building and Deploying > Namespaces](../building-and-deploying/namespaces.md) @@ -143,12 +142,6 @@ service: protocol: https ``` -:::tip - -If you want to use `https` or `tls` you need to provide a valid certificate using the [`certificate property`](#certificate) and you need to also define a [certificate resource](./certificates.mdx). - -::: - ### Target It defines what [machine](./machines.mdx) the service is pointing to. @@ -196,21 +189,19 @@ References: This connection tracking strategy keeps the machine `ready` as long as there is traffic to the service. It is useful for workloads that do not use persistent connections, such as HTTP/1.1 without keep-alive. -### Certificate - -The certificate property defines the name of the [certificate resource](./certificates.mdx) that will be used to secure the service. It is required if the service is using `https` or `tls` as protocol. - -```yaml title="robot-service.lttle.yaml" -service: - certificate: terminator-cert -``` - ## Domains and DNS For development, research and testing feel free to use the `eu.lttle.host` sub-domain. For production workloads, you should use your own domain and [configure the DNS records accordingly](#configure-dns). We are maintaining the `eu.lttle.host` sub-domain and you can use it for free. We are using a wildcard certificate for it, so you don't need to worry about certificates during the early stages of your project. +:::tip + +If you want to use `https` or `tls` with your custom domain, you also need to create a [certificate resource](./certificates.mdx) with the same domain. This resource will configure TLS termination with a valid certificate matching your domain. + +::: + + ### Configure DNS To configure your own domain for production workloads, you need to set up the DNS records to point to your services. This typically involves creating `A` records in your DNS provider's management console. diff --git a/lttle/certs.lttle.yaml b/lttle/certs.lttle.yaml index d90202c..d318a25 100644 --- a/lttle/certs.lttle.yaml +++ b/lttle/certs.lttle.yaml @@ -2,8 +2,8 @@ certificate: namespace: docs-main name: main domains: - - docs.lttle.aifrim.com - - docs-search.lttle.aifrim.com + - docs.lttle.cloud + - docs-search.lttle.cloud issuer: auto: provider: letsencrypt diff --git a/lttle/docs.lttle.yaml b/lttle/docs.lttle.yaml index 4992ff8..2c2d137 100644 --- a/lttle/docs.lttle.yaml +++ b/lttle/docs.lttle.yaml @@ -27,8 +27,8 @@ service: protocol: http bind: external: - # Production is docs.lttle.aifrim.com (for now) where we have dedicated certificate + # Production is docs.lttle.cloud (for now) where we have dedicated certificate # Preview Environments are reusing the same wildcard certificate from *.eu.lttle.host - host: "${{ git.ref == 'main'? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host' }}" + host: "${{ git.ref == 'main'? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host' }}" protocol: https certificate: "${{ git.ref == 'main'? 'main' : null }}" diff --git a/lttle/typesense.lttle.yaml b/lttle/typesense.lttle.yaml index d837cdf..e97c4b1 100644 --- a/lttle/typesense.lttle.yaml +++ b/lttle/typesense.lttle.yaml @@ -78,7 +78,7 @@ service: protocol: http bind: external: - host: "${{ git.ref == 'main'? 'docs-search.lttle.aifrim.com' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host' }}" + host: "${{ git.ref == 'main'? 'docs-search.lttle.cloud' : 'docs-search-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host' }}" protocol: https certificate: "${{ git.ref == 'main'? 'cert-docs' : null }}" --- diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx index f9189b2..03d3787 100644 --- a/src/components/HomepageFeatures/index.tsx +++ b/src/components/HomepageFeatures/index.tsx @@ -15,7 +15,7 @@ const FeatureList1: FeatureItem[] = [ Svg: require("@site/static/img/chunk/folder-open.svg").default, description: ( <> - lttle.cloud is designed to be lightweight and efficient, with a focus on + lttle.cloud is lightweight and efficient, with a focus on performance and scalability. ), @@ -35,7 +35,7 @@ const FeatureList1: FeatureItem[] = [ Svg: require("@site/static/img/chunk/checkmark.svg").default, description: ( <> - lttle.cloud is designed to be simple and easy to use, with a focus on + lttle.cloud is simple and easy to use, with a focus on usability and accessibility. ), @@ -44,22 +44,21 @@ const FeatureList1: FeatureItem[] = [ const FeatureList2: FeatureItem[] = [ { - title: "secure", - Svg: require("@site/static/img/chunk/shield-check.svg").default, + title: "community", + Svg: require("@site/static/img/chunk/users.svg").default, description: ( <> - lttle.cloud is designed to be secure and reliable, with a focus on - security and reliability. + a growing network of builders, developers, and founders who believe cloud infrastructure should be simple, fair, and fast. ), }, { - title: "community", - Svg: require("@site/static/img/chunk/users.svg").default, + title: "secure", + Svg: require("@site/static/img/chunk/shield-check.svg").default, description: ( <> - lttle.cloud is designed to be high performance, with a focus on - performance and scalability. + lttle.cloud is secure and reliable, with a focus on + security and reliability. ), }, @@ -68,8 +67,7 @@ const FeatureList2: FeatureItem[] = [ Svg: require("@site/static/img/chunk/arrow-trend-up.svg").default, description: ( <> - lttle.cloud is designed to be high performance, with a focus on - performance and scalability. + lttle.cloud offers blazing-fast, cold-start-free infrastructure that runs any Docker image in under 10ms ), }, diff --git a/src/components/HomepageSocials/index.tsx b/src/components/HomepageSocials/index.tsx index d871917..13f4843 100644 --- a/src/components/HomepageSocials/index.tsx +++ b/src/components/HomepageSocials/index.tsx @@ -47,7 +47,7 @@ export default function HomepageSocials(): ReactNode {
join the community -

Connect with other developers and get help when you need it.

+

connect with other developers and get help when you need it.

{socials.map((s, i) => ( diff --git a/wait-for-app.sh b/wait-for-app.sh index e22cb5b..f1be471 100755 --- a/wait-for-app.sh +++ b/wait-for-app.sh @@ -13,7 +13,7 @@ if [ -z "${GITHUB_HEAD_REF+x}" ]; then export GITHUB_HEAD_REF fi -docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.aifrim.com' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") +docsURL="https://"$(lttle deploy --eval "git.ref == 'main' ? 'docs.lttle.cloud' : 'docs-' + env.GITHUB_HEAD_REF.toSlug() + '-lttle-aifrim.eu.lttle.host'") namespace="docs-"$(lttle deploy --eval "git.ref == 'main' ? 'main' : env.GITHUB_HEAD_REF.toSlug() + '-branch'") isUp "$namespace" "nginx-docs" "$docsURL" 300 1 \ No newline at end of file