# Spawned > Spawned is a platform for deploying and managing cloud infrastructure with the help of AI agents. You describe your app as a set of components and connections (an `infra.json`), and Spawned compiles it into real infrastructure and provisions it. Build it on a visual canvas, from the CLI, or by pointing your coding agent at it. Deploy to a cloud account (AWS) that Spawned manages or that you bring, or to your own Kubernetes cluster. Every change is versioned in Git, so you can export and leave at any time. --- # Philosophy Spawned aims to solve three problems: application code should not be coupled to infrastructure, you should not be locked to a single cloud, and you should not have to configure infrastructure by hand. Spawned exists to be the safe way for AI agents to deploy to the cloud, providing oversight over what is created and its security. Under the hood, a cloud-agnostic infrastructure SDK compiles your definition to standard infrastructure, so you can take it and leave at any point. Two problems with AI-generated code, and how Spawned addresses them: - The "a lot" problem: the infrastructure canvas gives instant visibility into everything generated, at a higher level of abstraction than raw code. - The "suboptimal" problem: the SDK constrains AI to reasoning-based decisions only. Security defaults, compliance, and best practices are built in, not left to the model. You build, Spawned ships. Use any stack, framework, or language; Spawned handles deployment. --- # How It Works 1. Describe your infrastructure as components and connections. Build it on the canvas, with the CLI, or with your coding agent. Together this is your project's `infra.json`. 2. Spawned compiles it into real infrastructure for wherever it runs (cloud resources for a Cloud, manifests for a Kubernetes cluster) and applies it. Security defaults are built in. 3. Every apply is a new version, backed by Git, so you can revisit any point in your project's history. 4. Run and manage everything from the project dashboard: logs, cost, health, secrets, and restarts, without touching a cloud console. A project runs on a Cloud you connect: a commercial cloud such as AWS, or a Kubernetes cluster you bring. The same components describe your app either way. --- # Components & Connections A project is a set of components and the connections between them, captured in an `infra.json`. Common components: - Container: a long-running service built from your code (web app, API, worker) - Function: a serverless function, optionally run on a schedule - Database: a managed relational (PostgreSQL) database - Bucket: object storage for files - Volume: a persistent disk attached to a container - Secret: sensitive values made available at runtime - Domain: a custom domain in front of your app, with TLS - CDN: caches static content close to your users Networking (the private network and load balancers) is added automatically. A connection is a directed link from one component to another. It opens the network path AND injects what the source needs as environment variables, prefixed with the target component's name in uppercase. - Container to Database: injects `_HOST`, `_PORT`, `_NAME`, `_USER`, `_PASSWORD` - Container to Bucket: grants read/write access and injects `_BUCKET` - Container to Secret: makes the secret's values available - Domain to Container: routes the domain's traffic to the container, with TLS Example `infra.json`: ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Container", "name": "api", "source": { "git": { "url": "github.com/you/app", "ref": "main" } }, "ports": [{ "name": "http", "port": 8000 }] }, { "type": "Database", "name": "db", "db_name": "app" }, { "type": "Bucket", "name": "uploads" } ], "connections": [ { "from": "api", "to": "db" }, { "from": "api", "to": "uploads" } ] } ``` Edit the `infra.json` by hand, view it from the canvas Source button, or let your coding agent write it. Run `spawned schema` for the full list of components and their fields. --- # Building for the Cloud Cloud services fall into three categories: compute (runs your code), storage (holds your data), and networking (connects it). Spawned reads the choices you make locally and provisions the matching cloud resources. For example, a fishing-map app grows from a single container, to a container plus a managed database, to an object-storage bucket for large files, to a scheduled function that refreshes data hourly, all provisioned automatically from source. Networking is provisioned automatically with every deployment and needs no configuration: load balancer, private network, CDN, container registry, and SSL certificates. --- # Compute Containers (AWS ECS) are the default choice for most apps: a packaged version of your app that runs the same in the cloud as on your machine. Use them for web apps, APIs, and always-on background jobs. A `Dockerfile` or `docker-compose.yml` is enough; Spawned handles containerization from your source. Serverless (AWS Lambda) runs code only when triggered, and supports scheduled cron jobs (fetching data hourly, daily reports, cleanup). Cheaper than an always-on container for idle workloads, with a cold-start tradeoff. Structure the function as a Lambda handler: ```python def handler(event, context): return {"statusCode": 200} ``` Kubernetes: Spawned generates a full set of manifests from the same components you use on any platform, and your cluster syncs them with GitOps. See the Kubernetes section. --- # Storage Buckets (AWS S3) store files: images, uploads, backups, static assets. Use MinIO locally. Relational Databases (AWS RDS) store structured data. Use SQLite or PostgreSQL locally; Spawned provisions a managed instance with backups and encryption. Secrets (AWS Secrets Manager) store sensitive values such as API keys and passwords. On AWS, add a Secret component and enter values in the dashboard (key/value pairs or paste a `.env`); values are written to Secrets Manager on apply and injected at runtime. On Kubernetes, a Secret is a shape-only declaration (it lists keys but carries no values); populate values against the cluster with the generated `kubectl create secret` command. Values stay on your machine and are never stored by the platform. --- # Examples Complete architectures, each a full `infra.json` to copy and adapt. All use `platform: "aws"`. Every example with a workload in a network (container, function, database, volume) declares a managed `Network` with a membership connection per workload, which the validator requires; a bucket-and-CDN architecture needs none. On a Spawned-managed cloud use the `ImportedNetwork` that `spawned init` seeds instead. Deploy any of them with `spawned init`, replace the `infra.json`, `spawned validate`, `spawned apply`. Run `spawned schema` for every component and field. ## Containerized Web App An always-on process: one container built from your repo, a PostgreSQL database, a bucket for uploads, a load balancer in front. This is one architecture among those below rather than the default the rest depart from. A `Function` behind an `ImportedDomain` also answers HTTP, through an API Gateway HTTP API and with no load balancer at all, so the choice between them is whether the process should stay up, not which is more advanced. Connections inject `DB_HOST/PORT/NAME/USER/PASSWORD` and `UPLOADS_BUCKET`. The repo needs a Dockerfile; the `ports` entry must match what the process binds. Set `health_check_path` to a route that returns 200 (defaults to `/`). Container filesystems are ephemeral, so user files go in the bucket. ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Network", "name": "network" }, { "type": "Container", "name": "app", "source": { "git": { "url": "github.com/you/app", "ref": "main" } }, "ports": [{ "name": "http", "port": 8000 }] }, { "type": "Database", "name": "db", "db_name": "app" }, { "type": "Bucket", "name": "uploads" }, { "type": "LoadBalancer", "name": "edge" } ], "connections": [ { "from": "network", "to": "app" }, { "from": "network", "to": "db" }, { "from": "network", "to": "edge" }, { "from": "edge", "to": "app", "health_check_path": "/health" }, { "from": "app", "to": "db" }, { "from": "app", "to": "uploads" } ] } ``` ## Static Site on a CDN No compute at all: a bucket holds the built files, a CDN serves them over HTTPS from the edge. The bucket's `source.git` makes CI clone the repo, run `commands`, and sync `directory` into the bucket (`dist` for Vite, `build` for CRA, `out` for `next export`; omit `commands` to sync as-is). The `CDN -> Bucket` connection keeps the bucket private (only the distribution can read it). The cheapest architecture; the natural home for Lovable/v0 exports. ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Bucket", "name": "site", "source": { "git": { "url": "github.com/you/site", "ref": "main", "commands": [ "npm ci", "npm run build" ], "directory": "dist" } } }, { "type": "CDN", "name": "cdn" } ], "connections": [ { "from": "cdn", "to": "site" } ] } ``` ## Self-Hosted n8n Run software you did not write by pulling a published image (`source.image`) instead of building from a repo (`source.git`): image for software you consume, git for software you author. Example: n8n (workflow automation). A `Volume` is a persistent network disk mounted at `mount_path`; n8n keeps SQLite data in `/home/node/.n8n`, which would vanish on restart without it. Port, data directory, and health endpoint come from the image's own docs. Configure via the container's `env` map. Variants: Uptime Kuma (`louislam/uptime-kuma:2`, port 3001, mount `/app/data`), Excalidraw (`excalidraw/excalidraw`, port 80, stateless so no volume). ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Network", "name": "network" }, { "type": "Container", "name": "n8n", "source": { "image": "docker.n8n.io/n8nio/n8n:stable" }, "ports": [{ "name": "http", "port": 5678 }], "cpu": 0.5, "memory": "1Gi" }, { "type": "Volume", "name": "data" }, { "type": "LoadBalancer", "name": "edge" } ], "connections": [ { "from": "network", "to": "n8n" }, { "from": "network", "to": "data" }, { "from": "network", "to": "edge" }, { "from": "edge", "to": "n8n", "health_check_path": "/healthz" }, { "from": "n8n", "to": "data", "mount_path": "/home/node/.n8n" } ] } ``` ## Self-Hosted Open WebUI Open WebUI (a self-hosted ChatGPT-style interface) plus the Secret pattern. The `Secret` component declares key names only; values are entered in the dashboard after the first apply and written straight to the cloud secret store, never into `infra.json` or Git. The `Container -> Secret` connection projects each key as an env var with the same name (`keys` selects a subset, `prefix` namespaces). Rotating a key is a secret update plus restart, not a deploy. ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Network", "name": "network" }, { "type": "Container", "name": "chat", "source": { "image": "ghcr.io/open-webui/open-webui:main" }, "ports": [{ "name": "http", "port": 8080 }], "cpu": 1, "memory": "2Gi" }, { "type": "Volume", "name": "data" }, { "type": "Secret", "name": "llm-keys", "keys": ["OPENAI_API_KEY"] }, { "type": "LoadBalancer", "name": "edge" } ], "connections": [ { "from": "network", "to": "chat" }, { "from": "network", "to": "data" }, { "from": "network", "to": "edge" }, { "from": "edge", "to": "chat", "health_check_path": "/health" }, { "from": "chat", "to": "data", "mount_path": "/app/backend/data" }, { "from": "chat", "to": "llm-keys" } ] } ``` ## Scheduled Jobs Work that is triggered and then done (nightly reports, hourly syncs, cleanup) belongs in a `Function`: billed per execution, idle for free, bounded runtime. `schedule` takes `cron(...)` or `rate(...)` expressions, versioned with the rest of the infrastructure. For a `zip` function, `commands` run in `context` and must produce `artifact`; or set `package_type: "image"` and build from a Dockerfile. VPC attachment (`Network -> Function`) is only needed to reach a Database or Volume; functions calling external APIs or buckets need no network connection. The handler is `handler(event, context)`; `DB_*` and `REPORTS_BUCKET` are injected like for containers. ```json { "version": "2.0", "platform": "aws", "components": [ { "type": "Network", "name": "network" }, { "type": "Database", "name": "db" }, { "type": "Bucket", "name": "reports" }, { "type": "Function", "name": "nightly-report", "runtime": "python3.12", "handler": "main.handler", "schedule": "cron(0 3 * * ? *)", "memory": 512, "timeout": 120, "source": { "git": { "url": "github.com/you/app", "context": "jobs/report", "commands": [ "pip install -r requirements.txt -t .", "zip -r report.zip ." ], "artifact": "report.zip" } } } ], "connections": [ { "from": "network", "to": "db" }, { "from": "network", "to": "nightly-report" }, { "from": "nightly-report", "to": "db" }, { "from": "nightly-report", "to": "reports" } ] } ``` ## Design Rules of Thumb - Start with one container and a database; split services when deploy cadence or scaling genuinely diverge. - Image for software you consume, git for software you author. - State outlives compute: files in buckets, structured data in the database, tool-owned data on volumes. Container disks are ephemeral. - Private by default: nothing is reachable until you connect a load balancer, CDN, or domain to it. - Consolidate the edge: one load balancer with host/path rules beats one per service. - Let connections carry configuration: hand-written endpoints or credentials in `env` usually mean a missing connection. --- # AWS Run your project on AWS, either on infrastructure Spawned manages or in your own account. - Spawned Hosted: Spawned runs your project in an AWS account it manages; you don't need an account of your own; cloud costs are included in your Spawned bill. - Bring Your Own Cloud: connect your AWS account and Spawned deploys into it; you pay AWS directly and Spawned bills per component. Good for existing credits, compliance requirements, direct AWS access, or working alongside resources you already run. Connect an account under Settings > Clouds: launch the CloudFormation stack Spawned generates (it grants a scoped, cross-account role; no long-lived credentials are shared), then paste the stack Outputs back. From the CLI: `spawned accounts connect`, then `spawned accounts configure --role-arn `. Projects currently deploy to the eu-central-1 (Frankfurt) region. --- # Kubernetes Spawned deploys to a Kubernetes cluster you bring (EKS, GKE, AKS, or self-hosted). Your project is compiled into a `manifests.yaml` served from a read-only Git repository, and your cluster syncs it with GitOps. Spawned never needs your cluster credentials and cannot see inside your cluster, so use `kubectl` or your cluster's dashboard for live status and logs. 1. Register your cluster in your organization's Clusters settings (owner role). Configure the namespace, HTTP routing (Ingress or the Gateway API), TLS through cert-manager, an optional default domain, and pod security. Turn on "Manage projects through ArgoCD". 2. Connect ArgoCD once, using the cluster's setup panel: - Register a credential: `argocd repocreds add https://api.prod.spawned.ai/git --username spawned --password ` - Install the root Application (app-of-apps) from the panel with `kubectl apply`. It watches a Spawned-managed repository that lists one Application per project on the cluster. 3. Deploy projects as usual and choose this cluster. Each project appears in ArgoCD automatically and removed ones are pruned. There is no per-project manifest to apply by hand. ArgoCD is the supported GitOps tool. Flux can pull the same repositories, but Spawned does not generate Flux configuration for you. --- # Project Dashboard A project's dashboard has five sections: Infrastructure, Versions, Usage, Security, and Settings. - Infrastructure: the canvas, a live diagram of your components and how they connect. Add components, connect them, inspect and configure them, and view the `infra.json` with the Source button. Edits collect as pending changes; Apply builds and provisions them. There is no separate deploy button. - Component details (click a component): Properties (settings; for a Secret, a key/value editor), Logs (containers and functions), Replicas (container instances, with a rolling restart), Files (bucket file browser), Outputs (URLs and DNS records), Connections, and Settings (remove). Logs, Replicas, and Files appear once the component is deployed. - Versions: a Git-backed timeline of every apply; each version has build logs and a read-only snapshot of the infrastructure at that point. - Usage: cloud cost over time (total, daily average, trend, per-day table) for the last 7, 30, or 90 days. Cost data is delayed a few days. - Security: a posture scan with an overall grade, passing checks, and open issues with remediation links. Populated once deployed. - Settings: rename and set an icon, Clone with Git (generate an API key and a git clone URL), Export as a zip, and Delete. --- # CLI Install: `curl -fsSL https://spawned.sh/install.sh | bash`. The installer picks the build for your machine, puts the `spawned` binary in `~/.local/bin`, and adds bash, zsh, or fish completions. macOS, Linux, and Windows under Git Bash or WSL are supported. Set `INSTALL_DIR` to install elsewhere; add that directory to your PATH if the script says `spawned` is not on it. Re-run the same command to upgrade. Sign in with `spawned login`; `spawned --version` confirms the install and `spawned logout` clears the stored tokens (`~/.config/spawned/` on macOS and Linux, `%LOCALAPPDATA%\spawned\` on Windows). In CI or anywhere without a browser, set `SPAWNED_API_KEY` to an API key instead of signing in. The CLI works from an `infra.json`. A typical flow: `spawned init ` to create a project, edit the `infra.json` (by hand or with your coding agent), and `spawned apply` followed by `spawned get `. Commands: `init`, `validate`, `apply`, `list`, `get`, `logs`, `builds`, `upload`, `export`, `schema`, `clouds`, `org`, `apikeys`, `repos`, `config`, `login`, `logout`. Run `spawned --help` for details. Global flags: `-h, --help`; `-v, --version`. --- # Agent Skill The skill is a single Markdown file that teaches a coding agent how to work with Spawned: the shape of an `infra.json`, the CLI commands and their flags, how to read a failed build, and what to do when a deploy is stuck. It is published at https://spawned.ai/SKILL.md and always describes the current platform. It drives the CLI, so install the CLI first. Three ways to install the same file: - Paste a URL, for any agent that can fetch a page, with nothing to install: "Read https://spawned.ai/SKILL.md and use the Spawned CLI to configure this codebase for my project." A project's dashboard offers the same prompt with the project name filled in, next to the CLI install command on an empty canvas. - Claude Code plugin, for people who deploy often: `/plugin marketplace add spawned-ai/spawned-skill` then `/plugin install spawned@spawned`. Invoke with `/spawned`. Update with `/plugin marketplace update spawned` and `/plugin update spawned@spawned`. - File on disk, for agents that read local files or work offline: `mkdir -p ~/.config/spawned/skills` then `curl -s https://spawned.ai/SKILL.md > ~/.config/spawned/skills/SKILL.md`. Re-run to refresh; this is the only method that can go stale silently. Update the skill when a command, flag, or schema field it expects does not match what the CLI does. Whichever method you use, `spawned schema` is the authoritative list of components, fields, and connections; when the skill and the schema disagree, the schema is right. For everything else these docs cover, point an agent at https://spawned.ai/llms-full.txt, the complete documentation in one file. --- # Custom Domains Serve your project from a domain you own, with HTTPS provisioned for you. Most projects also start out reachable at a free `your-project.spawned.app` address. There are two routes, in one list under your organization's Settings > Custom domains. Both are added through the same row: a Domain field, a Type dropdown offering "CNAME records" or "Nameservers", a Cloud dropdown, and an Add domain button. Pointing one hostname is the default Type and leaves your DNS with you (two `CNAME` records, usable by any project in the organization including ones on the shared cloud). Delegating is the heavier option, chosen by switching Type to "Nameservers", and hands Spawned the whole zone (four nameservers, the domain lives in a Cloud you connected). Some things only work that way: wildcard certificates, new hostnames going live without another DNS change, and root domains on a provider that cannot flatten a CNAME at the zone root. Pointing one hostname with CNAME records: 1. Add the hostname (for example `app.example.com`) under Settings > Custom domains, with Type left on "CNAME records". The Cloud dropdown stays disabled on this route; the hostname is owned by the organization. Root domains work here too, but only on a provider that flattens a CNAME at the zone root (Cloudflare) or offers an ALIAS/ANAME record that can point at an outside name (Namecheap, Porkbun, DNSimple, Gandi, Google Cloud DNS and others), since a CNAME cannot literally sit alongside the SOA and NS records at an apex. Route 53 and Azure DNS do not qualify: their ALIAS records only target their own resources, so delegate the zone instead. 2. Add both records shown, at your DNS provider. One `CNAME` on the hostname itself sends visitors to your project; one `CNAME` on `_acme-challenge.` hands certificate validation to Spawned so HTTPS is issued now and renewed automatically. Neither value ever changes. Turn off any proxy option. 3. Status advances from Waiting for DNS records to Verified, rechecked every 20 seconds while the page is open, and a Check button runs one immediately. Expanding the row ("View DNS configuration") shows both records, each with its own status dot: amber until Spawned can resolve it, green once it does, which is how you tell which of the two is holding things up. Once Verified, a padlock line under the row reads "Issuing certificate…" and becomes "HTTPS ready" on its own. 4. Serve it from a project, once it is Verified: on the connection between the project's domain and its load balancer or function, add the hostname to `aliases` and apply. Applying with an unverified hostname fails. It serves alongside the project's existing address. A CDN connection cannot take aliases, since a distribution carries one certificate that must cover every name on it. Delegating a whole domain: 1. Add the domain under Settings > Custom domains with Type switched to "Nameservers". The Cloud dropdown becomes selectable; choose which Cloud the zone lives in. Either a root domain or a subdomain can be delegated. 2. Delegate with nameservers. Spawned shows four nameservers; point your domain at them (root domain: replace the nameservers; subdomain: add four `NS` records). Turn off any proxy option. This is the only DNS change you need. 3. Verification and certificates advance through: Waiting for nameservers, Issuing certificates, Verified (or Failed). A wildcard TLS certificate is issued and renewed automatically. 4. Attach the domain to a project from the canvas: add an Imported Domain, select your verified domain (same Cloud as the project), set the hostname, and connect it to your container (or a load balancer or CDN). --- # GitHub Install the GitHub App from Settings to deploy from private repositories. Public repositories work without connecting. --- # Organizations & Teams Every account starts with a personal organization, created automatically; your own projects live there. Create a team organization to collaborate; its projects, billing, connected Clouds, and Kubernetes clusters are shared by the team. Roles: Owner (full control, including members, billing, connected Clouds, and clusters) and Member (can work with the organization's projects). Invite people from the organization's member settings; they receive an invite link that adds them once accepted. An organization holds projects, billing and credits, connected Clouds, and an audit log of what changed and who changed it. Choose your active organization from the switcher, or scope CLI commands with `--org`. --- # Billing & Credits Billing runs on credits. Personal organizations get a free monthly credit allowance (currently $20) applied to usage, so small projects can stay free. Add more credit from your billing settings. Pricing depends on where a project runs: - Bring your own Cloud: pay Spawned a monthly rate for each component in your project (rates vary by component type), and pay your cloud provider directly for the underlying resources. - Spawned-managed: Spawned bills a percentage of your cloud spend, and the cloud costs are included in your Spawned bill. Billing belongs to the organization: a personal organization is billed to you, and a team organization shares a single credit balance. See https://spawned.ai/pricing for current rates. Track a project's own cloud cost under Usage on its dashboard. --- # API Keys API keys authenticate you outside the browser. Create one under Settings > API keys or with `spawned apikeys create `; the `sk_`-prefixed key is shown only once. List and revoke with `spawned apikeys list` and `spawned apikeys revoke`. Uses: read-only Git access to a project's generated infrastructure (the key is the HTTPS password), ArgoCD authentication for Kubernetes clusters, and running the CLI in CI or other non-interactive environments. Treat keys like passwords and revoke any that leak. --- # Writing These Docs House style for the docs themselves (`/docs/style`), for anyone adding a page. Section headings (`##`) are Title Case and match the sidebar label. Subheadings (`###`) follow what they name: Title Case for a named thing (Docker Hub), sentence case for a description (Build fails); one style per page. Separate major sections with a horizontal rule. Two spacing tiers: figures (tables, diagrams) sit at 32px, code blocks and paragraphs at 20px, so a bordered block reads as its own object rather than part of the prose. Keep JSON lines under ~70 characters or the docs column gains a permanent horizontal scrollbar. Stay at the abstraction the reader works in: components and connections, not the cloud resources they compile to. Say what a connection grants, not what it emits. Reference pages lead with a table, explanatory pages with prose. No em dashes. The "Last updated" stamp under each H1 is generated from the last commit that changed that page, never hardcoded. --- # Troubleshooting - Build fails: build your `Dockerfile` locally first, commit your dependency file, and use the Context field for build args or steps. - Deployment hangs: provisioning typically takes 1-10 minutes; check the logs in your project's version history. - Container won't start: check the component's Logs, confirm the port, add secrets via a Secret component connected to the container, and raise memory if it is OOMKilled. - Domain stuck on "Waiting for nameservers": confirm delegation to the nameservers shown in Settings > Custom domains, with any proxy turned off; verify with `dig NS` or dnschecker.org. - Domain stuck on "Issuing certificates": this is automatic; if it does not complete, ensure any `CAA` record allows Amazon to issue certificates. - Domain verification failed: confirm the nameservers match exactly, then delete and re-add the domain. - GitHub: install the GitHub App (on the organization for org repos). CI/CD applies to GitHub repository sources on the default branch. Getting help: join the Discord community (https://discord.gg/cdcZaYv94C) or email team@askrike.ai.