# Backend branching Source: https://docs.insforge.dev/agent-native/branching Spin up an isolated child project with its own Postgres, auth, storage, and functions to test schema and config changes, then merge or reset safely. A branch is a child project with its own Postgres, auth config, storage, edge functions, email templates, realtime channels, and schedules. Merge back when ready or reset and retry. Available on InsForge OSS 2.1.0+. ## Concepts Each branch runs on its own EC2 instance, restored from the parent at create time. `merge` runs a three-way diff against the parent's create-time state. The branch shares the parent's `JWT_SECRET` but gets its own `API_KEY`. Compute services and frontend deployments do not branch. ## Usage Create a branch with `full` (schema + data) or `schema-only` (faster, empty user tables). ```bash theme={null} npx @insforge/cli branch create feat-billing --mode full npx @insforge/cli branch list ``` Preview the merge SQL before applying. ```bash theme={null} npx @insforge/cli branch merge feat-billing --dry-run --save-sql ./preview.sql npx @insforge/cli branch merge feat-billing ``` Roll back to the create-time snapshot, or delete the branch. ```bash theme={null} npx @insforge/cli branch reset feat-billing npx @insforge/cli branch delete feat-billing ``` ## Specific usage cases Use a branch for risky schema migrations, RLS rewrites, OAuth provider swaps, and edge function refactors. Skip it for trivial changes and data backfills (user-data rows are not auto-merged). Merges block on conflicts. Resolve on the branch or reset and retry. Quotas: 3 parent projects per org, 2 active branches per parent, no nesting. A successful merge does not auto-delete the branch. ## More resources * [Database migrations](/core-concepts/database/migrations) for forward-only SQL files. * [Database overview](/core-concepts/database/overview) for what runs under each branch. * [CLI reference](https://github.com/InsForge/InsForge) for the full `branch` flag set. # CLI harness Source: https://docs.insforge.dev/agent-native/cli-harness The @insforge/cli is the agent's hands: one terminal interface with JSON output for schema, config, deploys, and diagnostics across your InsForge project. The `@insforge/cli` is the interface a coding agent uses to operate your backend. Where a human reaches for the dashboard, the agent reaches for the terminal: it runs a command, reads the output, and decides what to do next. Every command speaks `--json`, so the agent works from structured data instead of scraping a screen. Run the CLI with `npx @insforge/cli`. Do not install it globally, so the agent always uses the version pinned to the project. ## Why a CLI for agents A dashboard is built for a human pointer; a CLI is built for anything that can write text. Pass `--json` to any command and the agent gets a structured result it can parse; pass `--yes` and it runs without stopping for a confirmation prompt. Schema, auth config, storage, functions, deploys, branches, and diagnostics are all subcommands of the same tool, so there is one surface to learn instead of a dashboard to navigate. It runs in any terminal, any editor, or CI, with no integration to set up. And after a change, the agent can run [`npx @insforge/cli diagnose`](/agent-native/diagnostics) and read back exactly what broke. ## Command surface | Area | Commands | | -------------- | -------------------------------------------------------------------------- | | Auth & context | `login`, `logout`, `whoami`, `current`, `list` | | Project | `create`, `link`, `projects update-version`, `projects delete` | | Schema | `db migrations new`, `db migrations up`, `db migrations list` | | Config as code | `config plan`, `config apply`, `config export` | | Branching | `branch create`, `branch merge`, `branch reset`, `branch delete` | | Build | `functions`, `storage`, `deployments`, `secrets`, `schedules`, `ai` | | Diagnose | `diagnose`, `diagnose advisor`, `diagnose db`, `diagnose logs`, `metadata` | Run `npx @insforge/cli help ` for the flags on any of these. ## Built-in secrets The `secrets` commands manage the same store your edge functions read from. Every project ships with reserved keys that you can read but not edit or delete: | Key | What it is | | ------------------- | ----------------------------------------------------------------------------------------------------- | | `ANON_KEY` | Public client key, passed as `anonKey` when you create an SDK client | | `API_KEY` | Admin API key (`ik_...`) for privileged server-side and CLI access | | `JWT_SECRET` | Secret for HS256-signed JWTs that InsForge accepts, seeded from the deployment's `JWT_SECRET` env var | | `INSFORGE_BASE_URL` | The project's API base URL | ```bash theme={null} npx @insforge/cli secrets list # every key, values hidden npx @insforge/cli secrets get ANON_KEY # decrypted value of one key ``` Use `secrets add`, `secrets update`, and `secrets delete` for your own keys, and `secrets rotate api-key` or `secrets rotate anon-key` to reissue a project key with an optional grace period. The API key and anon key are also in the dashboard: click **Install** and open **API Keys**. ## Upgrading and deleting a project ```bash theme={null} npx @insforge/cli projects update-version npx @insforge/cli projects delete --project ``` `projects update-version` moves the project to the latest InsForge backend version. It targets the linked project by default; pass `--project ` to pick another. The command restarts the instance, so expect a brief downtime. Add `--wait` to block until the update finishes. `projects delete` permanently deletes the project: its database, storage, and every other resource, including its backend branches. Here `--project` is required and never falls back to the linked project, so a stray ambient value can't point the deletion at the wrong one. Both commands confirm before acting, but only in interactive mode: `--yes` and `--json` each skip the prompt, so `projects delete --project --json` deletes immediately. Deletion is irreversible. Never pass `--yes` here, and have a human verify the exact project id before the command runs. To do either from the dashboard instead, both live under **Settings → General**. ## A typical agent run ```bash theme={null} # connect npx @insforge/cli login npx @insforge/cli link # read current state npx @insforge/cli --json metadata # change schema, safely npx @insforge/cli db migrations new add-orders-table npx @insforge/cli db migrations up --all # check the result npx @insforge/cli diagnose --json ``` ## Let the agent interpret the output Diagnostics ships an AI flag so the agent can hand its own backend data to a model and get back an explanation: ```bash theme={null} npx @insforge/cli diagnose --ai "why are auth requests failing after the last migration?" ``` This pairs the raw signals (advisor findings, DB health, error logs) with a plain-language read, which is what turns "here is a stack trace" into "here is the fix." See [Diagnostics & advisor](/agent-native/diagnostics). ## Next steps * Put auth, SMTP, storage, retention, and deployment settings in version control with [config as code](/agent-native/config-as-code). * Test risky changes on a [backend branch](/agent-native/branching) before touching production. # Config as code Source: https://docs.insforge.dev/agent-native/config-as-code Manage InsForge auth, SMTP, storage, and deployment settings from a single insforge.toml using the CLI's plan, apply, and export workflow. ## Overview `insforge.toml` is a declarative, version-controlled snapshot of a subset of your project's config: auth policy, allowed redirect URLs, password rules, SMTP, storage upload size, realtime and schedule retention, and the deployment subdomain. The CLI provides three commands that work against this file: * **`config plan`**: diff `insforge.toml` against the linked project. Shows what would change. * **`config apply`**: push the diff to the live project. Per-change capability gating, env-resolved secrets, dry-run mode. * **`config export`**: pull current project state and write a fresh `insforge.toml`. Useful for bootstrapping from an existing project. You keep **one** `insforge.toml` in your repo. To apply it to a different environment (staging, prod, a teammate's local backend, or a self-hosted instance), point the CLI at a different project (re-link or use `--project-id`). Secrets are read from environment variables via `env(...)` references, so the file itself stays free of credentials and can be committed safely. This works the same way on InsForge Cloud projects and on self-hosted OSS deployments. All examples use `npx @insforge/cli`. Do not install the CLI globally. ## Command summary | Command | Purpose | | ----------------------------- | -------------------------------------------------------------------- | | `config plan` | Show diff between `insforge.toml` and live project state | | `config apply` | Apply `insforge.toml` to the live project | | `config apply --dry-run` | Print the plan without applying | | `config apply --auto-approve` | Skip the interactive confirmation prompt (required in `--json` mode) | | `config export` | Pull live config and write `insforge.toml` | | `config export --force` | Overwrite an existing `insforge.toml` without confirmation | `config plan` and `config apply` read `insforge.toml`; pass `--file ` if it lives somewhere other than `./insforge.toml`. `config export` writes the file; pass `--out ` to write it to a custom location. ## Recommended workflow If you already configured the project through the dashboard, export it once to get a working file: ```bash theme={null} npx @insforge/cli config export ``` This writes `insforge.toml` reflecting the current backend state. Check `insforge.toml` into version control. Secrets are referenced via `env(...)`, so the file is safe to commit. Change the TOML, then preview the diff before applying: ```bash theme={null} npx @insforge/cli config plan ``` ```bash theme={null} npx @insforge/cli config apply ``` Review the rendered plan and confirm. In CI, pass `--auto-approve` and `--json`. To push the same config to staging or a self-hosted backend, point the CLI at the other project and re-run apply: ```bash theme={null} npx @insforge/cli --project-id config apply ``` Secrets that differ between environments (SMTP password, etc.) are resolved per-environment from the local shell, so the TOML doesn't need to change. ## What `insforge.toml` covers The file mirrors a curated subset of project config, the parts that are useful to manage declaratively. Anything not in this list still lives on the dashboard and the API. | Section | Keys | | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | | `[auth]` | `allowed_redirect_urls`, `require_email_verification`, `verify_email_method`, `reset_password_method`, `disable_signup` | | `[auth.password]` | `min_length`, `require_number`, `require_lowercase`, `require_uppercase`, `require_special_char` | | `[auth.smtp]` | `enabled`, `host`, `port`, `username`, `password`, `sender_email`, `sender_name`, `min_interval_seconds` | | `[storage]` | `max_file_size_mb` | | `[realtime]` | `retention_days` | | `[schedules]` | `retention_days` | | `[deployments]` | `subdomain` | Because TOML has no `null` literal, use `retention_days = 0` to disable retention cleanup for realtime messages or schedule execution logs; `config apply` sends `null` to the backend for that value. Email templates, OAuth provider app credentials, buckets, realtime channels, functions, deployment environment variables, and secrets are not managed through this file. A complete example: ```toml theme={null} [auth] require_email_verification = true verify_email_method = "code" reset_password_method = "code" disable_signup = false allowed_redirect_urls = [ "https://app.example.com/auth/callback", "http://localhost:3000/auth/callback", ] [auth.password] min_length = 12 require_number = true require_lowercase = true require_uppercase = true require_special_char = false [auth.smtp] enabled = true host = "smtp.sendgrid.net" port = 587 username = "apikey" password = "env(SENDGRID_API_KEY)" sender_email = "noreply@example.com" sender_name = "Acme" [storage] max_file_size_mb = 100 [realtime] retention_days = 7 [schedules] retention_days = 0 [deployments] subdomain = "acme-prod" ``` ## `config plan` ```bash theme={null} npx @insforge/cli config plan npx @insforge/cli config plan --file ./config/insforge.toml npx @insforge/cli --json config plan ``` `plan` reads `insforge.toml`, fetches live state via `/api/metadata` and the optional config endpoints for storage, realtime, and schedules, then prints a rendered diff. It also tags any section the live backend doesn't expose yet (older self-hosted versions, etc.). Apply will skip those instead of failing the whole run. Use `plan` before every `apply` in interactive sessions, and as a CI gate to catch unintended drift. ## `config apply` ```bash theme={null} npx @insforge/cli config apply npx @insforge/cli config apply --dry-run npx @insforge/cli config apply --auto-approve npx @insforge/cli --json config apply --auto-approve ``` `apply` runs the same diff as `plan`, then walks the change set: 1. **Per-change capability gate.** Each change is checked against the backend's metadata or the section's config endpoint. If the backend doesn't support a section (e.g. an older self-hosted instance without SMTP exposed), that section is skipped with a named warning, and the rest of the changes still apply. 2. **Secret resolution.** `env(...)` references in the TOML are resolved at apply time from the local environment. If a referenced variable is missing, the command aborts before sending any update, so the backend isn't left half-configured. 3. **Per-section dispatch.** Each change is sent to the appropriate backend endpoint (`/api/auth/config`, `/api/auth/smtp-config`, `/api/storage/config`, `/api/realtime/config`, `/api/schedules/config`, `/api/deployments/slug`, etc.). Changes are independent, so a failure on one section won't roll back earlier successful sections. Flags: * `--dry-run` prints the plan and exits without applying. * `--auto-approve` skips the interactive confirmation. Required when `--json` is set, since there's no TTY for the prompt. * `--file ` overrides the default `./insforge.toml` location. ## `config export` ```bash theme={null} npx @insforge/cli config export npx @insforge/cli config export --out ./config/insforge.toml npx @insforge/cli config export --force ``` `export` pulls the live project's configurable surface and writes it to a TOML file. Use it to: * Bootstrap an `insforge.toml` from a project you've been configuring through the dashboard. * Diff hand-edits against current backend state by exporting to a temporary file and comparing. * Snapshot config before a risky change so you can re-apply the snapshot if you need to roll back. The file written by `export` is the same shape the CLI expects from `apply`, so round-tripping is supported. Without `--force`, `export` refuses to overwrite an existing file in interactive mode and surfaces an `OUTPUT_EXISTS` error in `--json` mode. ## Secret references `auth.smtp.password` and any other sensitive field can be expressed as `env(VAR_NAME)` instead of a literal value: ```toml theme={null} [auth.smtp] password = "env(SENDGRID_API_KEY)" ``` At apply time the CLI reads `SENDGRID_API_KEY` from the local environment, validates it's present, and sends the resolved value to the backend. The TOML itself never contains the secret, so it can be committed. This is what lets one `insforge.toml` apply cleanly to multiple environments: the dev and prod backends differ only in *which* `SENDGRID_API_KEY` is in scope when you run `apply`. `env(...)` refs in a TOML that's the target of `apply` re-send the resolved password on every run. That is the only way the CLI can tell the backend "the secret may have rotated, please update." Fields without an `env(...)` ref are treated as preserve-existing. ## When to use this * **Version control for project config.** Redirect URLs, sign-up policy, password policy, email verification mode, SMTP, storage upload size, and retention windows live in a file your team reviews via PR. * **Multi-environment parity.** One TOML, applied to dev, staging, and prod, keeps the supported project settings aligned everywhere. Environment-specific values (subdomain, SMTP credentials) flow through `--project-id` overrides and `env(...)` refs. * **CI-driven config changes.** Run `config apply --auto-approve --json` from your deploy pipeline. Combine with `config plan` as a PR check so reviewers see what the merge will change in prod. * **Disaster recovery.** A committed `insforge.toml` is a known-good config snapshot. Re-apply it after restoring a project to bring auth, SMTP, storage, retention, and deployment settings back to the expected shape in seconds. * **Self-hosted and local OSS development.** Run `npx @insforge/cli link --api-base-url http://localhost:7130 --api-key ` against a docker-compose stack, then point the CLI's config commands at your local OSS instance the same way you'd point at cloud. ## Troubleshooting **`Refusing to apply in --json mode without --auto-approve or --yes`.** The CLI never silently applies changes in non-interactive runs. Pass `--auto-approve` (or `-y`) explicitly. **`your backend doesn't expose
`.** The linked backend is on a version that doesn't have the relevant API yet. The rest of your changes still applied. Upgrade the backend (or wait for the next release) to apply that section. **`env(...)` reference resolves to nothing.** The CLI aborts before any API call when a referenced env var is missing. Set the variable in your shell or your CI's secret store and re-run. **`Slug is already taken`.** `deployments.subdomain` conflicts with another project's subdomain on the same backend. Pick a different value. ## Related * [CLI harness](/agent-native/cli-harness): the full command surface an agent drives * [Deployment security guide](/deployment/deployment-security-guide): hardening a self-hosted backend after deploy # Diagnostics & advisor Source: https://docs.insforge.dev/agent-native/diagnostics Use insforge diagnose so the agent can read backend health, advisor findings, RLS warnings, and error logs, then apply the fix without a dashboard. When something breaks, an agent should not have to guess. `npx @insforge/cli diagnose` turns the backend's own signals into something an agent can fetch and act on directly: advisor findings with remediations, database health checks, instance metrics, and aggregated error logs. Each finding comes with a fix, so "what is wrong" and "what to do about it" arrive together. The agent pulls all of this itself, with no dashboard in the loop, which is what lets it close security gaps like a permissive RLS policy before they leak data. Backend Advisor finding: a permissive RLS policy on public.messages with a Copy Remediation button ## Full health report Run `diagnose` with no subcommand for everything at once: ```bash theme={null} npx @insforge/cli diagnose npx @insforge/cli diagnose --json ``` The report bundles the advisor scan, database health, instance metrics, and recent error logs into one output. In `--json` mode the agent gets it all as structured data. ## Backend Advisor The advisor scans for security, performance, and health issues and writes a remediation for each one. The dashboard shows the findings with a "Copy Remediation" button, but the agent does not need the dashboard: it fetches the same scan directly with `--json` and applies the fix itself, so a security issue gets closed without waiting for a human to notice it. ```bash theme={null} npx @insforge/cli diagnose advisor npx @insforge/cli diagnose advisor --json ``` A typical finding is a permissive RLS policy that exposes a table to anonymous users. The advisor names the table, the severity, and the SQL to fix it. The agent reads the finding, applies the remediation as a [migration](/core-concepts/database/migrations), and re-scans to confirm it cleared. ## Targeted checks Each part of the report is also available on its own: | Command | What it checks | | ------------------ | ------------------------------------------------------------- | | `diagnose advisor` | Latest advisor scan: security, performance, and health issues | | `diagnose db` | Database health: connections, table bloat, index usage | | `diagnose metrics` | Instance metrics: CPU, memory, disk, and network | | `diagnose logs` | Error-level logs aggregated across every backend source | For raw logs from a single source, use the top-level command: ```bash theme={null} npx @insforge/cli logs function.logs npx @insforge/cli logs postgres.logs ``` Sources include `insforge.logs`, `postgREST.logs`, `postgres.logs`, `function.logs`, and `function-deploy.logs`. ## Let the agent interpret it `diagnose --ai` hands the collected diagnostic data to a model and returns a plain-language analysis: ```bash theme={null} npx @insforge/cli diagnose --ai "why did write latency spike after the last deploy?" ``` This is the difference between dumping a stack trace and explaining the fix. The agent asks a question about the live backend and gets an answer grounded in the actual signals. ## Next steps * Apply advisor remediations as [migrations](/core-concepts/database/migrations) so the fix is versioned. * Rehearse a risky fix on a [backend branch](/agent-native/branching) before it touches production. * Read the [CLI harness](/agent-native/cli-harness) for the rest of the command surface. # Agent-Native Initiatives Source: https://docs.insforge.dev/agent-native/overview Explore the primitives — CLI harness, config as code, branching, and diagnostics — that let a coding agent operate your InsForge backend without a dashboard. Most backends assume a human in a dashboard. InsForge assumes a coding agent at a terminal. The products (Database, Auth, Storage, and the rest) are the building blocks; the primitives on this page are how an agent operates them: as files it can edit, branches it can test on, and a backend it can diagnose and fix on its own. New here? Start with [Connect via CLI](/quickstart) to link your project. This section is about how an agent *works* with the backend once it is connected. ## The primitives The agent's hands. One terminal interface for login, schema, deploys, config, and diagnostics. Pull advisor findings, DB health, metrics, and error logs the agent can read and fix. Auth, SMTP, storage, retention, and deployment settings live in `insforge.toml`. Plan and apply like infrastructure. Clone the whole backend into an isolated branch to test risky changes, then merge or reset. ## Why it matters A person and an agent want different things from a backend. A person wants a UI to click. An agent wants a stable text interface it can drive, read back, and reason about. That shows up everywhere in InsForge. Schema changes are [migrations](/core-concepts/database/migrations) in your repo, and project config is an [`insforge.toml`](/agent-native/config-as-code) file, so the agent edits text, commits it, and reviews it in a PR instead of clicking through forms. When it wants to try something risky, it spins up a [backend branch](/agent-native/branching), runs the change against a copy, and throws the branch away if it goes wrong. When something looks off, it fetches [diagnostics and advisor findings](/agent-native/diagnostics) directly with `npx @insforge/cli diagnose`, no dashboard in the loop, and applies the fix itself. That last part is also how the backend gets more secure: the agent reads a security finding like a permissive RLS policy and remediates it on its own, instead of waiting for a human to remember to check. And it reads current schemas, logs, and metadata straight from the backend with `npx @insforge/cli metadata`, so it works from real state rather than guessing. ## The loop These chain together. A session usually goes: 1. Read the current state with `npx @insforge/cli metadata`. 2. Branch the backend, write a [migration](/core-concepts/database/migrations) and check what is pending with `npx @insforge/cli db migrations list`, or edit `insforge.toml` and preview the config diff with `npx @insforge/cli config plan`. 3. Apply it with `npx @insforge/cli db migrations up --all` or `config apply`, against the branch first, then the parent. 4. Run `npx @insforge/cli diagnose` to check advisor findings and error logs, and ask `diagnose --ai` to interpret them. 5. Apply the remediation, re-run diagnose, and merge the branch. ## Next steps * Read the [CLI harness](/agent-native/cli-harness) to see the full command surface an agent drives. * Set up [config as code](/agent-native/config-as-code) so project settings live in version control. * Use [diagnostics](/agent-native/diagnostics) to let the agent find and fix backend issues. # VS Code extension Source: https://docs.insforge.dev/agent-native/vscode-extension Sign in to InsForge from VS Code, pick an org and project, and install the InsForge MCP server into your AI coding assistant with one click. ## Overview The InsForge VS Code extension: * Logs you in to InsForge (OAuth + PKCE) * Lets you pick an org/project * Installs InsForge MCP for your AI client It launches the MCP installer (`npx @insforge/install`) with your project’s API key and API base URL. ## Links * [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=InsForge.insforge) * [Open VSX](https://open-vsx.org/extension/InsForge/insforge) ## Install 1. Open VS Code 2. Go to **Extensions** 3. Search for **InsForge** 4. Install the extension published by **insforge** ## Use the extension Click the **InsForge** icon in the Activity Bar (left sidebar). Click **Login with InsForge** and complete the login flow in your browser. The callback is [http://127.0.0.1:54321/callback](http://127.0.0.1:54321/callback). Pick an organization and project from the InsForge tree view, or run InsForge: Select Project from the Command Palette. Run InsForge: Install MCP (or right-click a project and choose **Install MCP**), then pick the AI client you want to configure. The installer runs in a VS Code terminal. ## Supported AI clients The extension supports the following MCP “clients” (the value passed to `@insforge/install`): | Client | Typical config location | | ------------- | ---------------------------------------------------- | | `cursor` | `~/.cursor/mcp.json` | | `claude-code` | `.mcp.json` in your workspace (project-local) | | `copilot` | `.vscode/mcp.json` in your workspace (project-local) | | `windsurf` | `~/.codeium/windsurf/` | | `cline` | Cline VS Code extension config | | `roocode` | Roo Code VS Code extension config | | `codex` | OpenAI Codex CLI config | | `trae` | Trae IDE config | | `qoder` | Qoder IDE config | If your client isn’t listed, run the installer manually (see Quickstart). ## Notes * **Project-local installs**: `claude-code` and `copilot` require an open workspace folder. * **Port in use**: free up `54321` and retry login. # Alternatives Source: https://docs.insforge.dev/alternatives Compare InsForge with Supabase and Firebase across Postgres, auth, storage, functions, MCP, and agent-native CLI workflows for coding agents. InsForge is the **agent-native cloud infrastructure platform**. A coding agent gives any app a Postgres database, authentication, storage, edge functions, compute, hosting, and an AI model gateway, driven end to end through one CLI. It is open source (Apache-2.0). Its MCP server and CLI expose that infrastructure as structured, machine-readable context, so AI coding agents can plan and execute operations autonomously within scoped permissions. The shift in the human's role: with traditional platforms you **implement and execute**; with InsForge you **define intent and review**. For the full, always-current write-ups see [InsForge vs Supabase](https://insforge.dev/alternatives/insforge-vs-supabase) and [InsForge vs Firebase](https://insforge.dev/alternatives/insforge-vs-firebase). ## InsForge vs. Supabase Supabase is an open-source, Postgres-based backend built for human developers. InsForge shares the Postgres foundation but is built for AI coding agents as first-class operators. | Dimension | Supabase | InsForge | | ------------------- | ------------------------------ | ---------------------------------- | | Primary operator | Human developers | AI coding agents | | Configuration model | Explicit UI, SQL, CLI | Agent-executed operations | | Permission design | Developer-managed access | Scoped agent autonomy | | System context | Implicit, developer-maintained | Structured, machine-readable (MCP) | | Payments | Via app code | Built-in Stripe integration | | AI model access | External model wiring | Built-in model gateway | | Security | Per-integration implementation | Production-ready defaults | | Agent readiness | Requires manual wiring | End-to-end operable | ## InsForge vs. Firebase Firebase targets rapid prototyping on document-oriented NoSQL with human operators. InsForge targets agentic coding on relational Postgres. | Dimension | Firebase | InsForge | | ---------------- | ----------------------- | ---------------------------------- | | Database | Document-oriented NoSQL | Relational Postgres | | Data model | Schemaless JSON | Structured tables with relations | | Joins | Not natively supported | Native joins and queries | | Transactions | Supported with limits | Full ACID transactions | | Self-hosting | Not supported | Docker / self-hostable | | Primary operator | Human developers | AI coding agents | | System context | Implicit, fragmented | Structured, machine-readable (MCP) | | AI integration | External setup | Built-in model gateway | | Payments | Manual integration | Built-in Stripe primitive | ## Why teams pick InsForge One MCP server exposes schemas, permissions, and logs as structured context, so an AI coding agent can provision and operate the whole stack end to end, not just query a database. Payments (Stripe), an AI model gateway, and deployment are included out of the box, instead of wiring each integration yourself. Native joins, full ACID transactions, and schema enforcement, so your data and skills transfer. No proprietary document model to design around. Run it on your own infrastructure with Docker, or use the managed cloud. Create a project and connect your favorite AI coding agent. # Create and Execute Database Migration Source: https://docs.insforge.dev/api-reference/admin/create-and-execute-database-migration https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml post /api/database/migrations # Create schedule Source: https://docs.insforge.dev/api-reference/admin/create-schedule https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/schedules.yaml post /api/schedules Creates a new scheduled job. # Create Table Source: https://docs.insforge.dev/api-reference/admin/create-table https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml post /api/database/tables # Delete Table Source: https://docs.insforge.dev/api-reference/admin/delete-table https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml delete /api/database/tables/{tableName} # Get Table Schema Source: https://docs.insforge.dev/api-reference/admin/get-table-schema https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/tables/{tableName}/schema # List Database Migrations Source: https://docs.insforge.dev/api-reference/admin/list-database-migrations https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/migrations # List schedules Source: https://docs.insforge.dev/api-reference/admin/list-schedules https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/schedules.yaml get /api/schedules Returns all schedules configured in the system. # List Tables Source: https://docs.insforge.dev/api-reference/admin/list-tables https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml get /api/database/tables # Update Table Schema Source: https://docs.insforge.dev/api-reference/admin/update-table-schema https://raw.githubusercontent.com/InsForge/InsForge/main/openapi/tables.yaml patch /api/database/tables/{tableName}/schema # Model Gateway Source: https://docs.insforge.dev/core-concepts/ai/overview Call any LLM through one OpenAI-compatible endpoint, with InsForge-managed provider keys, OpenRouter routing, and per-project usage tracking and quotas. Use the Model Gateway to call chat, streaming, and embedding models through one OpenAI-compatible endpoint. InsForge holds the provider keys, tracks usage per project, and routes traffic through [OpenRouter](https://openrouter.ai), so your application code never sees Anthropic, OpenAI, or Mistral credentials directly. InsForge dashboard Model Gateway overview showing code samples, provider chips, and usage charts **Want to run AI code, not call a model?** Use [Edge Functions](/core-concepts/functions/overview) to orchestrate prompts, retrieval, and tools. The Model Gateway is the call; functions are the program around it. ```mermaid theme={null} graph TB Dashboard[InsForge Dashboard] --> Key[Active OpenRouter Key] Dashboard --> Catalog[OpenRouter Model Catalog] Dashboard --> Metrics[OpenRouter Usage Overview] App[Application Backend or Server Route] --> SDK[OpenAI SDK] SDK --> OpenRouter[OpenRouter API] OpenRouter --> OpenAI[OpenAI] OpenRouter --> Anthropic[Anthropic] OpenRouter --> Google[Google] OpenRouter --> More[Other Providers] style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style App fill:#166534,stroke:#22c55e,color:#dcfce7 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style OpenRouter fill:#c2410c,stroke:#fb923c,color:#fed7aa ``` ## Features ### OpenAI-compatible API Point any OpenAI SDK or `openai`-compatible library at `https://.insforge.dev/v1` and it works. `/v1/chat/completions`, `/v1/embeddings`, and `/v1/models` all behave like the upstream spec. ### Streaming Server-sent events for chat completions. Use the streaming endpoint the same way you would with OpenAI; the gateway forwards tokens as they arrive from the provider. ### Embeddings Generate dense vectors from any embedding model OpenRouter supports. Store the result in Postgres with [pgvector](/core-concepts/database/pgvector) for semantic search. ### Per-project quotas Each project carries its own rate limit and spend cap. Hit it, and the gateway returns a clean 429 instead of leaking provider quota state into your app. ### Usage tracking Every request is logged with model, token count, and cost. Query usage from the dashboard, CLI, or MCP — billing reconciles to OpenRouter's invoice automatically. ### Multi-provider routing Switch between Anthropic, OpenAI, Mistral, Llama, Gemini, and dozens more by changing the model name in the request. Application code does not change. ## Build with it Chat, stream, and embed from Node, browser, and edge runtimes. Native Swift AI client for iOS and macOS. Coroutines-first AI client for Android and JVM. Plain HTTP AI endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/ai) for chat and embedding patterns. # Analytics Source: https://docs.insforge.dev/core-concepts/analytics/overview Wire PostHog to your InsForge project to see traffic, retention, and session replay dashboards for your app without leaving the InsForge dashboard. Use InsForge Analytics to understand how people actually use your app: page traffic, retention, and session replays, all wired up by connecting a PostHog project to your InsForge backend. Once connected, the dashboard renders Traffic, User Retention, and Session Replay pages on top of your PostHog data without leaving InsForge. Connect PostHog once with one click, drop the setup prompt into your coding agent so it runs the PostHog wizard and installs the PostHog SDK on your frontend, and the Analytics pages start filling in. InsForge Analytics dashboard showing visitor KPIs, a visitor trend chart, and top pages, countries, and devices breakdowns PostHog remains the source of truth for events, dashboards, insights, and recordings. InsForge surfaces a focused subset for everyday checks, then deep-links into PostHog for anything beyond it. ```mermaid theme={null} flowchart TB Admin["Dashboard"] --> AnalyticsAPI["Analytics API"] App["Frontend App"] --> SDK["PostHog SDK"] SDK --> PostHog["PostHog"] AnalyticsAPI --> PostHog PostHog --> Traffic["Traffic / KPIs"] PostHog --> Retention["User Retention"] PostHog --> Replay["Session Replay"] Traffic --> Pages["Analytics pages"] Retention --> Pages Replay --> Pages style Admin fill:#1e293b,stroke:#475569,color:#e2e8f0 style App fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style AnalyticsAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style PostHog fill:#c2410c,stroke:#fb923c,color:#fed7aa style Traffic fill:#0e7490,stroke:#06b6d4,color:#cffafe style Retention fill:#0e7490,stroke:#06b6d4,color:#cffafe style Replay fill:#0e7490,stroke:#06b6d4,color:#cffafe style Pages fill:#6b21a8,stroke:#a855f7,color:#f3e8ff ``` ## Features ### One-click PostHog connection Connect PostHog from the Analytics page in the dashboard. InsForge provisions or links a PostHog project for you, stores credentials server-side, and unlocks the Traffic, Retention, and Session Replay pages once the connection succeeds. ### SDK setup via PostHog wizard After connecting, the empty state ships a setup prompt you can paste into your coding agent: ``` I want to add product analytics to this project. Read the current directory and use the InsForge skill to set up PostHog analytics by running `npx @insforge/cli posthog setup`. ``` `@insforge/cli posthog setup` links your InsForge project to PostHog, then prints the official [PostHog wizard](https://posthog.com/docs/libraries/wizard) command (`npx -y @posthog/wizard@latest`) for you (or your agent) to run next. The wizard detects your framework, installs the right PostHog SDK, and drops in initialization code so pageviews, autocapture events, and session recordings start flowing. ### Traffic KPIs over your selected time range (visitors, pageviews, sessions, bounce rate, and trend), plus breakdowns by Page, Country, and Device Type. Useful for the first "how is the app doing this week" pass without opening PostHog. ### User retention Cohort retention chart built from your PostHog events. Pick a time range and see how many users come back over the following days or weeks. ### Session replay A paginated list of recent session recordings with duration, person, and a deep-link into PostHog's full replay player. Helps you watch what users actually did right after spotting something odd in Traffic or Retention. ### Settings and disconnect The Analytics Config dialog (the gear icon in the sidebar) lets admins review the linked PostHog project, jump straight into PostHog, and disconnect when needed. Disconnecting only severs the InsForge ↔ PostHog link; your PostHog project, events, and recordings stay intact. ## Concepts Events, autocapture, insights, and dashboards behind the Analytics pages. How recordings are captured, redacted, and played back. ## Build with it Auto-detects your framework, installs the right PostHog SDK, and adds initialization code. Capture custom events on top of what the wizard sets up. `npx @insforge/cli posthog setup` links your InsForge project to PostHog, then prints the wizard command. ## Next steps * Open the Analytics page in the dashboard and click **Connect PostHog**. * Paste the setup prompt into your coding agent, then run the `@posthog/wizard` command it prints to wire the SDK into your app. * Set up the [CLI](/quickstart) if you want to manage the connection from the terminal. # Authentication Source: https://docs.insforge.dev/core-concepts/authentication/overview Authenticate and authorize users in InsForge with email, magic links, one-time codes, OAuth, OIDC providers, and JWT-based sessions. Use InsForge Authentication to handle sign-up, login, sessions, and identity for your app. Users can sign in with email and password, magic link, one-time code, OAuth providers (Google, GitHub, Apple, and others), or any OIDC-compliant identity provider you bring. InsForge issues JSON Web Tokens on login, and every other product on the platform consumes the same token. InsForge dashboard Auth Methods showing email and password, Google OAuth, and GitHub OAuth **Authentication** is checking that a user is who they say they are. **Authorization** is checking what they can do. InsForge handles the first directly and powers the second through [row-level security](/core-concepts/database/overview) policies that read the auth JWT. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> AuthAPI[Auth API] AuthAPI --> JWT[JWT Service] AuthAPI --> OAuth[OAuth Providers] AuthAPI --> DB[(PostgreSQL)] OAuth --> Google[Google OAuth 2.0] OAuth --> GitHub[GitHub OAuth] JWT --> Secret[Shared Secret] JWT --> Validation[Token Validation] DB --> Users[auth.users Table] DB --> Providers[auth.user_providers] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style AuthAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style JWT fill:#c2410c,stroke:#fb923c,color:#fed7aa style OAuth fill:#6b21a8,stroke:#a855f7,color:#f3e8ff style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Secret fill:#991b1b,stroke:#ef4444,color:#fee2e2 style Google fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style GitHub fill:#1e293b,stroke:#64748b,color:#f1f5f9 style Validation fill:#991b1b,stroke:#ef4444,color:#fee2e2 style Users fill:#0e7490,stroke:#22d3ee,color:#cffafe style Providers fill:#0e7490,stroke:#22d3ee,color:#cffafe ``` ## Features ### Email and password The default. New users sign up with an email and password, get a confirmation email, and receive a session JWT on login. Password reset, email verification, and brute-force throttling are built in. **Self-hosting?** Sending these emails, including passwordless magic-link and one-time-code sign-in, requires an email provider. Cloud-linked projects use the managed sender automatically; a self-hosted instance must set up [Custom SMTP](/core-concepts/messaging/custom-smtp) first, otherwise verification, reset, and sign-in emails will not be delivered. ### Magic link and OTP Send a one-time link or six-digit code to the user's email. Passwordless sign-in, account recovery, and step-up auth all use the same primitive. ### OAuth providers First-class support for Google, GitHub, Apple, Microsoft, GitLab, Discord, and more. Add custom OAuth 2.0 / OIDC providers (Keycloak, Okta, Auth0, your own IdP) by URL without writing provider-specific code. ### OAuth server mode Run InsForge itself as an OAuth 2.0 / OIDC identity provider for your own downstream apps. See the [OAuth Server guide](/oauth-server) for the full setup. ### Row-level security The auth JWT flows through every InsForge SDK call automatically. Postgres RLS policies read claims from the token and decide, row by row, what the user can read and write. The same identity and the same policies apply whether the request hits the database, storage, or a realtime channel. ### `auth.users` in your database User state lives in your project's Postgres database in the `auth` schema. Join `auth.users` to your application tables via foreign keys, react to identity changes with triggers, and back the whole thing up the same way you back up everything else. When you add a user from the dashboard, the Auto-confirm toggle sets `email_verified` on the new row. Enable it to mark the email as verified at creation, so the user can sign in without confirming it. ## Build with it Sign up, log in, and manage sessions from Node, browser, and edge. Native Swift auth client for iOS and macOS. Coroutines-first auth client for Android and JVM. Plain HTTP auth endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/auth) for sign-in patterns. # Custom Compute Source: https://docs.insforge.dev/core-concepts/compute/overview Run long-lived containers next to your InsForge project for queue workers, AI inference loops, websocket servers, background jobs, and scrapers. Use InsForge Custom Compute to run long-lived containers next to your project: queue workers, background processors, AI inference loops, websocket servers, scrapers, anything that needs to stay up. **Just need to handle a request?** Use [Edge Functions](/core-concepts/functions/overview) for request/response work and short jobs. Custom Compute is for processes that need to run continuously. ```mermaid theme={null} graph TB Dashboard[InsForge Dashboard] --> Service[Compute Service] CLI[InsForge CLI] --> Service Service --> Container[Long-lived Container] Container --> DB[(Database)] Container --> Storage[Storage] Container --> Auth[Auth] style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style CLI fill:#1e40af,stroke:#3b82f6,color:#dbeafe style Service fill:#166534,stroke:#22c55e,color:#dcfce7 style Container fill:#c2410c,stroke:#fb923c,color:#fed7aa style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Storage fill:#0e7490,stroke:#06b6d4,color:#cffafe style Auth fill:#0e7490,stroke:#06b6d4,color:#cffafe ``` ## Features ### Container deploys Push any Docker image to InsForge and it runs. Point at a pre-built image on a registry, or upload a build context and let InsForge build it from your `Dockerfile`. No proprietary build pipeline to learn. For a stock service — a Redis cache, an nginx proxy — point a service at a public image and it is pulled and started, with no `Dockerfile` or build step. From the CLI: ```bash theme={null} npx @insforge/cli compute deploy \ --image redis:7-alpine \ --name cache \ --port 6379 \ --protocol tcp \ --memory 512 ``` `--image` selects image mode (no `flyctl` or Docker needed). Use `--protocol tcp` for raw-TCP services like Redis; the default `--protocol http` suits HTTP servers. Re-running `compute deploy` with the same `--name` updates the existing service in place. Because Custom Compute has no persistent volumes yet, treat a Redis container as an ephemeral cache — anything it holds is discarded when the image, environment variables, or port change. Keep durable data in your project's Postgres or Storage. ### Reaching your project Set the credentials your container needs as environment variables on the service — the project URL, an API key, S3 credentials, whatever the workload uses. Nothing is injected for you, so you decide exactly what the container can reach. When you self-host, compute containers join your project's own network by default, so `postgres:5432` and `postgrest:3000` resolve by name from inside the container exactly as they do for edge functions — no public round trip. ### Resources Memory and CPU are configurable per service. Each service runs one instance; run several services if you need several workers. ### Logs Structured logs per container, queryable by service and time range. Tail in the dashboard, CLI, or MCP without `kubectl exec`-ing into anything. ### Secrets and env vars Set environment variables and secrets per service, separately from your edge-function secrets. Rotate without redeploying. **No persistent volumes yet.** Container state survives restarts and host reboots, but changing the image, environment variables, or port recreates the container and discards anything written inside it. Use your project's Postgres or Storage for data you need to keep. ## Self-hosting: enable compute On InsForge Cloud, compute is fully managed and you configure nothing. When you self-host, you choose where containers run. Two providers are available, and until one is configured the compute endpoints return `503 COMPUTE_NOT_CONFIGURED`. Containers run on the same Docker daemon that runs InsForge, as siblings of the InsForge container. Nothing to sign up for, and no per-container bill. Enabling it is a single deliberate act: mount the Docker socket into the InsForge container. In your compose file, uncomment the line that is already there for this: ```yaml theme={null} services: insforge: volumes: - ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:${DOCKER_SOCKET_PATH:-/var/run/docker.sock} ``` That is the whole edit. The socket is mode `660 root:docker` on Linux and `root:root` on Docker Desktop, and the group id differs per host, so the container reads it off the socket at startup and joins that group before dropping to the app user. Nothing to look up and nothing to set. Restart the stack. The driver registers itself when the socket is reachable and logs `Compute provider "docker" ready`. **The Docker socket is root-equivalent on the host.** Anyone who can reach it can start a container that reads the whole filesystem, so mounting it is a decision to make deliberately. InsForge builds every container spec itself and never forwards caller-supplied options, so a leaked InsForge API key cannot ask for a privileged container or a host bind mount — but the socket itself remains as powerful as the account that owns it. Optional settings: | Variable | Default | What it does | | ----------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DOCKER_SOCKET_PATH` | `/var/run/docker.sock` | Point at a rootless Docker (`$XDG_RUNTIME_DIR/docker.sock`) or Podman (`/run/podman/podman.sock`) socket. The compose file mounts it at the same path inside the container, so one value covers both sides. | | `COMPUTE_ISOLATE_NETWORK` | off | Keep compute containers off the project network. Off by default, because sitting next to the database and storage is the point. | | `COMPUTE_BUILD_MAX_CONTEXT` | `64mb` | Ceiling on an uploaded build context. The whole tarball is buffered in memory, so lower it on a small host. | | `COMPUTE_BUILD_UPLOAD_IDLE_TIMEOUT` | `30` | Seconds an upload may send nothing before it is treated as stalled. Resets on every chunk, so a slow but active connection is never cut. | Containers run on [Fly.io](https://fly.io) under your own account. Turn it on with two environment variables in your `.env`: * `FLY_API_TOKEN`: an org-scoped Fly.io API token, created with `fly tokens create org -o `. Paste the whole line the CLI prints — the `FlyV1` prefix is handled for you. InsForge uses it to create and manage your compute containers. * `FLY_ORG`: your Fly organization slug, from `fly orgs list`. This is the org the containers are created in. Both are required. A token with no org has nothing to authenticate against, and an org with no token can't be called. Set them, then restart the container. If both are configured, existing services stay with the provider that created them and new ones go to Fly. Set `COMPUTE_PROVIDER` to `fly`, `docker`, or `off` to be explicit. ### Choosing how a service is reachable Each service picks an ingress mode. Which modes exist depends on the provider, and so does the default: on a single host it is `none`, because most compute — queue workers, processors, inference loops — takes no inbound traffic at all, while Fly gives every app a hostname and so offers only `host`. Omit the field and the active provider's default applies; ask for a mode the provider cannot give and it is coerced to one it can. | Mode | What happens | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `none` | Reachable only on the project's internal network. No host port is published and no URL is advertised. | | `port` | Published on a host port the daemon assigns. Set `COMPUTE_PUBLIC_HOST` to have InsForge advertise a URL; left empty, no URL is returned rather than one that may not resolve. | | `host` | Reachable at a hostname you route yourself. Set `COMPUTE_DOMAIN` to the base domain. | Published ports bind to `127.0.0.1` by default. Set `COMPUTE_BIND_ADDRESS` to change that — Docker's own default publishes on every interface, including IPv6, which would put your container on the public internet on a reachable host. Set the deployment-wide default with `COMPUTE_DEFAULT_INGRESS`. For `host` mode, InsForge advertises the hostname but does not terminate TLS or route traffic — run your own gateway (Caddy, Traefik, nginx) in front, as you already do for the dashboard. ### Building from source Deploying a pre-built image needs nothing special: create the service with an image reference and it is pulled and started. To have InsForge build your `Dockerfile`, reserve the service, then upload the build context as a tarball: ```bash theme={null} # 1. Reserve the name (no image yet) and keep the id it returns ID=$(curl -sX POST "$INSFORGE_URL/api/compute/services/deploy" \ -H "x-api-key: $INSFORGE_API_KEY" -H 'Content-Type: application/json' \ -d '{"name":"worker","port":8080,"memory":512}' | jq -r .id) # 2. Upload the context; the response carries the build log and the deployed tag tar --no-xattrs -cf context.tar -C ./worker . curl -X POST "$INSFORGE_URL/api/compute/services/$ID/build" \ -H "x-api-key: $INSFORGE_API_KEY" -H 'Content-Type: application/x-tar' \ --data-binary @context.tar ``` Add `?dockerfile=docker/Dockerfile` if your `Dockerfile` is not at the context root; the path must stay inside the context. One build runs at a time — a second upload gets `429` rather than being buffered. On macOS, tar with `--no-xattrs` (or `COPYFILE_DISABLE=1`): extended attributes that the Linux daemon cannot apply will make it reject the whole context. ### Platform support | Tier | Platforms | Notes | | ---------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Verified | Docker Compose on Linux, Docker Desktop (macOS) | Both deploy paths tested end to end, including a host reboot and SELinux in enforcing mode on Amazon Linux 2023. | | Expected to work | Dokploy, Coolify, Containarium | The socket mount is the same; not yet exercised for compute. | | Not supported | Managed container platforms that do not expose a Docker socket | There is no daemon to talk to. | ### What differs between providers Ask `GET /api/metadata` for the `compute` slice — it reports the configured providers and what each one can do, so tools can stop offering options that would be ignored. | | Docker | Fly.io | | ------------- | ---------------------------- | ------------------------------ | | Regions | Single host | Selectable | | Scale to zero | Not available | Supported | | Ingress modes | `none`, `port`, `host` | Hostname | | Source builds | Upload a context to InsForge | Built by the CLI with `flyctl` | ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * See [Edge Functions](/core-concepts/functions/overview) if request/response is all you need. # Database backups and restore Source: https://docs.insforge.dev/core-concepts/database/backups Create manual database backups, restore snapshots from the InsForge dashboard, and use automated daily backups on InsForge Cloud with 7-day retention. Back up and restore your database from **Database → Backup & Restore** in the dashboard. InsForge Cloud also backs up your project automatically every day, so you always have a recent copy to roll back to. Backups cover the database only — files uploaded to Storage are not included. InsForge dashboard Backup & Restore page showing manual backups and scheduled backups with a Restore button ## Scheduled backups InsForge Cloud takes a full backup of every active project on a paid plan once a day, retained for 7 days. The **Scheduled Backups** list shows when each backup expires, and any of them can be restored at any time. Manual backups don't affect the daily schedule. ## Manual backups Click **Create a Backup** before a risky change and optionally give it a name. The backup runs in the background; the list shows its status, size, and creation time, and lets you rename or delete it. The Free plan includes 1 manual backup slot; paid plans include 5. Manual backups are kept until you delete them. ## Restore Click **Restore** next to any completed backup — scheduled or manual. Restoring replaces the current database with the backup: the project goes offline during the restore, you lose data created after the backup, and you can't undo the action. Take a manual backup first if you're unsure. ## Self-hosting Self-hosted deployments have the same **Backup & Restore** page for manual backups and restores. Backups are stored alongside your project's storage (local disk, or your S3 bucket when configured). The official Docker image needs no extra setup. Scheduled backups are available too, but run in your own backend rather than on InsForge Cloud. Turn them on from the **Database Settings** dialog (the gear icon in the Database sidebar, or **Configure** on the Backup & Restore page): pick a frequency — presets from every 6 hours to weekly, or any 5-field cron expression, evaluated in UTC and limited to at most one backup per hour — and a retention period. Expired scheduled backups are deleted automatically after each successful run; manual backups are always kept until you delete them. A backup missed while the server was down runs shortly after it starts again. ## More resources * [Database overview](/core-concepts/database/overview) for how the database is exposed to your app. * [Database migrations](/core-concepts/database/migrations) to version schema changes instead of restoring to undo them. * [Database branching](/agent-native/branching) to rehearse risky changes on a copy. # Database migrations Source: https://docs.insforge.dev/core-concepts/database/migrations Track Postgres schema changes as timestamped SQL files in git and apply them forward-only with the InsForge CLI, recording history for every project. Migrations are versioned SQL files in `migrations/` applied with `@insforge/cli`. Each successful run is recorded in `system.custom_migrations`. The workflow is forward-only. ## Concepts A migration is one SQL file prefixed with a 14-digit UTC timestamp: `_.sql`. The CLI applies pending files in order inside a transaction, sets `search_path` to `public`, and records history only on success. PostgREST reloads schema metadata automatically. `BEGIN`/`COMMIT`/`ROLLBACK` inside a file are rejected. ## Usage Link the backend, then create a file. ```bash theme={null} npx @insforge/cli login npx @insforge/cli link npx @insforge/cli db migrations new create-employees-table ``` Write the SQL. ```sql theme={null} create table if not exists public.employees ( id bigint primary key generated always as identity, name text not null, email text, created_at timestamptz default now() ); ``` Apply pending migrations and check history. ```bash theme={null} npx @insforge/cli db migrations up --all npx @insforge/cli db migrations list ``` Target a single file with `up `, or apply everything pending up to and including a target with `up --to `. ## Specific usage cases Adopting migrations on an existing project: run `db migrations fetch` first to materialize remote history into local files. Once applied remotely, never edit a migration in place. Write a forward migration instead. Once you opt in, route all schema changes through files. Ad hoc dashboard edits cause drift between git and `system.custom_migrations`. ## Non-transactional statements Because the CLI runs each migration file inside a single transaction (the same reason `BEGIN`/`COMMIT`/`ROLLBACK` are rejected), a few PostgreSQL commands that **cannot run inside a transaction block** will fail in a migration with an error like `CREATE INDEX CONCURRENTLY cannot run inside a transaction block`. The common ones are: * `CREATE INDEX CONCURRENTLY` — use a plain `CREATE INDEX` in a migration when a brief write lock is acceptable, or run the concurrent build outside a migration (below). * `VACUUM` — routine vacuuming is handled by PostgreSQL autovacuum, so you rarely need to run this yourself. * `REINDEX ... CONCURRENTLY`, `ALTER TYPE ... ADD VALUE` (on older PostgreSQL), and similar commands. Run these outside a migration with the CLI's unrestricted raw-SQL path, which executes the statement **without** wrapping it in a transaction: ```bash theme={null} npx @insforge/cli db query --unrestricted "CREATE INDEX CONCURRENTLY idx_orders_customer ON public.orders (customer_id)" ``` Plain `npx @insforge/cli db query` (without `--unrestricted`) wraps your statement in a transaction, so it hits the same error — use `--unrestricted` for non-transactional commands. Both `db query` paths enforce a 30-second `statement_timeout`, so `CREATE INDEX CONCURRENTLY` or `VACUUM` on a large table may time out. ## More resources * [Database branching](/agent-native/branching) to rehearse a migration on a copy. * [Database overview](/core-concepts/database/overview) for how PostgREST picks up schema changes. * [PostgreSQL DDL docs](https://www.postgresql.org/docs/15/ddl.html) for the SQL you write. # Database Source: https://docs.insforge.dev/core-concepts/database/overview Every InsForge project ships a full Postgres database with typed REST and SDK endpoints, row-level security, pgvector, and realtime change feeds built in. Every InsForge project comes with a full [Postgres](https://www.postgresql.org/) database. Every table is automatically a typed REST and SDK endpoint. Auth tokens scope every read and write through row-level security. The same Postgres handles relational queries, semantic search via pgvector, and realtime change feeds. InsForge dashboard table editor showing a messages table with typed columns **Looking for file storage?** Use [Storage](/core-concepts/storage/overview) for images, PDFs, and other binary content. The database stores rows; storage stores objects. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> API[InsForge API] API --> PostgREST[PostgREST v12.2] PostgREST --> PG[(PostgreSQL 15)] API --> PG PG --> RLS[Row Level Security] PG --> Triggers[Database Triggers] PG --> Functions[Stored Functions] PG --> Schemas[Multiple Schemas] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style API fill:#166534,stroke:#22c55e,color:#dcfce7 style PostgREST fill:#c2410c,stroke:#fb923c,color:#fed7aa style PG fill:#0e7490,stroke:#06b6d4,color:#cffafe style RLS fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Triggers fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Functions fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Schemas fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### Tables as APIs Define a table and you immediately get REST endpoints plus a typed SDK client for it. No code generation step. The auth JWT scopes every query through RLS. ### Migrations Track and apply SQL changes in order. [Migrations](/core-concepts/database/migrations) ship as plain `.sql` files in your repo, applied with `npx @insforge/cli db migrations up --all` or via the MCP tool. ### Branching Spin up an isolated database branch to test risky schema changes against a copy of production data. See [Branching](/agent-native/branching). ### pgvector Native vector search for embeddings, with HNSW and IVFFlat indexes. See [pgvector](/core-concepts/database/pgvector). ### Row-level security Postgres RLS policies enforce access at the row level. Policies read the auth JWT, so the same rule applies to REST queries, SDK calls, realtime subscriptions, and storage requests. ## Concepts Apply SQL changes in order, safely. Isolated databases for preview and risky changes. Vector search for embeddings. ## Build with it Typed queries, inserts, and updates from Node, browser, and edge. Native Swift database client for iOS and macOS. Coroutines-first database client for Android and JVM. Plain HTTP database endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/database) for typed queries. # pgvector Source: https://docs.insforge.dev/core-concepts/database/pgvector Use the pgvector extension shipped with every InsForge project to store embeddings and run semantic search, recommendations, and RAG queries in Postgres. [pgvector](https://github.com/pgvector/pgvector) ships on every InsForge project. Use it for semantic search, recommendations, and [RAG](https://www.pinecone.io/learn/retrieval-augmented-generation/). ## Prompt your agent > Add pgvector to my project. Create a `documents` table with `content` and a 1536-dim `embedding` column, plus an HNSW cosine index. When I insert content, embed it with OpenRouter's `text-embedding-3-small` from a server-side route. Expose a `match_documents(query, count, threshold)` RPC that returns top similarity matches. ## Concepts A vector is a list of numbers representing an item. Two vectors are similar if they sit close in vector space. Store the vector next to its row, embed the user query the same way, and pgvector ranks by distance. ## Usage Enable the extension and create a vector column. Match the dimension to your model (`text-embedding-3-small` is 1536). ```sql theme={null} create extension if not exists vector; create table documents ( id bigserial primary key, content text, embedding vector(1536) ); ``` Generate an embedding server-side and insert it. ```typescript theme={null} import OpenAI from 'openai'; import { createClient } from '@insforge/sdk'; const openai = new OpenAI({ baseURL: 'https://openrouter.ai/api/v1', apiKey: process.env.OPENROUTER_API_KEY, }); const insforge = createClient({ projectId: process.env.INSFORGE_PROJECT_ID }); const { data } = await openai.embeddings.create({ model: 'openai/text-embedding-3-small', input: 'hello world', }); await insforge.database.from('documents').insert({ content: 'hello world', embedding: data[0].embedding, }); ``` Query by cosine distance (`<=>`). L2 (`<->`) and inner product (`<#>`) are also available. ```sql theme={null} select id, content from documents order by embedding <=> $1 limit 5; ``` ## Specific usage cases Wrap search in a Postgres function and call it via `rpc()` to keep the math server-side: ```sql theme={null} create or replace function match_documents( query_embedding vector(1536), match_count int default 5, match_threshold float default 0 ) returns table (id bigint, content text, similarity float) language sql stable as $$ select id, content, 1 - (embedding <=> query_embedding) as similarity from documents where 1 - (embedding <=> query_embedding) > match_threshold order by embedding <=> query_embedding limit match_count; $$; ``` Past \~10k rows, add an HNSW index: ```sql theme={null} create index on documents using hnsw (embedding vector_cosine_ops); ``` ## More resources * [pgvector on GitHub](https://github.com/pgvector/pgvector) for operators and indexes. * [OpenRouter embeddings](https://openrouter.ai/docs/features/multimodal/embeddings) for the model catalog. * [Model Gateway overview](/core-concepts/ai/overview) for the InsForge side of OpenRouter. # Edge Functions Source: https://docs.insforge.dev/core-concepts/functions/overview Deploy Deno-powered serverless TypeScript edge functions with HTTP invocation, cron schedules, database triggers, secrets, and environment variables. Use InsForge edge functions to run TypeScript on [Deno](https://deno.com), deployed close to your users for low latency. Functions can be invoked on-demand from any client, chained from database triggers, or scheduled to run on a cron expression. The runtime ships standard fetch, streaming responses, and ESM imports out of the box. **Need a process that stays up?** Use [Compute](/core-concepts/compute/overview) for queue workers, AI inference loops, and anything stateful. Edge Functions are for request/response and short-lived jobs. ```mermaid theme={null} graph TB HTTP[HTTP Request] --> Fn[Edge Function on Deno] Schedule[Cron Schedule] --> Fn Trigger[Database Trigger] --> Fn Fn --> SDK[InsForge SDK] SDK --> DB[(Database)] SDK --> Storage[Storage] SDK --> Gateway[Model Gateway] style HTTP fill:#1e293b,stroke:#475569,color:#e2e8f0 style Schedule fill:#1e293b,stroke:#475569,color:#e2e8f0 style Trigger fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Fn fill:#c2410c,stroke:#fb923c,color:#fed7aa style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Storage fill:#166534,stroke:#22c55e,color:#dcfce7 style Gateway fill:#166534,stroke:#22c55e,color:#dcfce7 ``` ## Features ### HTTP triggers Every function is reachable at `https://.insforge.dev/functions/`. Standard fetch in, standard `Response` out. Streaming, JSON, redirects, and websockets all work. ### Schedules Attach a cron expression to a function and InsForge invokes it on time, with retry on failure. See [Schedules](/core-concepts/functions/schedules) for the cron syntax and execution model. ### Database triggers Wire a function to fire on `INSERT`, `UPDATE`, or `DELETE` against a table. The function receives the row payload and runs with a service-role JWT so it can perform privileged follow-up writes. ### Secrets and environment variables Set env vars and secrets per function. The dashboard, CLI, and MCP all read and write the same store; secrets never round-trip through your repo. The difference is how each value is stored and protected. **Secrets** are encrypted at rest: `secrets list` shows only metadata with the values hidden, and `secrets get` returns the decrypted value on demand. A secret can also carry an expiry date (`--expires `) and be marked protected from deletion (`--reserved`). **Environment variables** are plain configuration values with no encryption or expiry. Put credentials like API keys and passwords in secrets, and non-sensitive config in environment variables. Either one is available to your function code the same way. ```bash theme={null} # a protected secret with an expiry npx @insforge/cli secrets add STRIPE_SECRET_KEY sk_live_xxx --reserved --expires 2027-01-01T00:00:00Z # read the decrypted value back npx @insforge/cli secrets get STRIPE_SECRET_KEY ``` ### Logs Structured logs are captured per invocation, queryable by status, duration, and function name. The InsForge MCP `get-function-logs` tool lets your agent diagnose failures without leaving the editor. ### Deno standard library Use the [Deno standard library](https://jsr.io/@std) and any ESM module from `jsr.io`, `esm.sh`, or `npm:` specifiers. You don't run a bundler, and there's no `node_modules` directory to ship. ## Concepts Run a function on a cron expression instead of in response to a request. ## Build with it Invoke and stream functions from Node, browser, and edge. Invoke functions from iOS and macOS apps. Invoke functions from Android and JVM apps. Plain HTTP function endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/functions) for invocation patterns. # Schedules: cron-triggered functions Source: https://docs.insforge.dev/core-concepts/functions/schedules Trigger InsForge edge functions on a cron schedule with pg_cron: secret-aware headers, encrypted keys, job logs, retry guidance, and keep-alive tips. Schedules invoke functions on a recurring cron expression. [pg\_cron](https://github.com/citusdata/pg_cron) fires an HTTP request to the function URL at each tick and logs the result. ## Concepts A schedule is a cron expression, a target URL, and headers. On creation, `${{secrets.KEY}}` placeholders in headers are resolved and encrypted with `pgcrypto`. At each tick, `execute_job()` decrypts headers, calls the function, and writes status and duration to `schedules.job_logs`. ## Usage Standard 5-field cron (no seconds). Reference secrets in headers instead of hardcoding keys. ```text theme={null} */5 * * * * every 5 minutes 0 * * * * every hour 0 0 * * * daily at midnight 0 9 * * 1 every Monday at 9am 0 0 1 * * first of every month ``` Create via dashboard or SQL: ```sql theme={null} select schedules.create_job( name => 'daily-cleanup', schedule => '0 0 * * *', url => 'https://myapp.functions.insforge.app/cleanup', headers => jsonb_build_object('Authorization', 'Bearer ${{secrets.CRON_TOKEN}}') ); ``` ## Limits Minimum interval is 1 minute (pg\_cron). Failed runs are logged but not retried, so the function must be idempotent. Deleting a referenced secret breaks every job using it until you update or disable the schedule. ## Long-interval callers and keep-alive The backend closes idle HTTP connections after 65 seconds by default (configurable with `KEEP_ALIVE_TIMEOUT_MS`). A job that fires less often than that — every 5 minutes, for example — always finds a reused keep-alive socket already closed on the server side. This can stall the first request in a warm function for \~30 seconds. When a scheduled function calls other APIs, you have two options. Set a short client timeout (well under 30 seconds) and retry once on a fresh connection. Or disable connection reuse by sending `Connection: close` so every tick opens a fresh socket. ## More resources * [pg\_cron docs](https://github.com/citusdata/pg_cron) for cron syntax. * [Functions overview](/core-concepts/functions/overview) for the runtime. * [crontab.guru](https://crontab.guru) to check an expression. # Custom SMTP Source: https://docs.insforge.dev/core-concepts/messaging/custom-smtp Configure your own SMTP server to deliver auth and transactional emails from InsForge, including verification, magic links, and password resets. When enabled, every email (auth flows and `emails.send()` calls) routes through your SMTP server. Toggle off to revert; credentials are preserved. A self-hosted instance cannot turn SMTP off while required email verification depends on it. **Self-hosted instances need this to send any email at all.** The zero-setup managed sender routes through InsForge Cloud and is only available to cloud-linked projects. A self-hosted instance with no SMTP configured cannot deliver auth emails, so email verification, magic links, and password resets will not be delivered until you complete the setup below. ## Concepts Provider is resolved on every send, so saves take effect on the next request. InsForge runs `transporter.verify()` before saving, so a persisted config always works. Passwords are encrypted at rest with AES-256-GCM and never returned by the API. ## Usage Configure SMTP under **Authentication → Email**. Flip the switch on the **SMTP Provider** card. Host, port (`25`, `465`, `587`, `2525`), username, password, sender email, sender name. Private IPs and self-signed certs are rejected. InsForge runs an SMTP handshake before persisting. Bad credentials fail fast. The **Email Templates** card unlocks the four auth templates. The `From:` header is always your configured sender. SDK callers cannot spoof it. ## Email templates Templates render locally from `email.templates`. Variables use `{{ variable }}` and are HTML-escaped. | Template | When it sends | | ------------------------- | ------------------------------------------- | | `email-verification-code` | New-user verification with a 6-digit code | | `email-verification-link` | New-user verification with a clickable link | | `reset-password-code` | Password reset with a 6-digit code | | `reset-password-link` | Password reset with a clickable link | Variables: `{{ token }}` (code templates), `{{ link }}` (link templates, must start with `http://` or `https://`), `{{ name }}` and `{{ email }}` (all templates). ## Considerations * **Rate limiting.** **Min interval (seconds)** caps per-recipient frequency. Sends within the cooldown return HTTP `429`. Defaults to `60`; `0` disables. * **SSRF protection.** Private, loopback, link-local, and carrier-NAT ranges are rejected. * **Audit log.** Config saves log `UPDATE_SMTP_CONFIG`; template edits log `UPDATE_EMAIL_TEMPLATE`. ## More resources * [Messaging overview](/core-concepts/messaging/overview) for the routing model. * [nodemailer SMTP transport](https://nodemailer.com/smtp/) for connection options. * [Authentication overview](/core-concepts/authentication/overview) for the flows that emit these emails. # Messaging overview Source: https://docs.insforge.dev/core-concepts/messaging/overview Send transactional emails from your InsForge project with the managed sender or custom SMTP. SMS and push are on the roadmap for the same API. InsForge Messaging sends transactional notifications from your project: receipts, digests, password-reset codes, notification roll-ups, anything you would otherwise wire SendGrid, Postmark, or Twilio in for. Email is the first channel; SMS and push are on the roadmap and will share the same API surface. **Just sending auth emails?** Magic links, verification codes, and password resets are wired into [Authentication](/core-concepts/authentication/overview) out of the box. You only need this product for transactional messages beyond auth. **Self-hosting?** The managed InsForge Cloud sender is only available to cloud-linked projects. On a self-hosted instance you must configure [Custom SMTP](/core-concepts/messaging/custom-smtp) before any email can send, auth emails included. InsForge rejects enabling required email verification until a provider is available, and it prevents disabling the only SMTP provider while verification remains required. If a verification email fails at runtime, registration returns an error with instructions to retry with `POST /api/auth/email/send-verification`. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> API[Email API] API --> Service[EmailService] Service --> Decision{SMTP enabled?} Decision -->|No| Cloud[InsForge Cloud] Cloud --> SES[AWS SES] Decision -->|Yes| SMTP[Your SMTP server] SES --> Inbox[Recipient Inbox] SMTP --> Inbox style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style API fill:#166534,stroke:#22c55e,color:#dcfce7 style Service fill:#166534,stroke:#22c55e,color:#dcfce7 style Decision fill:#7c3aed,stroke:#a78bfa,color:#ede9fe style Cloud fill:#7c3aed,stroke:#a78bfa,color:#ede9fe style SES fill:#ea580c,stroke:#f97316,color:#fed7aa style SMTP fill:#0e7490,stroke:#06b6d4,color:#cffafe style Inbox fill:#0e7490,stroke:#06b6d4,color:#cffafe ``` ## Channels Managed SMTP or bring your own provider. Templates, delivery tracking, and webhook events. Coming soon. Same API, Twilio or Sinch on the back. Coming soon. APNs and FCM via a single endpoint. ## Features ### One API, every channel Same `emails.send()` shape for email today, with SMS and push to follow when they land. Switching channels is a field change, not a rewrite. ### Managed delivery or bring your own Send through InsForge Cloud (AWS SES for email today) for zero setup, or plug in your own provider when you need to control deliverability and sender reputation. See [Custom SMTP](/core-concepts/messaging/custom-smtp). ### Templates Pick a template by name, pass the variables, and InsForge renders and sends. Templates are editable per project; the four auth templates (`email-verification-*`, `reset-password-*`) ship with sensible defaults. ### Delivery tracking Send events (`accepted`, `delivered`, `bounced`, `complained`) are recorded per message. Query the audit table in Postgres, subscribe over webhooks, or watch the dashboard. ### Rate limits Per-project and per-plan limits keep stray loops from melting deliverability. Configurable from the dashboard, enforced at the gateway. ## Concepts Bring your own SMTP provider (SendGrid, Postmark, AWS SES, etc.). ## Build with it Send mail from Node, browser, and edge runtimes. Plain HTTP messaging endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/email) for send patterns. # Payments Source: https://docs.insforge.dev/core-concepts/payments/overview Collect money in your app with your own Stripe or Razorpay account. Pick the provider-specific InsForge Payments integration for checkout and subscriptions. InsForge Payments lets your app collect money with your own Stripe or Razorpay account. The two providers use different payment models, so the docs are split by provider instead of describing one generic payment flow. InsForge Payments dashboard Stripe or Razorpay remains the source of truth for charges, invoices, refunds, disputes, taxes, and account-level financial operations. InsForge is not a payment processor or merchant of record, and it does not replace the provider dashboard. ## Choose a provider Use Stripe Checkout, Products, Prices, Subscriptions, and Billing Portal. Use Razorpay Orders, Items, Plans, Subscriptions, and Razorpay Checkout. ## Architecture Provider-native tables keep provider concepts intact: | Provider | Runtime tables | Catalog tables | Subscription tables | | -------- | ------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- | | Stripe | `payments.stripe_checkout_sessions`, `payments.stripe_customer_portal_sessions` | `payments.stripe_products`, `payments.stripe_prices` | `payments.stripe_subscriptions`, `payments.stripe_subscription_items` | | Razorpay | `payments.razorpay_orders` | `payments.razorpay_items`, `payments.razorpay_plans` | `payments.razorpay_subscriptions` | Shared tables are used only where the durable shape is useful across providers: | Table | Purpose | | ------------------------------- | ---------------------------------------------------------------------------------------------- | | `payments.provider_connections` | Provider key, account, sync, and webhook setup status by `provider` and `environment`. | | `payments.customer_mappings` | App billing subject to provider customer ID mapping. | | `payments.customers` | Admin/customer mirror for dashboard visibility. | | `payments.webhook_events` | Verified provider webhook event ledger. Use this for durable fulfillment triggers. | | `payments.transactions` | InsForge dashboard/reporting projection for successful, failed, and refunded payment activity. | `payments.transactions` is not the fulfillment contract. It is a projection built from provider events and sync. For business logic, create app-owned tables such as `public.orders`, `public.credit_ledger`, or `public.team_entitlements`, then populate them from verified rows in `payments.webhook_events`. ## Fulfillment Do not fulfill from a Stripe success URL or a Razorpay Checkout callback alone. Those are user experience signals. Durable fulfillment should run from verified provider webhook events. ```sql theme={null} CREATE TRIGGER fulfill_from_payment_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_payment_event(); ``` If your app accepts multiple providers, keep the trigger idempotent and branch on `NEW.provider` and `NEW.event_type`. Protect your app-owned fulfillment tables with your own RLS policies. Webhook events are processed independently and providers give no ordering guarantee across events. Rows derived from an event are committed before that event is marked `processed`, but rows owned by other events — such as `payments.customer_mappings`, which checkout completion creates — may not exist yet when your trigger fires. Resolve billing subjects from the event payload first and treat lookups into other tables as fallbacks. See the provider guides for subscription fulfillment examples. Older Stripe-only `payments.payment_history` rows are migrated into `payments.transactions` for dashboard and reporting. Triggers on `payment_history` are not rewritten automatically. Move fulfillment logic to `payments.webhook_events`. ## Build with it Pick the Stripe or Razorpay provider module for app code. Review provider-specific Payments API routes and webhook routes. ## Next steps * Read [Stripe Payments](/core-concepts/payments/stripe) if you are using Stripe Checkout or Billing Portal. * Read [Razorpay Payments](/core-concepts/payments/razorpay) if you are using Razorpay Orders or Subscriptions. * Configure provider keys in Dashboard -> Payments -> Settings. * Add app-specific RLS or server-side membership checks for billing subjects. * Add trigger-backed fulfillment from `payments.webhook_events`. # Razorpay Payments Source: https://docs.insforge.dev/core-concepts/payments/razorpay Integrate Razorpay Orders, Items, Plans, and Subscriptions with InsForge: server-side signature verification, mirrored tables, and webhook fulfillment. Use the Razorpay integration when you want Razorpay Orders, Razorpay Checkout, Items, Plans, and Subscriptions. Razorpay is not a hosted redirect flow like Stripe Checkout. Your backend creates the provider object, your frontend opens the Razorpay Checkout script, and your backend verifies the returned signature. ## Razorpay model | Razorpay concept | Meaning in InsForge | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Item | Amount-bearing sellable unit. Mirrored in `payments.razorpay_items`. | | Plan | Recurring subscription definition around an item. Mirrored in `payments.razorpay_plans`. | | Order | One-time payment object. Created through `POST /api/payments/razorpay/{environment}/orders` and mirrored in `payments.razorpay_orders`. | | Payment | Provider payment result. Projected into `payments.transactions` from webhooks and sync. | | Subscription | Razorpay subscription tied to a Plan. Mirrored in `payments.razorpay_subscriptions`. | | Webhook event | Verified provider event in `payments.webhook_events` with `provider = 'razorpay'`. | Razorpay does not have a Stripe Billing Portal equivalent. InsForge exposes backend routes to cancel, pause, and resume subscriptions after checking the caller against `payments.razorpay_subscriptions` RLS `UPDATE` policies. For one-time products, Razorpay Orders can be created with only an amount, currency, and receipt. Still, prefer creating Razorpay Items for sellable products so the catalog is visible in InsForge and Razorpay after sync. Treat Orders as payment attempts, not as your product catalog. ## Setup Configure `test` and `live` Razorpay Key ID and Key Secret in Dashboard -> Payments -> Settings or through the admin API. InsForge generates a webhook signing secret if one does not already exist. Razorpay webhooks must be created manually in the Razorpay Dashboard. Razorpay normal merchant API keys do not support automatic webhook registration. 1. Open Dashboard -> Payments -> Settings -> Webhooks. 2. Copy the Razorpay Webhook URL and Webhook Secret. 3. In the Razorpay Dashboard, create a webhook with the copied URL and secret. 4. Select the events InsForge handles. 5. Save the webhook and complete a test payment to confirm delivery. Razorpay can only deliver webhooks to a public HTTPS URL. Localhost URLs need a public tunnel or a deployed backend. Handled events: * `payment.authorized` * `payment.captured` * `payment.failed` * `order.paid` * `invoice.paid` * `invoice.expired` * `refund.created` * `refund.processed` * `refund.failed` * `subscription.created` * `subscription.activated` * `subscription.charged` * `subscription.updated` * `subscription.cancelled` * `subscription.paused` * `subscription.resumed` * `subscription.halted` * `subscription.completed` * `subscription.expired` ## One-time orders Create an app-owned pending order first. Then create a Razorpay Order with the current InsForge user token. ```typescript theme={null} const { data, error } = await insforge.payments.razorpay.createOrder('test', { amount: 50000, currency: 'INR', receipt: 'order_123', subject: { type: 'team', id: 'team_123' }, customerEmail: 'buyer@example.com', notes: { order_id: 'order_123' } }); if (error) throw error; ``` The SDK wraps `POST /api/payments/razorpay/{environment}/orders`. The response includes `checkoutOptions` with Razorpay Checkout-native fields such as `key` and `order_id`. Load `https://checkout.razorpay.com/v1/checkout.js` in the frontend and pass those options to `new Razorpay(options).open()`. If your fulfillment trigger reads `notes.order_id`, pass `notes: { order_id: ... }` when creating the Order or Subscription. After Razorpay Checkout returns `razorpay_order_id`, `razorpay_payment_id`, and `razorpay_signature`, verify the signature on the backend: ```typescript theme={null} await insforge.payments.razorpay.verifyOrder('test', { orderId: response.razorpay_order_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); ``` Signature verification proves the immediate Checkout callback came from Razorpay. Durable order fulfillment should still run from verified Razorpay webhook events. ## Subscriptions Create or sync a Razorpay Plan before creating subscriptions. A Plan is not the same thing as a Stripe Price. It is a recurring definition around a Razorpay Item. ```typescript theme={null} const { data, error } = await insforge.payments.razorpay.createSubscription('test', { planId: 'plan_123', totalCount: 12, subject: { type: 'team', id: 'team_123' }, customerEmail: 'buyer@example.com' }); if (error) throw error; ``` The SDK wraps `POST /api/payments/razorpay/{environment}/subscriptions`. The response includes `checkoutOptions.subscription_id`. Open Razorpay Checkout with that subscription ID. After Checkout returns `razorpay_subscription_id`, `razorpay_payment_id`, and `razorpay_signature`, verify the authorization payment: ```typescript theme={null} await insforge.payments.razorpay.verifySubscription('test', { subscriptionId: response.razorpay_subscription_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature }); ``` Manage subscriptions through backend routes: ```typescript theme={null} await insforge.payments.razorpay.cancelSubscription('test', 'sub_123', { cancelAtCycleEnd: false }); await insforge.payments.razorpay.pauseSubscription('test', 'sub_123'); await insforge.payments.razorpay.resumeSubscription('test', 'sub_123'); ``` Subscription creation evaluates `INSERT` policies on `payments.razorpay_subscriptions`. Cancel, pause, and resume evaluate `UPDATE` policies on the same table. PostgreSQL also applies `SELECT` policies to rows returned by `INSERT/UPDATE ... RETURNING`, so make the same subject visible to the caller when a policy probe needs to return the row. Grant users only the table access needed for policy checks; provider mutations still run through the backend. ## Webhooks and fulfillment Razorpay's Checkout callback and signature verification are not a replacement for webhooks. Use `payments.webhook_events` for fulfillment triggers. Do not attach fulfillment triggers to provider mirror tables such as `payments.razorpay_subscriptions`; sync and webhook projection can update those rows independently of provider event delivery. ```sql theme={null} CREATE OR REPLACE FUNCTION public.fulfill_razorpay_order() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider = 'razorpay' AND NEW.event_type IN ('payment.captured', 'order.paid', 'invoice.paid') AND NEW.processing_status = 'processed' AND COALESCE( NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id', NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id' ) IS NOT NULL THEN UPDATE public.orders SET status = 'paid', paid_at = COALESCE(NEW.processed_at, NOW()) WHERE id::text = COALESCE( NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id', NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id' ) AND status = 'pending'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER fulfill_razorpay_order_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_razorpay_order(); ``` ## Sync and dashboard state Razorpay sync mirrors Items, Plans, Customers, Subscriptions, Invoices, and Payments. Invoices and payments feed the `payments.transactions` dashboard/reporting projection. Transactions include provider reference IDs such as payment, invoice, order, subscription, and refund IDs so you can inspect the source record in the Razorpay Dashboard. Keep user-facing order, credit, and entitlement state in your own app tables. Treat `payments.transactions` as dashboard/reporting state, not as the primary business workflow. ## References * [Razorpay Standard Checkout integration](https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/integration-steps/) * [Razorpay Subscriptions integration](https://razorpay.com/docs/payments/subscriptions/integration-guide/) * [Razorpay Webhooks setup](https://razorpay.com/docs/payments/dashboard/account-settings/webhooks/) * [TypeScript Razorpay payments guide](/sdks/typescript/payments-razorpay) # Stripe Payments Source: https://docs.insforge.dev/core-concepts/payments/stripe Integrate Stripe Checkout, Billing Portal, Products, Prices, Subscriptions, and verified webhook fulfillment with InsForge Payments and mirrored tables. Use the Stripe integration when you want Stripe-hosted Checkout, Stripe Products and Prices, Stripe Subscriptions, and the hosted Billing Portal. InsForge stores Stripe secret keys server-side, creates Checkout and Billing Portal sessions from your app, automatically manages the Stripe webhook endpoint when your backend is reachable, mirrors Stripe state into the `payments` schema, and records verified webhook events. ## Stripe model | Stripe concept | InsForge table or API | | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Product | `payments.stripe_products` | | Price | `payments.stripe_prices` | | Checkout Session | `POST /api/payments/stripe/{environment}/checkout-sessions` and `payments.stripe_checkout_sessions` | | Billing Portal Session | `POST /api/payments/stripe/{environment}/customer-portal-sessions` and `payments.stripe_customer_portal_sessions` | | Subscription | `payments.stripe_subscriptions` and `payments.stripe_subscription_items` | | Customer mapping | `payments.customer_mappings` with `provider = 'stripe'` | | Webhook event | `payments.webhook_events` with `provider = 'stripe'` | | Dashboard transaction row | `payments.transactions` with `provider = 'stripe'` | ## Setup Configure `test` and `live` Stripe secret keys in Dashboard -> Payments -> Settings, the CLI, or the admin API. ```bash theme={null} npx @insforge/cli payments stripe status npx @insforge/cli payments stripe config set --environment test sk_test_xxx npx @insforge/cli payments stripe sync --environment test npx @insforge/cli payments stripe webhooks configure --environment test ``` After a key is connected, InsForge validates the account, stores the key in the secret store, tries to create the managed Stripe webhook endpoint, and runs sync for Products, Prices, Customers, and Subscriptions. ## Checkout Create Checkout Sessions from frontend code with the current InsForge user token. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'payment', lineItems: [{ priceId: 'price_123', quantity: 1 }], successUrl: `${window.location.origin}/checkout/success`, cancelUrl: `${window.location.origin}/pricing`, customerEmail: user?.email ?? null, metadata: { order_id: orderId }, idempotencyKey: `order:${orderId}` }); if (error) throw error; if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` For subscription Checkout, pass a billing subject. The subject is your app-owned billing owner, such as a user, team, workspace, organization, tenant, or group. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', { mode: 'subscription', subject: { type: 'team', id: teamId }, lineItems: [{ priceId: 'price_monthly_123', quantity: 1 }], successUrl: `${window.location.origin}/billing/success`, cancelUrl: `${window.location.origin}/billing`, customerEmail: user.email, idempotencyKey: `team:${teamId}:pro-monthly` }); if (error) throw error; if (data?.checkoutSession.url) { window.location.assign(data.checkoutSession.url); } ``` Checkout inserts a row in `payments.stripe_checkout_sessions` using the caller's InsForge token. Add RLS policies so users can only create sessions for subjects they are allowed to bill. PostgreSQL applies `SELECT` policies to rows returned by `INSERT ... RETURNING` and idempotent lookups, so retries also need a matching `SELECT` policy for the same subject and idempotency key. ## Billing Portal Use the hosted Billing Portal for an existing Stripe customer mapping. ```typescript theme={null} const { data, error } = await insforge.payments.stripe.createCustomerPortalSession('test', { subject: { type: 'team', id: teamId }, returnUrl: `${window.location.origin}/billing` }); if (error) { if ('statusCode' in error && error.statusCode === 404) { return; } throw error; } if (data?.customerPortalSession.url) { window.location.assign(data.customerPortalSession.url); } ``` Portal creation requires an authenticated user and an existing `payments.customer_mappings` row for the subject. Protect portal creation with RLS or a server-side membership check so users cannot open billing settings for a team or organization they do not manage. ## Webhooks and fulfillment Stripe webhooks are managed automatically when the backend has a public URL. InsForge listens for the events needed to keep checkout attempts, customers, subscriptions, refunds, and transaction projections current. Stripe also recommends fulfilling Checkout orders from webhooks instead of the success URL. In InsForge, attach fulfillment triggers to `payments.webhook_events`. ```sql theme={null} CREATE OR REPLACE FUNCTION public.fulfill_stripe_order() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider = 'stripe' AND NEW.event_type = 'checkout.session.completed' AND NEW.processing_status = 'processed' AND (NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'order_id') IS NOT NULL THEN UPDATE public.orders SET status = 'paid', paid_at = COALESCE(NEW.processed_at, NOW()) WHERE id::text = NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'order_id' AND status = 'pending'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER fulfill_stripe_order_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_stripe_order(); ``` ### Event ordering Webhook events are verified and processed independently. InsForge commits every row derived from an event before marking that event `processed`, but Stripe gives no ordering guarantee across events: `invoice.paid` can be processed before `checkout.session.completed`, so rows created by another event (such as `payments.customer_mappings`) may not exist yet when your trigger fires. For subscription events, resolve the billing subject from the event payload first — InsForge stamps `insforge_subject_type` and `insforge_subject_id` into subscription metadata at checkout, and Stripe snapshots it onto subscription-generated invoices as `parent.subscription_details.metadata`. Check `invoice.metadata` next, then fall back to `payments.customer_mappings` (the same order InsForge uses internally): ```sql theme={null} CREATE OR REPLACE FUNCTION public.grant_subscription_access() RETURNS TRIGGER AS $$ DECLARE v_subject_type TEXT; v_subject_id TEXT; BEGIN IF NEW.provider = 'stripe' AND NEW.event_type = 'invoice.paid' AND NEW.processing_status = 'processed' THEN v_subject_type := COALESCE( NEW.payload -> 'data' -> 'object' -> 'parent' -> 'subscription_details' -> 'metadata' ->> 'insforge_subject_type', NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'insforge_subject_type' ); v_subject_id := COALESCE( NEW.payload -> 'data' -> 'object' -> 'parent' -> 'subscription_details' -> 'metadata' ->> 'insforge_subject_id', NEW.payload -> 'data' -> 'object' -> 'metadata' ->> 'insforge_subject_id' ); IF v_subject_id IS NULL THEN SELECT m.subject_type, m.subject_id INTO v_subject_type, v_subject_id FROM payments.customer_mappings m WHERE m.provider = NEW.provider AND m.environment = NEW.environment AND m.provider_customer_id = NEW.payload -> 'data' -> 'object' ->> 'customer'; END IF; IF v_subject_id IS NULL THEN RAISE WARNING 'Stripe event % has no resolvable billing subject', NEW.provider_event_id; RETURN NEW; END IF; -- Branch on the subject type sent at checkout; team_id is a UUID here, -- so the type check also guards the cast. IF v_subject_type = 'team' THEN INSERT INTO public.team_entitlements (team_id, plan, active, updated_at) VALUES (v_subject_id::uuid, 'pro', true, NOW()) ON CONFLICT (team_id) DO UPDATE SET plan = EXCLUDED.plan, active = true, updated_at = NOW(); END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER grant_subscription_access_from_stripe_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.grant_subscription_access(); ``` Never let fulfillment skip silently — log or dead-letter events you cannot resolve so they can be replayed. ## Sync and dashboard state Stripe sync mirrors Products, Prices, Customers, and Subscriptions. Webhooks maintain session, subscription, customer, refund, and transaction state as Stripe emits events. `payments.transactions` is a reporting projection for the dashboard. It gives you provider reference IDs such as payment intent, charge, invoice, checkout session, and refund IDs so you can look up details in the Stripe Dashboard. Keep user-facing order, credit, or entitlement state in your own tables. ## References * [Stripe Checkout fulfillment](https://docs.stripe.com/checkout/fulfillment) * [Stripe Billing Portal Sessions API](https://docs.stripe.com/api/customer_portal/sessions/create) * [TypeScript Stripe payments guide](/sdks/typescript/payments-stripe) # Realtime Source: https://docs.insforge.dev/core-concepts/realtime/overview Push database changes, broadcasts, and presence updates to clients over WebSocket channels, with optional webhook fan-out and Row Level Security checks. Use InsForge Realtime when your app needs to update without a page refresh. Clients subscribe to channels such as `order:123` or `chat:room-1`, then receive database changes, broadcasts, and presence updates over WebSockets. Channels can also fan out the same messages to webhook URLs when another service should receive the event. InsForge Realtime dashboard **Need server-side code to run after a database change?** Put that business logic in an [Edge Function](/core-concepts/functions/overview) and invoke it from a database trigger. Use Realtime when the change should be delivered to connected clients or configured webhook endpoints. ```mermaid theme={null} graph TB App[Client application] --> SDK[InsForge SDK] SDK --> Channel[Realtime channel] Database[(Postgres)] --> Trigger[Database trigger] Trigger --> Channel Channel --> WebSocket[WebSocket subscribers] Channel --> Presence[Presence state] Channel --> Webhook[Webhook URLs] Channel --> History[(Message history)] Auth[Auth token and RLS] --> Channel style App fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style Channel fill:#166534,stroke:#22c55e,color:#dcfce7 style Database fill:#0e7490,stroke:#06b6d4,color:#cffafe style Trigger fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style WebSocket fill:#c2410c,stroke:#fb923c,color:#fed7aa style Presence fill:#c2410c,stroke:#fb923c,color:#fed7aa style Webhook fill:#c2410c,stroke:#fb923c,color:#fed7aa style History fill:#0e7490,stroke:#06b6d4,color:#cffafe style Auth fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### Channels Channels are named topics that clients can join. Use exact names for shared rooms, or patterns like `order:%` when every record needs its own live stream. ### Database changes Use database changes when a table write should become a live app event. Create a trigger on the table you want to watch. In its trigger function, call the predefined `realtime.publish(channel, event, payload)` function to decide which channel receives the message, which event name clients handle, and what payload they receive. For a channel pattern such as `order:%`, a trigger can publish one event per order: ```sql theme={null} CREATE OR REPLACE FUNCTION public.notify_order_status() RETURNS TRIGGER AS $$ BEGIN PERFORM realtime.publish( 'order:' || NEW.id::text, 'status_changed', jsonb_build_object( 'id', NEW.id, 'status', NEW.status, 'updatedAt', NEW.updated_at ) ); RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER order_status_realtime AFTER UPDATE OF status ON public.orders FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status) EXECUTE FUNCTION public.notify_order_status(); ``` Then subscribe from the app with the SDK: ```typescript theme={null} const channel = `order:${orderId}`; await insforge.realtime.connect(); const subscription = await insforge.realtime.subscribe(channel); if (!subscription.ok) { throw new Error(subscription.error.message); } insforge.realtime.on('status_changed', (message) => { renderOrderStatus(message.status); }); ``` ### Client broadcasts Clients can publish messages to channels they have already joined. Use this for chat, typing indicators, cursors, collaborative editing signals, and other user-to-user updates that do not need to start from a database write. ```typescript theme={null} await insforge.realtime.publish(`chat:${roomId}`, 'typing', { userId, isTyping: true }); ``` ### Webhooks Attach webhook URLs to a channel when another service should receive each message. InsForge posts the event payload to every configured URL, includes headers for the event name, channel, and message ID, retries transient network failures, and records webhook delivery counts in message history. ### Presence Presence tracks who is online in a channel. Clients receive the current member snapshot when they subscribe, then `presence:join` and `presence:leave` events as members come and go. Store durable room membership, roles, and permissions in your own tables; presence is only online state. ```typescript theme={null} const response = await insforge.realtime.subscribe(`chat:${roomId}`); if (response.ok) { renderOnlineMembers(response.presence.members); } insforge.realtime.on('presence:join', (message) => { addOnlineMember(message.member); }); insforge.realtime.on('presence:leave', (message) => { removeOnlineMember(message.member.presenceId); }); ``` ### Row-level security Realtime can be open while prototyping, then locked down with Postgres RLS. Use `SELECT` policies on `realtime.channels` to control who can subscribe, and `INSERT` policies on `realtime.messages` to control who can publish from a client. This policy lets authenticated users subscribe to `order:` channels only when the order belongs to them: ```sql theme={null} ALTER TABLE realtime.channels ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_subscribe_own_orders" ON realtime.channels FOR SELECT TO authenticated USING ( pattern = 'order:%' AND EXISTS ( SELECT 1 FROM public.orders WHERE id = NULLIF(split_part(realtime.channel_name(), ':', 2), '')::uuid AND user_id = auth.uid() ) ); ``` Use `realtime.channel_name()` in subscribe policies because clients subscribe to resolved channels such as `order:123`, while `realtime.channels` stores patterns such as `order:%`. ### Message history Every delivered event is recorded with WebSocket and webhook delivery counts. The dashboard can inspect recent messages, delivery stats, and retention settings when you need to debug live behavior. ## Build with it Subscribe to channels, publish events, and track presence from Node, browser, and edge. Native Swift realtime client for iOS and macOS. Coroutines-first realtime client for Android and JVM. Use the raw Socket.IO contract from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project. * Create channels in the Realtime dashboard. * Use the [TypeScript SDK reference](/sdks/typescript/realtime) for client subscriptions. * Add webhook URLs to a channel when another service needs the same event stream. # Sites Source: https://docs.insforge.dev/core-concepts/sites/overview Deploy the frontend of your InsForge project to Vercel from the CLI or dashboard, with tracked URLs, environment variables, domains, and deployment history. Use InsForge Sites to deploy, publish, ship, or go live with the browser-facing app you built on InsForge. If you are asking "can I deploy my app?" or "how do I take my app live?", Sites is the answer. The InsForge CLI uploads your frontend source through InsForge, which creates a Vercel production deployment. The dashboard tracks the URL, status, deployment history, environment variables, and domains. InsForge Sites dashboard **Need to deploy a container or backend service?** Use [Compute](/core-concepts/compute/overview) for workers, queues, WebSocket servers, and long-running services. Sites are for frontend websites and framework builds that produce a hosted web app. **Trying to run InsForge itself on your own servers?** That is self-hosting the InsForge platform, not deploying your app. See the self-hosting guides for [AWS EC2](/deployment/deploy-to-aws-ec2), [GCP](/deployment/deploy-to-google-cloud-compute-engine), [Azure](/deployment/deploy-to-azure-virtual-machines), or any [Linux VPS](/deployment/deployment-security-guide). Sites deploys the app you built; self-hosting deploys the InsForge backend that your app runs on. ```mermaid theme={null} flowchart TB CLI[InsForge CLI] --> API[InsForge deployment API] Dashboard[Dashboard] --> API API --> Source[Frontend source upload] API --> Config[Environment variables and domains] Source --> Vercel[Vercel production build] Config --> Vercel Vercel --> App[Frontend app] App --> URL[Public URL] App --> Status[Status and deployment history] style CLI fill:#1e293b,stroke:#475569,color:#e2e8f0 style Dashboard fill:#1e293b,stroke:#475569,color:#e2e8f0 style API fill:#166534,stroke:#22c55e,color:#dcfce7 style Source fill:#0e7490,stroke:#06b6d4,color:#cffafe style Config fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe style Vercel fill:#c2410c,stroke:#fb923c,color:#fed7aa style App fill:#166534,stroke:#22c55e,color:#dcfce7 style URL fill:#166534,stroke:#22c55e,color:#dcfce7 style Status fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe ``` ## Features ### CLI deploys Deploy from your app's source directory. The CLI uploads the source tree, skips local-only files such as `node_modules`, `.git`, build output, and `.env` files, then starts the Vercel build through InsForge. ```bash theme={null} npx @insforge/cli deployments deploy ./frontend ``` ### Framework builds Deploy React, Vue, Svelte, Next.js, static sites, and other frontend projects. InsForge sends the source files to Vercel, where framework detection and project files such as `package.json` and `vercel.json` decide how the app builds. ### Environment variables Manage provider environment variables from the dashboard. Use public prefixes such as `VITE_` or `NEXT_PUBLIC_` only for values that are safe to expose in browser code. ```bash theme={null} npx @insforge/cli deployments env list npx @insforge/cli deployments env set VITE_INSFORGE_URL https://your-project.region.insforge.app npx @insforge/cli deployments env set VITE_INSFORGE_ANON_KEY ik_xxx ``` ### Deployment history Review previous runs, sync Vercel status, inspect metadata, and cancel in-progress deployments from the Deployment Logs page. ```bash theme={null} npx @insforge/cli deployments list npx @insforge/cli deployments status deployment_123 --sync npx @insforge/cli deployments cancel deployment_123 ``` ### Domains Every ready deployment gets a default URL at `https://.insforge.site`. You can also set an InsForge-managed slug at `https://.insforge.site`. For a custom domain, add the domain in the dashboard and configure the DNS record it returns, usually a CNAME for subdomains. ## Deploy with it Connect your project and run InsForge CLI commands from your app directory. ## Next steps * Set up the [CLI](/quickstart) and connect your project. * Add browser-safe environment variables from the dashboard or with `npx @insforge/cli deployments env set`. * Run `npx @insforge/cli deployments deploy ./frontend`. # Storage Source: https://docs.insforge.dev/core-concepts/storage/overview Store and serve images, video, PDFs, and other binary files in an S3-compatible bucket with signed URLs and Row Level Security on every InsForge project. Use InsForge to store and serve large binary files: images, videos, PDFs, audio, backups, anything you would not put in a database row. Every project gets an S3-compatible bucket. Files are served behind signed URLs, access policies follow the same row-level security model as the database, and the S3 API works with rclone, the AWS CLI, Terraform, and SDKs in any language. InsForge dashboard storage browser showing a photos bucket and the file listing **Looking for structured data?** Use [Database](/core-concepts/database/overview) for rows, relations, and queries. Storage holds objects; the database holds rows. Keep file metadata (owner, name, size, content type) in a database table and the bytes in storage. ```mermaid theme={null} graph TB Client[Client Application] --> SDK[InsForge SDK] SDK --> StorageAPI[Storage API] StorageAPI --> S3[AWS S3] StorageAPI --> DB[(PostgreSQL)] DB --> Metadata[File Metadata] DB --> Buckets[Bucket Configuration] S3 --> DirectUpload[Presigned URLs] S3 --> SecureAccess[IAM Policies] style Client fill:#1e293b,stroke:#475569,color:#e2e8f0 style SDK fill:#1e40af,stroke:#3b82f6,color:#dbeafe style StorageAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style S3 fill:#ea580c,stroke:#f97316,color:#fed7aa style DB fill:#0e7490,stroke:#06b6d4,color:#cffafe style Metadata fill:#0e7490,stroke:#22d3ee,color:#cffafe style Buckets fill:#0e7490,stroke:#22d3ee,color:#cffafe style DirectUpload fill:#ea580c,stroke:#fb923c,color:#fed7aa style SecureAccess fill:#ea580c,stroke:#fb923c,color:#fed7aa ``` ## Features ### S3-compatible API Point any S3 client at your project's bucket. Native AWS credentials, native multipart uploads, native presigned URLs. See [S3 compatibility](/core-concepts/storage/s3-compatibility). ### Signed URLs Generate time-limited URLs to share private objects without exposing your credentials. The SDK and REST API both issue signed URLs for upload and download. ### Row-level security Storage policies read the same auth JWT as database queries. The same user who can `SELECT` a row can `GET` the file the row references, so you never maintain a separate set of storage permissions. ### Buckets Group objects into buckets with separate access policies. Public buckets serve files directly over HTTPS; private buckets require a signed URL or an authenticated request. ### Direct uploads Browser and mobile clients upload straight to storage with a presigned URL. The backend never proxies bytes. ## Concepts Point any S3 client at your project's bucket with native credentials. ## Build with it Upload, download, list, and manage objects from Node, browser, and edge. Native Swift storage client for iOS and macOS. Coroutines-first storage client for Android and JVM. Plain HTTP storage endpoints, callable from any language. ## Next steps * Set up the [CLI](/quickstart) to link your project (the recommended path). * Browse the [TypeScript SDK reference](/sdks/typescript/storage) for uploads and downloads. # S3-compatible gateway Source: https://docs.insforge.dev/core-concepts/storage/s3-compatibility Use any AWS SigV4 client — aws CLI, rclone, boto3, Terraform — against InsForge Storage through the S3-compatible gateway at /storage/v1/s3. InsForge Storage speaks the [AWS S3 protocol](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) at `/storage/v1/s3`. Available on cloud projects and on self-hosted deployments with an S3-backed storage provider — see [Self-hosted storage](/deployment/self-host-storage). ## Concepts Long-lived access keys signed with SigV4. Project-admin scope across every bucket, path-style URLs only. S3 uploads appear immediately in the REST API and Dashboard. Generate keys in **Storage → Settings → S3 Configuration**. ## Usage Fetch endpoint and region from the Dashboard or `GET /api/storage/s3/config`. ```ini theme={null} # ~/.aws/credentials [insforge] aws_access_key_id = your_access_key_id aws_secret_access_key = your_secret_access_key # ~/.aws/config [profile insforge] region = us-east-2 endpoint_url = https://project_ref.region.insforge.app/storage/v1/s3 s3 = addressing_style = path ``` ```bash theme={null} aws --profile insforge s3 cp ./photo.jpg s3://my-bucket/photo.jpg aws --profile insforge s3 sync ./dist s3://my-bucket/dist ``` In code, set `forcePathStyle: true` and point `endpoint` at `/storage/v1/s3`. ```ts theme={null} import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; const client = new S3Client({ forcePathStyle: true, region: 'us-east-2', endpoint: 'https://project_ref.region.insforge.app/storage/v1/s3', credentials: { accessKeyId: '...', secretAccessKey: '...' }, }); await client.send(new PutObjectCommand({ Bucket: 'my-bucket', Key: 'hello.txt', Body: 'hello' })); ``` ## Limits `PutObject` caps at 5 GB, multipart at 5 TB. 50 keys per project, 15-minute clock skew. Secret keys show once on creation. Not supported: presigned URLs (use `POST /api/storage/buckets/:bucket/upload-strategy`), session tokens, virtual-hosted URLs. Versioning, SSE-C/KMS, ACLs, object lock, tagging, lifecycle, and CORS return `501 NotImplemented`. ## More resources * [Storage overview](/core-concepts/storage/overview) for the gateway internals. * [TypeScript storage SDK](/sdks/typescript/storage) for browser uploads. * [AWS SigV4 reference](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html) for signing details. # Web Scraper overview: Apify integration for InsForge Source: https://docs.insforge.dev/core-concepts/webscraper/overview Connect Apify to InsForge to let your coding agent run scrapers on demand, manage actors and runs, and land scraped datasets in your app. Use InsForge Web Scraper to give your coding agent live access to external data. Connect your own Apify account once, and your agent can run scrapers (Apify calls them actors) on demand. The dashboard shows your actors, run history, and scraped datasets without leaving InsForge. Connect Apify once, then paste the scrape prompt into your coding agent. The agent authenticates with your InsForge-managed Apify token, picks the right actor for the job, and returns the results. InsForge Web Scraper dashboard showing the Actors tab with connected Apify actors, their last run times, and run counts Apify remains the source of truth for actors, runs, and datasets. InsForge surfaces a focused subset for everyday checks, then deep-links into the Apify console for anything beyond it. The Web Scraper integration runs on InsForge Cloud and on self-hosted deployments alike; only the way you connect Apify differs. ```mermaid theme={null} flowchart TB Agent["Coding Agent"] --> Skill["Apify Skill + CLI"] Admin["Dashboard"] --> ScraperAPI["Web Scraper API"] Skill --> Apify["Apify"] ScraperAPI --> Apify Apify --> Actors["Actors"] Apify --> Runs["Runs"] Apify --> Datasets["Datasets"] Actors --> Pages["Web Scraper pages"] Runs --> Pages Datasets --> Pages style Admin fill:#1e293b,stroke:#475569,color:#e2e8f0 style Agent fill:#1e293b,stroke:#475569,color:#e2e8f0 style Skill fill:#1e40af,stroke:#3b82f6,color:#dbeafe style ScraperAPI fill:#166534,stroke:#22c55e,color:#dcfce7 style Apify fill:#c2410c,stroke:#fb923c,color:#fed7aa style Actors fill:#0e7490,stroke:#06b6d4,color:#cffafe style Runs fill:#0e7490,stroke:#06b6d4,color:#cffafe style Datasets fill:#0e7490,stroke:#06b6d4,color:#cffafe style Pages fill:#6b21a8,stroke:#a855f7,color:#f3e8ff ``` ## Features ### Apify connection Connect Apify from the Web Scraper page in the dashboard. What happens next depends on where InsForge runs. On InsForge Cloud it is one click: InsForge walks you through the Apify OAuth flow, stores the credentials server-side, and keeps the access token refreshed for you. Self-hosted deployments have no OAuth broker in front of them, so you bring your own credential instead. Create an API token in the Apify console under **Settings → Integrations**, paste it into the Web Scraper page, and InsForge stores it encrypted in your own backend's secret store. `insforge webscraper apify connect --token ` does the same from the CLI. Either way the token stays out of your repo and your app bundle; agents and functions fetch a live one from the backend when they need it. Self-hosted: mint a narrowly scoped Apify token rather than a full-account one. `GET /api/webscraper/apify/token` returns the stored token verbatim to admin callers — that is what lets the agent path log the Apify CLI in — so whatever the token can do in Apify is what an InsForge admin key can do. ### Scrape via your coding agent After connecting, the empty state ships a scrape prompt you can paste into your coding agent: ``` Use the insforge webscraper apify skill to scrape and return the results. ``` Behind the prompt, `npx @insforge/cli webscraper apify login` fetches your InsForge-managed Apify token, authenticates the local Apify CLI headlessly (no browser OAuth), and installs the Apify agent skills. From there the agent picks an actor from the Apify Store, starts runs, and reads the results back. ### Actors The actors you have used or created recently, with their last run time and total run count. Each row deep-links into the Apify console for full actor configuration. ### Runs Recent scraper executions with status (succeeded, failed, running), start time, and cost in USD. Useful for a quick "did last night's scrape work and what did it cost" check without opening Apify. ### Dataset Datasets produced by your runs, with item counts, creation time, and the actor that produced them. Deep-links into Apify storage where you can inspect or export the items. ### Landing scraped data in your database Scraped results live in Apify datasets by default; nothing is written to your project's Postgres unless you want it there. For small scrapes, your agent can just return the results. For anything you want to keep or refresh on a schedule, have the agent deploy an [edge function](/core-concepts/functions/overview) or [compute service](/core-concepts/compute/overview). Either one fetches the dataset from Apify and upserts rows into a table. ### Settings and disconnect The Web Scraper Config dialog (the gear icon in the sidebar) shows the connected Apify account, plan, and data retention, links into the Apify console, and lets admins disconnect. Disconnecting only stops InsForge from using your Apify credentials — self-hosted, it deletes the stored API token from your secret store outright. Your Apify account, actors, and datasets stay intact, and you can reconnect anytime. ## Concepts The serverless scrapers behind every run, from ready-made Store actors to your own. How datasets store scraped items and how to export or fetch them via API. ## Build with it `npx @insforge/cli webscraper apify connect` links your project to Apify, then logs your local agent in. Thousands of ready-made actors for common targets, from Google Maps to social platforms. Call actors and read datasets from your edge functions or compute services. ## Next steps * Open the Web Scraper page in the dashboard and click **Connect Apify** — or, self-hosted, paste your Apify API token there. * Paste the scrape prompt into your coding agent and tell it what you want to scrape. * When a scrape is worth keeping, ask your agent to land the dataset in a table via an [edge function](/core-concepts/functions/overview) or a [schedule](/core-concepts/functions/schedules). # Self-Host InsForge on AWS EC2 Source: https://docs.insforge.dev/deployment/deploy-to-aws-ec2 Step-by-step guide to self-host the InsForge platform on an AWS EC2 instance using Docker Compose, including SSH setup, domain config, and TLS termination. # Self-Host InsForge on AWS EC2 This guide will walk you through self-hosting the InsForge platform on an AWS EC2 instance using Docker Compose. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. This guide is for running the InsForge backend on your own infrastructure. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## 📋 Prerequisites * AWS Account with EC2 access * Basic knowledge of SSH and command-line operations * Domain name (optional, for custom domain setup) ## 🚀 Deployment Steps ### 1. Create and Configure EC2 Instance #### 1.1 Launch EC2 Instance 1. **Log into AWS Console** and navigate to EC2 Dashboard 2. **Click "Launch Instance"** 3. **Configure Instance:** * **Name**: `insforge-server` (or your preferred name) * **AMI**: Ubuntu Server 24.04 LTS (HVM), SSD Volume Type * **Instance Type**: `t3.medium` or larger (minimum 2 vCPU, 4 GB RAM) * For production: `t3.large` (2 vCPU, 8 GB RAM) recommended * For testing: `t3.small` (2 vCPU, 2 GB RAM) minimum * **Key Pair**: Create new or select existing key pair (download and save the `.pem` file) * **Storage**: 30 GB gp3 (minimum 20 GB recommended) #### 1.2 Configure Security Group Create or configure security group with the following inbound rules: | Type | Protocol | Port Range | Source | Description | | ---------- | -------- | ---------- | --------- | --------------------- | | SSH | TCP | 22 | My IP | SSH access | | HTTP | TCP | 80 | 0.0.0.0/0 | HTTP access | | HTTPS | TCP | 443 | 0.0.0.0/0 | HTTPS access | | Custom TCP | TCP | 7130 | 0.0.0.0/0 | Dashboard + API | | Custom TCP | TCP | 5432 | 0.0.0.0/0 | PostgreSQL (optional) | > ⚠️ **Security Note**: For production, restrict PostgreSQL (5432) to specific IP addresses or remove external access entirely. Consider using a reverse proxy (nginx) and exposing only ports 80/443. #### 1.3 Allocate Elastic IP (Recommended) 1. Navigate to **Elastic IPs** in EC2 Dashboard 2. Click **Allocate Elastic IP address** 3. Associate the Elastic IP with your instance This ensures your instance keeps the same IP address even after restarts. ### 2. Connect to Your EC2 Instance ```bash theme={null} # Set correct permissions for your key file chmod 400 your-key-pair.pem # Connect via SSH ssh -i your-key-pair.pem ubuntu@your-ec2-public-ip ``` ### 3. Install Dependencies #### 3.1 Update System Packages ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` #### 3.2 Install Docker ```text theme={null} Follow the instructions of the link below to install and verify docker on your new ubuntu ec2 instance: https://docs.docker.com/engine/install/ubuntu/ ``` #### 3.3 Add Your User to Docker Group After installing Docker, you need to add your user to the `docker` group to run Docker commands without `sudo`: ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` **Verify it works:** ```bash theme={null} # This should now work without sudo docker ps ``` > 💡 **Note**: If `docker ps` doesn't work immediately, log out and log back in via SSH, then try again. > ⚠️ **Security Note**: Adding a user to the `docker` group grants them root-equivalent privileges on the system. This is acceptable for single-user environments like your EC2 instance, but be cautious on shared systems. #### 3.4 Install Git ```bash theme={null} sudo apt install git -y ``` ### 4. Deploy InsForge #### 4.1 Get the Repository ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge ``` Checks out the files the stack reads and generates `JWT_SECRET`, `ENCRYPTION_KEY`, `ROOT_ADMIN_PASSWORD` and `POSTGRES_PASSWORD` into `.env`. Nothing is started. #### 4.2 Create Environment Configuration ```bash theme={null} cd ~/insforge nano .env ``` The secrets are already generated — leave them as they are. Set the URL browsers will use: ```env theme={null} API_BASE_URL=http://:7130 VITE_API_BASE_URL=http://:7130 ``` Optional, all off by default: ```env theme={null} OPENROUTER_API_KEY= # AI features VERCEL_TOKEN= # site deployments GOOGLE_CLIENT_ID= # OAuth providers GOOGLE_CLIENT_SECRET= ``` `.env.example` carries every remaining variable with its defaults. > 💡 Back up `.env` somewhere safe. Its secrets are what let you migrate or restore this instance. #### 4.3 Start InsForge Services ```bash theme={null} # Pull Docker images and start services docker compose up -d # View logs to ensure everything started correctly docker compose logs -f ``` Press `Ctrl+C` to exit log view. #### 4.4 Verify Services ```bash theme={null} # Check running containers docker compose ps # You should see 4 running services: # - postgres # - postgrest # - insforge # - deno ``` ### 5. Access Your InsForge Instance #### 5.1 Test Backend API ```bash theme={null} curl http://your-ec2-ip:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "2.1.7", "service": "Insforge OSS Backend", "timestamp": "2025-10-17T..." } ``` #### 5.2 Access Dashboard Open your browser and navigate to: ```text theme={null} http://your-ec2-ip:7130 ``` Log in with the `ROOT_ADMIN_USERNAME` and `ROOT_ADMIN_PASSWORD` you set in `.env`. ### 6. Configure Domain (Optional but Recommended) #### 6.1 Update DNS Records Add DNS A records pointing to your EC2 Elastic IP: ```text theme={null} api.yourdomain.com → your-ec2-ip app.yourdomain.com → your-ec2-ip ``` #### 6.2 Install Nginx Reverse Proxy ```bash theme={null} sudo apt install nginx -y ``` Create Nginx configuration: ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Add the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } # Dashboard (served by the backend on the same port as the API) server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } ``` Enable the configuration: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` #### 6.3 Install SSL Certificate (Recommended) ```bash theme={null} # Install Certbot sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com # Follow the prompts to complete setup ``` Update your `.env` file with HTTPS URLs: ```bash theme={null} cd ~/insforge nano .env ``` Change: ```env theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart services: ```bash theme={null} docker compose down docker compose up -d ``` ## 🔧 Management & Maintenance ### View Logs ```bash theme={null} # All services docker compose logs -f # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` ### Stop Services ```bash theme={null} docker compose down ``` ### Restart Services ```bash theme={null} docker compose restart ``` ### Update InsForge An update is a pull and restart — but the checkout matters too: the stack reads Postgres's configuration and the Deno functions from it. Run this from `~/insforge`: ```bash theme={null} cd ~/insforge git pull origin main # Pick up any files this release added to the sparse checkout sh deploy/setup.sh . docker compose pull && docker compose up -d ``` ### Backup Database Run these from `~/insforge`: ```bash theme={null} # Create backup docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql # Restore from backup cat backup_file.sql | docker compose exec -T postgres psql -U postgres -d insforge ``` ### Monitor Resources ```bash theme={null} # Check disk usage df -h # Check memory usage free -h # Check Docker stats docker stats ``` ## 🐛 Troubleshooting ### Services Won't Start ```bash theme={null} # Check logs for errors docker compose logs # Check disk space df -h # Check memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### Cannot Connect to Database ```bash theme={null} # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres # Verify credentials in .env file cat .env | grep POSTGRES ``` ### Port Already in Use ```bash theme={null} # Check what's using the port sudo netstat -tulpn | grep :7130 # Kill the process or change port in docker-compose.yml ``` ### Out of Memory Consider upgrading to a larger instance type: ```text theme={null} - Current: t3.medium (4 GB RAM) - Upgrade to: t3.large (8 GB RAM) ``` ### SSL Certificate Issues ```bash theme={null} # Renew certificates sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ## 📊 Performance Optimization ### For Production Workloads 1. **Upgrade Instance Type**: Use `t3.large` or `t3.xlarge` 2. **Enable Auto-scaling**: Set up Application Load Balancer with auto-scaling groups 3. **Use RDS**: Migrate from containerized PostgreSQL to AWS RDS for better reliability 4. **Enable CloudWatch**: Monitor metrics and set up alarms 5. **Configure Backups**: Set up automated daily backups 6. **Use S3 for Storage**: Configure S3 bucket for file uploads instead of local storage ### Database Optimization ```conf theme={null} # Increase PostgreSQL shared_buffers (edit postgresql.conf in deploy/docker-init/db/) # Recommended: 25% of available RAM shared_buffers = 1GB effective_cache_size = 3GB ``` ## 🔒 Security Best Practices 1. **Change Default Passwords**: Update admin and database passwords 2. **Enable Firewall**: Use AWS Security Groups effectively 3. **Regular Updates**: Keep system and Docker images updated 4. **SSL/TLS**: Always use HTTPS in production 5. **Backup Regularly**: Automate database backups 6. **Monitor Logs**: Set up log monitoring and alerts 7. **Limit SSH Access**: Restrict SSH to specific IP addresses 8. **Use IAM Roles**: Instead of AWS access keys where possible ## 🆘 Support & Resources * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) ## 📝 Cost Estimation **Monthly AWS Costs (approximate):** | Component | Type | Monthly Cost | | --------------- | ----------------- | ---------------- | | EC2 Instance | t3.medium | \~\$30 | | Storage (30 GB) | EBS gp3 | \~\$3 | | Elastic IP | (if running 24/7) | \$0 | | Data Transfer | First 100GB free | Variable | | **Total** | | **\~\$33/month** | > 💡 **Cost Optimization**: Use AWS Savings Plans or Reserved Instances for long-term deployments to save up to 70%. *** **Congratulations! 🎉** Your InsForge instance is now running on AWS EC2. You can start building applications by connecting AI agents to your backend platform. For other production deployment strategies, check out our [deployment guides](/deployment/deployment-security-guide). # Self-Host InsForge on Azure Virtual Machines Source: https://docs.insforge.dev/deployment/deploy-to-azure-virtual-machines Self-host the InsForge platform on an Azure Virtual Machine using Docker Compose, covering SSH access, custom domains, HTTPS, and production hardening. # 📖 Self-Hosting InsForge on Azure Virtual Machines (Extended Guide) This guide provides comprehensive, step-by-step instructions for self-hosting, managing, and securing the InsForge platform on an Azure Virtual Machine (VM) using Docker Compose. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. This guide is for running the InsForge backend on your own infrastructure. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## Prerequisites * An active **Azure account**. * An **SSH client** to connect to the virtual machine. * Basic familiarity with the **Linux command line**. *** ## Step 1: 🖥️ Create an Azure Virtual Machine 1. **Log in to the [Azure Portal](https://portal.azure.com/)** and navigate to **Virtual machines**. 2. Click **+ Create** > **Azure virtual machine**. 3. **Basics Tab:** * **Resource Group:** Create a new one (e.g., `insforge-rg`). * **Virtual machine name:** `insforge-vm`. * **Image:** **Ubuntu Server 22.04 LTS** or newer. * **Size:** `Standard_B2s` (2 vCPUs, 4 GiB memory) is a good start. For production, consider `Standard_B4ms` (4 vCPUs, 16 GiB memory). * **Authentication type:** **SSH public key**. * **SSH public key source:** **Generate new key pair**. Name it `insforge-key`. 4. **Networking Tab:** * In the **Network security group** section, click **Create new**. * Add the following **inbound port rules** to allow traffic: * `22` (SSH) * `80` (HTTP for Nginx) * `443` (HTTPS for Nginx/SSL) * `7130` (InsForge API and dashboard) 5. **Review and Create:** * Click **Review + create**, then **Create**. * When prompted, **Download private key and create resource**. Save the `.pem` file securely. * Once deployed, find and copy your VM's **Public IP address**. *** ## Step 2: ⚙️ Connect and Set Up the Server 1. **Connect via SSH:** Open your terminal, give your key the correct permissions, and connect to the VM. ```bash theme={null} chmod 400 /path/to/your/insforge-key.pem ssh -i /path/to/your/insforge-key.pem azureuser@ ``` 2. **Update System Packages:** ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` 3. **Install Docker:** Follow the official, up-to-date instructions on the Docker website to install Docker Engine on Ubuntu: **[https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/)** 4. **Add Your User to the Docker Group:** This step allows you to run Docker commands without `sudo`. ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` Verify it works. This command should now run without `sudo`: ```bash theme={null} docker ps ``` > 💡 **Note:** If `docker ps` doesn't work, log out of your SSH session and log back in, then try again. > > ⚠️ **Security Note:** Adding a user to the `docker` group grants them root-equivalent privileges. This is acceptable for a single-user VM but be cautious on shared systems. 5. **Install Git:** ```bash theme={null} sudo apt install git -y ``` *** ## Step 3: 🚀 Deploy InsForge 1. **Get the Repository:** ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge ``` Checks out the files the stack reads and generates `JWT_SECRET`, `ENCRYPTION_KEY`, `ROOT_ADMIN_PASSWORD` and `POSTGRES_PASSWORD` into `.env`. Nothing is started. 2. **Create Environment Configuration:** The secrets are already generated — leave them as they are. Point the API URLs at your VM. ```bash theme={null} cd ~/insforge nano .env ``` ```ini theme={null} API_BASE_URL=http://:7130 VITE_API_BASE_URL=http://:7130 ``` The rest of `.env.example` covers optional features (OpenRouter, Vercel deployments, OAuth providers). Leave those blank unless you need them. > Back up `.env` somewhere safe. Its secrets are what let you migrate or restore this instance. 3. **Start InsForge Services:** Pull the Docker images and start all services in the background. ```bash theme={null} docker compose up -d ``` 4. **Verify Services:** Check that all four containers are running. ```bash theme={null} docker compose ps ``` You should see the `postgres`, `postgrest`, `insforge`, and `deno` services running. *** ## Step 4: 🔑 Access Your InsForge Instance 1. **Test Backend API:** Use `curl` to check the health endpoint. ```bash theme={null} curl http://:7130/api/health ``` You should see a response like: `{"status":"ok", ...}` 2. **Access Dashboard:** Open your browser and navigate to: `http://:7130` Log in with the `ROOT_ADMIN_USERNAME` and `ROOT_ADMIN_PASSWORD` you set in your `.env` file. *** ## Step 5: 🌐 Configure Domain (Optional but Recommended) 1. **Update DNS Records:** In your domain provider's DNS settings, add two **A records** pointing to your VM's Public IP address: * `api.yourdomain.com` → `` * `app.yourdomain.com` → `` 2. **Install and Configure Nginx as a Reverse Proxy:** ```bash theme={null} sudo apt install nginx -y sudo nano /etc/nginx/sites-available/insforge ``` Paste the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # Frontend Dashboard (served by the same port as the API) server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; } } ``` Enable the configuration and reload Nginx: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` 3. **Install SSL Certificate with Certbot:** ```bash theme={null} # Install Certbot for Nginx sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates and configure Nginx automatically sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com ``` Follow the prompts. Certbot will handle the rest. 4. **Update `.env` with HTTPS URLs:** Edit your `.env` file and update the URLs. ```bash theme={null} cd ~/insforge nano .env ``` Change the URLs to `https`: ```ini theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart the services for the changes to take effect: ```bash theme={null} docker compose down && docker compose up -d ``` *** ## 🔧 Management & Maintenance * **View Logs:** `docker compose logs -f` (all services) or `docker compose logs -f insforge` (specific service). * **Stop Services:** `docker compose down` * **Restart Services:** `docker compose restart` * **Update InsForge:** Run these from `~/insforge`. The images are prebuilt, so pull the latest tags instead of rebuilding. ```bash theme={null} cd ~/insforge git -C ~/insforge pull origin main sh deploy/setup.sh . docker compose pull && docker compose up -d ``` * **Backup Database:** Run from `~/insforge`. ```bash theme={null} docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql ``` ## 🐛 Troubleshooting * **Services Won't Start:** Check `docker compose logs` for errors. Ensure you have enough disk space (`df -h`) and memory (`free -h`). * **Port Already in Use:** Check which process is using the port with `sudo netstat -tulpn | grep :7130`. * **Out of Memory:** Consider upgrading your Azure VM to a size with more RAM. ## 📊 Cost Estimation > **Disclaimer:** Prices are estimates based on Pay-As-You-Go rates in a common region (e.g., East US) and can vary. Always check the official [Azure Pricing Calculator](https://azure.microsoft.com/en-us/pricing/calculator/) for the most accurate information. On Azure, you pay for the VM's resources (CPU, RAM, Storage), which are shared by all the Docker services you run on it. ### Free Tier (for Testing) * **Cost:** **\~\$0/month** for the first 12 months. * **Resources:** Azure provides a free tier that includes 750 hours/month of a `B1s` burstable VM. * **Limitations:** This VM has very limited resources (1 vCPU, 1 GiB RAM) and may run slowly. It's suitable only for basic testing and familiarization, not for active development or production. ### Starter Setup (for Development & Small Projects) * **Cost:** **\~$30 - $40/month** * **Resources:** This estimate is for a `Standard_B2s` VM (2 vCPU, 4 GiB RAM) running all the InsForge Docker containers. * **Breakdown:** The cost primarily consists of the VM compute hours. It also includes the OS disk storage and a static public IP address. This single VM runs your database, backend, Deno, and all other services. ### Production Setup (for Scalability & Reliability) For production, you can choose between an all-in-one, larger VM or a more robust setup using managed services. * **Option A: All-in-One Larger VM** * **Cost:** **\~$150 - $170/month** * **Resources:** A more powerful `Standard_B4ms` VM (4 vCPU, 16 GiB RAM) to handle higher traffic and all services. * **Pros:** Simple to manage, consolidated cost. * **Cons:** Database and application share resources, which can create performance bottlenecks. Scaling requires upgrading the entire VM. * **Option B: Managed Services (Recommended for Production)** * **Cost:** **\~\$120+/month** (highly variable) * **Resources:** * **Application VM:** A `Standard_B2s` VM for the app services (InsForge, PostgREST, Deno). `(~$30/month)` * **Managed Database:** Use **Azure Database for PostgreSQL** for reliability, automated backups, and scaling. `(~$40+/month for a starter tier)` * **Pros:** Highly reliable and scalable. Database performance is isolated and guaranteed. Managed backups and security. * **Cons:** More complex setup, costs are distributed across multiple services. ## 🔒 Security Best Practices * **Change Default Passwords:** Always update admin and database passwords. * **Enable Firewall:** Use Azure **Network Security Groups (NSGs)** to restrict access to necessary ports and IP addresses. * **Regular Updates:** Periodically run `sudo apt update && sudo apt upgrade -y` and update InsForge. * **Backup Regularly:** Automate database and configuration backups. # Self-Host InsForge on Containarium Source: https://docs.insforge.dev/deployment/deploy-to-containarium Self-host the InsForge platform on a Containarium LXC host with per-tenant containers, ZFS snapshots, and MCP-driven provisioning for agent-native deployments. # Self-Host InsForge on Containarium This guide walks through self-hosting the InsForge platform on a [Containarium](https://github.com/footprintai/containarium) host. Containarium is an open-source, self-hostable platform that gives each tenant a persistent Linux container (LXC) with first-class SSH, MCP, and TLS-on-a-hostname primitives, a natural fit for agent-driven InsForge deployments. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. This guide is for running the InsForge backend on your own infrastructure. This guide is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## When to choose Containarium Containarium fits InsForge deployments where you want: * **Self-hosted, multi-tenant infrastructure**: many isolated InsForge projects on one host, each in its own LXC, with one TLS hostname per project — no shared `docker compose -p` bookkeeping. * **Persistence and resilience**: ZFS-backed storage, daily snapshots with 30-day retention, automatic survival across host reboots and spot-VM termination. * **An agent-native control plane**: Containarium exposes its admin surface as an MCP server (`mcp-server`) and ships a second MCP that runs inside each container (`agent-box`), so the same agent that builds your app can also provision its backend end-to-end. ## Prerequisites * A running Containarium host. If you don't have one, the [Containarium quickstart](https://github.com/footprintai/containarium#quick-start) takes \~5 minutes on a fresh Ubuntu 24.04 VM. * `containarium` CLI on your local machine, configured to reach the daemon (`--server :8080`), or run the CLI directly on the host. * An admin token (`containarium token generate --username admin --roles admin --secret-file /etc/containarium/jwt.secret`). * A domain you control, with a DNS A/CNAME record pointing the chosen subdomain at your Containarium sentinel's public IP. Minimum sizing per InsForge box: **2 vCPU, 4 GB RAM, 30 GB disk**. ## Deployment ### 1. Provision a box with Docker pre-installed ```bash theme={null} containarium create insforge \ --stack docker \ --memory 4GB \ --cpu 2 \ --disk 30GB \ --ssh-key ~/.ssh/id_ed25519.pub ``` The `--stack docker` flag installs Docker CE and the compose plugin inside the container. Wire your SSH config so `ssh insforge` works: ```bash theme={null} containarium ssh-config sync # Then add one line to ~/.ssh/config: # Include ~/.containarium/ssh_config ssh insforge ``` ### 2. Set InsForge up inside the box ```bash theme={null} ssh insforge 'curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge' ``` Checks out the files the stack reads and generates the secrets into `~/insforge/.env`. Nothing is started. ### 3. Configure environment Edit `~/insforge/.env` inside the box. At minimum set: ```env theme={null} API_BASE_URL=https:// VITE_API_BASE_URL=https:// ``` The secrets are already generated — leave them as they are. See [`.env.example`](https://github.com/insforge/insforge/blob/main/.env.example) for the full list (OpenRouter, OAuth providers, Stripe, Vercel). > **Secrets handling:** for production, prefer Containarium's tmpfs secrets (`--delivery=file`; see [Containarium's secrets ops doc](https://github.com/footprintai/Containarium/blob/main/docs/SECRETS-OPERATIONS.md)). These are delivered as 0440 files on tmpfs and never appear in `/proc//environ`. Wire them into the compose stack via a compose override using `env_file:`. ### 4. Start InsForge and enable autostart You can start it once by hand: ```bash theme={null} ssh insforge 'cd ~/insforge && docker compose up -d' ``` …or — recommended — wire it into Containarium's compose-autostart so the stack survives host reboots: ```bash theme={null} containarium compose enable insforge --dir /home/insforge/insforge ``` This installs a systemd-user unit inside the box that brings the stack up at every container boot and restarts services on failure with backoff. Verify with: ```bash theme={null} containarium compose status insforge ``` You should see `4/4 services up`: `postgres`, `postgrest`, `insforge`, `deno`. (The compose file ships healthchecks for `postgres`, `postgrest`, and `deno`; `insforge` reports `Up` once the others are healthy and it has started.) ### 5. Expose on a public hostname InsForge serves the dashboard and API on port 7130 by default. ```bash theme={null} containarium expose-port insforge \ --container-port 7130 \ --domain ``` This wires Caddy on the Containarium sentinel to terminate TLS for `` and forward to the InsForge container. The certificate is provisioned automatically via ACME on the first request — no certbot, no nginx config. Verify: ```bash theme={null} curl https:///api/health ``` Expected: ```json theme={null} { "status": "ok", "version": "2.x.x", "service": "Insforge OSS Backend", "timestamp": "..." } ``` ### 6. Connect your agent to InsForge MCP Open `https://` in a browser and follow the in-product flow to connect your MCP-compatible agent (Cursor, Claude Code, Windsurf, OpenCode, etc.) to the InsForge MCP server. Verify the connection by sending this prompt to your agent: ```text theme={null} I'm using InsForge as my backend platform, call InsForge MCP's fetch-docs tool to learn about InsForge instructions. ``` ## Agent-driven deploy (optional) Because Containarium exposes its admin surface as an MCP server (`mcp-server`) and ships a second MCP inside every container (`agent-box`), an MCP-speaking agent can do the whole deployment end-to-end: ```text theme={null} agent: create me a container called 'insforge' → mcp__containarium__create_container( username="insforge", cpu="2", memory="4GB", disk="30GB", stack="docker") agent: set InsForge up, fill in .env → ssh insforge agent-box → shell_exec("curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge") → edit ~/insforge/.env: API_BASE_URL, VITE_API_BASE_URL (setup.sh already generated the secrets — do not rewrite the file) agent: enable autostart → mcp__containarium__compose_enable( username="insforge", dir="/home/insforge/insforge") agent: expose on a public hostname → mcp__containarium__expose_port( username="insforge", container_port=7130, domain="") ``` See Containarium's [`docs/MCP-INTEGRATION.md`](https://github.com/footprintai/Containarium/blob/main/docs/MCP-INTEGRATION.md) for the platform MCP tool catalog. ## Multi-tenant: many InsForge projects per host Each project gets its own LXC and its own hostname; the sentinel routes by SNI. No port collisions (each container has its own network namespace), no shared compose project names. ```bash theme={null} containarium create insforge-acme --stack docker --memory 4GB --cpu 2 ... containarium create insforge-globex --stack docker --memory 4GB --cpu 2 ... containarium expose-port insforge-acme --container-port 7130 \ --domain acme. containarium expose-port insforge-globex --container-port 7130 \ --domain globex. ``` Each project gets isolated postgres / storage / deno volumes. ## Management ### View logs ```bash theme={null} ssh insforge 'cd ~/insforge && docker compose logs -f' ``` Or per service: `docker compose logs -f insforge` / `postgres` / `deno`. ### Update InsForge ```bash theme={null} ssh insforge <<'EOF' cd ~/insforge git -C ~/insforge pull origin main sh deploy/setup.sh . docker compose pull docker compose up -d EOF ``` If compose-autostart is enabled, no need to re-enable the unit — it tracks the directory, not a specific image tag. ### Back up the database ```bash theme={null} ssh insforge 'cd ~/insforge && docker compose exec -T postgres \ pg_dump -U postgres insforge' > backup_$(date +%Y%m%d_%H%M%S).sql ``` Containarium also snapshots the entire container daily via ZFS (30-day retention by default), covering the postgres data volume as a point-in-time-restore backstop. ### Stop / restart ```bash theme={null} containarium compose disable insforge # stop the compose stack and disable autostart containarium sleep insforge # stop the entire box containarium wake insforge # start the box; compose comes up via autostart ``` ## Troubleshooting ### `containarium compose enable` fails Verify Docker is working inside the box: ```bash theme={null} ssh insforge 'docker ps' ``` If you skipped `--stack docker` at create time, either install it manually inside the box or recreate with the flag. ### Public hostname doesn't resolve `containarium expose-port` configures Caddy on the sentinel; the DNS A/CNAME record for your subdomain must point at the sentinel's public IP. Check: ```bash theme={null} dig +short ``` ### Hostname resolves but returns 502 Check that InsForge is reachable from inside the box: ```bash theme={null} ssh insforge 'curl -s http://localhost:7130/api/health' ``` If the in-box check is fine, the bridge between sentinel and box is the next thing to investigate — see Containarium's [`docs/TUNNEL-REVERSE-PROXY.md`](https://github.com/footprintai/Containarium/blob/main/docs/TUNNEL-REVERSE-PROXY.md). ### Out of memory after `docker compose up` InsForge's four services need \~3 GB resident at idle. If you sized the box at 2 GB, resize: ```bash theme={null} containarium resize insforge --memory 4GB containarium sleep insforge && containarium wake insforge ``` ## Limitations * **AUTH\_PORT (7131) and DENO\_PORT (7133)** are not exposed externally by the steps above. If your app calls the standalone auth endpoint or direct Deno function URLs from outside the box, add additional `expose-port` calls with separate subdomains. * **`containarium compose enable` requires Containarium v0.18 or later** (the compose-autostart feature). On earlier versions, run `docker compose up -d` and add a `@reboot` cron entry by hand. * **GPU passthrough**: Containarium supports it, but InsForge's stock edge functions don't use GPU. Leave it off unless your custom Deno functions need it. ## Security notes * The container's user is unprivileged on the host (LXC unprivileged mode); container root ≠ host root. * The sentinel front-door supports source-IP allowlists for admin endpoints — see Containarium's [security runbook](https://github.com/footprintai/Containarium/blob/main/docs/security/OPERATOR-SECURITY-RUNBOOK.md). * For production, opt into Containarium's KMS envelope encryption (Vault Transit or GCP KMS) for any InsForge secrets stored in Containarium's secret store. * Use `containarium token generate --scopes containers:read,containers:write ...` to mint least-privilege tokens for agents rather than handing out admin tokens. ## Resources * **Containarium**: [https://github.com/footprintai/containarium](https://github.com/footprintai/containarium) * **Containarium docs**: [https://github.com/footprintai/Containarium/tree/main/docs](https://github.com/footprintai/Containarium/tree/main/docs) * **InsForge docs**: [https://docs.insforge.dev](https://docs.insforge.dev) * **InsForge Discord**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) *** For other deployment strategies, see the [deployment guides](/deployment/deployment-security-guide). # Self-Host InsForge on Coolify Source: https://docs.insforge.dev/deployment/deploy-to-coolify Self-host InsForge on Coolify as a Docker Compose resource, with the Postgres image built from the repo so its config always matches the release. # Self-Host InsForge on Coolify This guide walks through self-hosting the InsForge platform on [Coolify](https://coolify.io), an open-source PaaS you run on your own server. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. ## Prerequisites * A Coolify instance and a server attached to it * A domain or subdomain pointed at that server ## 1. Create the resource **New Resource → Docker Compose**, connect this repository (a public repository needs no GitHub App), then set: | Field | Value | | ----------------------- | ------------------------------------ | | Base Directory | `/` | | Docker Compose Location | `/deploy/coolify/docker-compose.yml` | Leave Base Directory at the repository root. The compose file builds Postgres from `deploy/Dockerfile.postgres`, whose build context is the root. ## 2. Environment variables Set these under **Environment Variables**. Generate each secret with `openssl rand -hex 32`: ```env theme={null} JWT_SECRET=<32+ characters> ENCRYPTION_KEY=<32+ characters, different from JWT_SECRET> POSTGRES_PASSWORD= ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD= ``` `ENCRYPTION_KEY` falls back to `JWT_SECRET` when unset, and rotating `JWT_SECRET` afterwards makes every stored secret impossible to decrypt — set it to its own value now. Postgres reads `POSTGRES_PASSWORD` only when it initializes the cluster. Changing it later does not change the database password. Everything else is optional; [`.env.example`](https://github.com/insforge/insforge/blob/main/.env.example) lists every supported variable with its default. ## 3. Assign a domain Coolify does not expose a compose service that publishes no ports. Under the resource's **insforge** service, assign your domain and set the port to `7130`, then add the matching URLs to the environment: ```env theme={null} API_BASE_URL=https://insforge.example.com VITE_API_BASE_URL=https://insforge.example.com ``` These have to match the URL browsers use, or the dashboard will call the wrong origin. Only `insforge` needs a domain. Postgres, PostgREST and the Deno runtime stay on the internal network. ## 4. Deploy Press **Deploy**. The first run builds two small images (Postgres and the Deno function host) and pulls the rest, then runs the backend's migrations automatically. Open your domain and sign in with `ROOT_ADMIN_USERNAME` / `ROOT_ADMIN_PASSWORD`. ## Updating Coolify redeploys on push if you enabled automatic deployment, or press **Redeploy**. Each deploy rebuilds the Postgres and Deno images from the current commit, so their configuration and function host track the release. Review the diff for `.env.example` before updating — a release that adds a variable will not add it to your Coolify environment. ## Storage Object storage defaults to the container filesystem on a Docker volume. For S3, MinIO or RustFS, see [Self-Hosted Storage](./self-host-storage.mdx) and set the `S3_*` variables in Coolify's environment; the compose file passes them through. ## Why Postgres is built rather than pulled InsForge's Postgres needs three files from this repository: `postgresql.conf`, which loads the `insforge_pg_utils` extension that row-level security on managed tables depends on, plus two init scripts. Coolify creates file bind mounts as directories ([coollabsio/coolify#3375](https://github.com/coollabsio/coolify/issues/3375)), so mounting them is not an option — Postgres will not start. Building the image at deploy time puts the current files in it instead, which also means the configuration cannot fall behind the code the way a prebuilt image can. # Self-Host InsForge on Dokploy Source: https://docs.insforge.dev/deployment/deploy-to-dokploy Self-host InsForge on Dokploy as a Compose application, with the Postgres image built from the repo so its config always matches the release. # Self-Host InsForge on Dokploy This guide walks through self-hosting the InsForge platform on [Dokploy](https://dokploy.com), an open-source PaaS you run on your own server. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. ## Prerequisites * A Dokploy instance * A domain or subdomain pointed at that server ## 1. Create the application **Create → Compose**, connect this repository as the provider, then set: | Field | Value | | ------------ | ----------------------------------- | | Compose Path | `deploy/dokploy/docker-compose.yml` | | Compose Type | Docker Compose | ## 2. Environment variables Set these under **Environment**. Generate each secret with `openssl rand -hex 32`: ```env theme={null} JWT_SECRET=<32+ characters> ENCRYPTION_KEY=<32+ characters, different from JWT_SECRET> POSTGRES_PASSWORD= ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD= ``` `ENCRYPTION_KEY` falls back to `JWT_SECRET` when unset, and rotating `JWT_SECRET` afterwards makes every stored secret impossible to decrypt — set it to its own value now. Postgres reads `POSTGRES_PASSWORD` only when it initializes the cluster. Changing it later does not change the database password. Everything else is optional; [`.env.example`](https://github.com/insforge/insforge/blob/main/.env.example) lists every supported variable with its default. ## 3. Add a domain Nothing is published to the host, so the stack is only reachable once you route to it. Under **Domains**, add your domain with: | Field | Value | | -------------- | ---------- | | Service Name | `insforge` | | Container Port | `7130` | Then add the matching URLs to the environment: ```env theme={null} API_BASE_URL=https://insforge.example.com VITE_API_BASE_URL=https://insforge.example.com ``` These have to match the URL browsers use, or the dashboard will call the wrong origin. Only `insforge` needs a domain. Postgres, PostgREST and the Deno runtime stay on Dokploy's internal network. ## 4. Deploy Press **Deploy**. The first run builds two small images (Postgres and the Deno function host) and pulls the rest, then runs the backend's migrations automatically. Open your domain and sign in with `ROOT_ADMIN_USERNAME` / `ROOT_ADMIN_PASSWORD`. ## Updating Press **Deploy** again, or enable Auto Deploy to redeploy on push. Each deploy rebuilds the Postgres and Deno images from the current commit, so their configuration and function host track the release. Review the diff for `.env.example` before updating — a release that adds a variable will not add it to your Dokploy environment. ## Storage Object storage defaults to the container filesystem on a Docker volume. Dokploy takes a single compose file, so the MinIO and RustFS overlays do not apply; see [Self-Hosted Storage](./self-host-storage.mdx) for the two options that do. ## Why Postgres is built rather than pulled InsForge's Postgres needs three files from this repository: `postgresql.conf`, which loads the `insforge_pg_utils` extension that row-level security on managed tables depends on, plus two init scripts. Dokploy re-clones `code/` on every deploy, so a bind mount pointing into the repository goes stale — [its docs](https://docs.dokploy.com/docs/core/troubleshooting/volumes-mounts) require File Mounts created in the UI and referenced as `../files/`, which is manual setup for every install. Building the image at deploy time puts the current files in it instead, with nothing to configure, and the configuration cannot fall behind the code the way a prebuilt image can. # Self-Host InsForge on Google Cloud Compute Engine Source: https://docs.insforge.dev/deployment/deploy-to-google-cloud-compute-engine Self-host the InsForge platform on a Google Cloud Compute Engine VM with Docker Compose, covering firewall rules, SSH access, custom domains, and HTTPS setup. # Self-Host InsForge on Google Cloud Compute Engine This guide will walk you through self-hosting the InsForge platform on Google Cloud Compute Engine using Docker Compose. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. This guide is for running the InsForge backend on your own infrastructure. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## 📋 Prerequisites * Google Cloud Account with billing enabled * Basic knowledge of SSH and command-line operations * Domain name (optional, for custom domain setup) ## 🚀 Deployment Steps ### 1. Create and Configure Compute Engine Instance #### 1.1 Create Google Cloud Project 1. **Log into Google Cloud Console** at [console.cloud.google.com](https://console.cloud.google.com) 2. **Click "Select a project"** in the top navigation bar 3. **Click "New Project"** 4. **Enter project name** (e.g., `insforge-deployment`) 5. **Click "Create"** 6. **Wait for project creation to complete** #### 1.2 Enable Required APIs 1. In your project, navigate to **APIs & Services** → **Library** 2. Search for and enable these APIs: * **Compute Engine API** * **Cloud Storage API** (if using for backups) * **Cloud SQL Admin API** (if using Cloud SQL) #### 1.3 Create Compute Engine Instance 1. Navigate to **Compute Engine** → **VM instances** 2. Click **"Create Instance"** 3. Configure your instance: * **Name**: `insforge-server` (or your preferred name) * **Region**: Choose a region close to your users * **Zone**: Select an availability zone (e.g., us-central1-a) * **Machine configuration**: * **Series**: N2 or E2 * **Machine type**: `e2-medium` or larger (minimum 2 vCPU, 4 GB RAM) * For production: `e2-standard-2` (2 vCPU, 8 GB RAM) recommended * For testing: `e2-small` (2 vCPU, 2 GB RAM) minimum * **Boot disk**: * **Operating system**: Ubuntu LTS (Ubuntu 22.04 LTS or newer) * **Boot disk type**: Balanced persistent disk * **Size**: 30 GB (minimum 20 GB recommended) * **Firewall**: * Allow HTTP traffic: **Checked** * Allow HTTPS traffic: **Checked** #### 1.4 Configure Firewall Rules 1. Navigate to **VPC network** → **Firewall** 2. Create or modify firewall rules to allow the following ports: | Name | Direction | Targets | Protocols/ports | Source filters | | ------------------ | --------- | --------------- | --------------- | ------------------------------------- | | insforge-ssh | Ingress | insforge-server | tcp:22 | Your IP address | | insforge-http | Ingress | insforge-server | tcp:80 | 0.0.0.0/0 | | insforge-https | Ingress | insforge-server | tcp:443 | 0.0.0.0/0 | | insforge-app | Ingress | insforge-server | tcp:7130 | 0.0.0.0/0 | | insforge-deno | Ingress | insforge-server | tcp:7133 | 0.0.0.0/0 | | insforge-postgrest | Ingress | insforge-server | tcp:5430 | 0.0.0.0/0 | | insforge-postgres | Ingress | insforge-server | tcp:5432 | 0.0.0.0/0 (only if needed externally) | > ⚠️ **Security Note**: For production, restrict PostgreSQL (5432) to specific IP addresses or remove external access entirely. Consider using a reverse proxy (nginx) and exposing only ports 80/443. ### 2. Connect to Your Compute Engine Instance 1. In the Google Cloud Console, go to **Compute Engine** → **VM instances** 2. Find your instance and click the **SSH** button in the same row, or: ```bash theme={null} # Use gcloud CLI to SSH (if you have gcloud SDK installed locally) gcloud compute ssh insforge-server --zone=your-zone ``` ### 3. Install Dependencies #### 3.1 Update System Packages ```bash theme={null} sudo apt update && sudo apt upgrade -y ``` #### 3.2 Install Docker ```bash theme={null} # Add Docker's official GPG key sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add Docker repository echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` #### 3.3 Add Your User to Docker Group After installing Docker, you need to add your user to the `docker` group to run Docker commands without `sudo`: ```bash theme={null} # Add your user to the docker group sudo usermod -aG docker $USER # Apply the group changes newgrp docker ``` **Verify it works:** ```bash theme={null} # This should now work without sudo docker ps ``` > 💡 **Note**: If `docker ps` doesn't work immediately, log out and log back in via SSH, then try again. > ⚠️ **Security Note**: Adding a user to the `docker` group grants them root-equivalent privileges on the system. This is acceptable for single-user environments like your Compute Engine instance, but be cautious on shared systems. #### 3.4 Install Git ```bash theme={null} sudo apt install git -y ``` ### 4. Deploy InsForge #### 4.1 Get the Repository ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge ``` Checks out the files the stack reads and generates `JWT_SECRET`, `ENCRYPTION_KEY`, `ROOT_ADMIN_PASSWORD` and `POSTGRES_PASSWORD` into `.env`. Nothing is started. #### 4.2 Create Environment Configuration ```bash theme={null} cd ~/insforge nano .env ``` The secrets are already generated — leave them as they are. Set the URL browsers will use: ```env theme={null} API_BASE_URL=http://:7130 VITE_API_BASE_URL=http://:7130 ``` Optional, all off by default: ```env theme={null} OPENROUTER_API_KEY= # AI features VERCEL_TOKEN= # site deployments GOOGLE_CLIENT_ID= # OAuth providers GOOGLE_CLIENT_SECRET= ``` `.env.example` carries every remaining variable with its defaults. > 💡 Back up `.env` somewhere safe. Its secrets are what let you migrate or restore this instance. #### 4.3 Start InsForge Services ```bash theme={null} # Pull Docker images and start services docker compose up -d # View logs to ensure everything started correctly docker compose logs -f ``` Press `Ctrl+C` to exit log view. #### 4.4 Verify Services ```bash theme={null} # Check running containers docker compose ps # You should see 4 running services: # - postgres # - postgrest # - insforge # - deno ``` ### 5. Access Your InsForge Instance #### 5.1 Test Backend API ```bash theme={null} curl http://your-external-ip:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "2.1.7", "service": "Insforge OSS Backend", "timestamp": "2025-10-17T..." } ``` #### 5.2 Access Dashboard Open your browser and navigate to: ```text theme={null} http://your-external-ip:7130 ``` ### 6. Configure Domain (Optional but Recommended) #### 6.1 Reserve a Static External IP 1. In Google Cloud Console, go to **VPC network** → **External IP addresses** 2. Click **Reserve Static Address** 3. **Name**: `insforge-ip` 4. **Type**: Regional or Global (Regional for VM instances) 5. **Region**: Same as your VM instance 6. **Click Reserve** #### 6.2 Update DNS Records Point your domain's DNS records to the reserved static IP: ```text theme={null} api.yourdomain.com → your-static-external-ip app.yourdomain.com → your-static-external-ip ``` #### 6.3 Install Nginx Reverse Proxy ```bash theme={null} sudo apt install nginx -y ``` Create Nginx configuration: ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Add the following configuration: ```nginx theme={null} # Backend API server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } # Dashboard server { listen 80; server_name app.yourdomain.com; location / { proxy_pass http://localhost:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } ``` Enable the configuration: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` #### 6.4 Install SSL Certificate (Recommended) ```bash theme={null} # Install Certbot sudo apt install certbot python3-certbot-nginx -y # Obtain SSL certificates sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com # Follow the prompts to complete setup ``` Update your `.env` file with HTTPS URLs: ```bash theme={null} cd ~/insforge nano .env ``` Change: ```env theme={null} API_BASE_URL=https://api.yourdomain.com VITE_API_BASE_URL=https://api.yourdomain.com ``` Restart services: ```bash theme={null} docker compose down docker compose up -d ``` ## 🔧 Management & Maintenance ### View Logs ```bash theme={null} # All services docker compose logs -f # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` ### Stop Services ```bash theme={null} docker compose down ``` ### Restart Services ```bash theme={null} docker compose restart ``` ### Update InsForge ```bash theme={null} cd ~/insforge git pull origin main # Pick up any files this release added to the sparse checkout sh deploy/setup.sh . docker compose pull && docker compose up -d ``` ### Backup Database ```bash theme={null} # Create backup (run from ~/insforge) docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql # Store backup in Google Cloud Storage (optional) # First, install Google Cloud CLI and authenticate # Then: gsutil cp backup_$(date +%Y%m%d_%H%M%S).sql gs://your-backup-bucket/ ``` ### Monitor Resources ```bash theme={null} # Check disk usage df -h # Check memory usage free -h # Check Docker stats docker stats ``` ## 🐛 Troubleshooting ### Services Won't Start ```bash theme={null} # Check logs for errors docker compose logs # Check disk space df -h # Check memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### Cannot Connect to Database ```bash theme={null} # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres # Verify credentials in .env file cat .env | grep POSTGRES ``` ### Port Already in Use ```bash theme={null} # Check what's using the port sudo netstat -tulpn | grep :7130 # Kill the process or change port in docker-compose.yml ``` ### Out of Memory Consider upgrading to a larger instance type: ```text theme={null} - Current: e2-small (2 vCPU, 2 GB RAM) - Upgrade to: e2-standard-2 (2 vCPU, 8 GB RAM) ``` ### SSL Certificate Issues ```bash theme={null} # Renew certificates sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ## 📊 Performance Optimization ### For Production Workloads 1. **Upgrade Instance Type**: Use `e2-standard-2` or `e2-standard-4` 2. **Use Cloud SQL**: Migrate from containerized PostgreSQL to Google Cloud SQL for better reliability 3. **Enable Cloud Monitoring**: Monitor metrics and set up alerts 4. **Configure Backups**: Set up automated daily backups 5. **Use Cloud Storage**: Configure Google Cloud Storage for file uploads instead of local storage ### Database Optimization ```conf theme={null} # Increase PostgreSQL shared_buffers (edit postgresql.conf in deploy/docker-init/db/) # Recommended: 25% of available RAM shared_buffers = 1GB effective_cache_size = 3GB ``` ## 🔒 Security Best Practices 1. **Change Default Passwords**: Update admin and database passwords 2. **Enable Firewall**: Use Google Cloud Firewall rules effectively 3. **Regular Updates**: Keep system and Docker images updated 4. **SSL/TLS**: Always use HTTPS in production 5. **Backup Regularly**: Automate database backups 6. **Monitor Logs**: Set up log monitoring and alerts 7. **Limit SSH Access**: Restrict SSH to specific IP addresses 8. **Use Service Accounts**: Instead of API keys where possible ## 🆘 Support & Resources * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) ## 📝 Cost Estimation **Monthly Google Cloud Costs (approximate):** | Component | Type | Monthly Cost | | ----------------------- | ---------------------------- | ---------------- | | Compute Engine | e2-medium (2 vCPU, 4 GB RAM) | \~\$29 | | Persistent Disk (30 GB) | Standard | \~\$3 | | Network Egress | First 1GB free | Variable | | **Total** | | **\~\$32/month** | > 💡 **Cost Optimization**: Use sustained use discounts for 24/7 running instances to save up to 30%. Consider preemptible instances for development/testing environments. *** **Congratulations! 🎉** Your InsForge instance is now running on Google Cloud Compute Engine. You can start building applications by connecting AI agents to your backend platform. For other production deployment strategies, check out our [deployment guides](/deployment/deployment-security-guide). # Self-Host InsForge on Hetzner Cloud Source: https://docs.insforge.dev/deployment/deploy-to-hetzner Step-by-step guide to self-host the InsForge platform on a Hetzner Cloud VPS using Docker Compose, including firewall setup, domain config, and TLS termination. # Self-Host InsForge on Hetzner Cloud This guide walks through self-hosting the InsForge platform on a [Hetzner Cloud](https://www.hetzner.com/cloud) server using Docker Compose. **This deploys InsForge itself, not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. This guide is for running the InsForge backend on your own infrastructure. This cloud walkthrough is community-maintained and can lag the latest InsForge release. The canonical, always-current setup is the `deploy/docker-compose/` directory in the [InsForge repo](https://github.com/InsForge/InsForge). ## 📋 Prerequisites * A [Hetzner Cloud](https://console.hetzner.cloud/) account and project * An SSH key added to your Hetzner account **before** you create the server ([Hetzner docs](https://docs.hetzner.com/cloud/servers/getting-started/creating-a-server)) * Basic familiarity with SSH and the command line * A domain name (optional, but recommended for HTTPS in production) ## 🚀 Deployment Steps ### 1. Create a Hetzner Cloud Server 1. Open the [Hetzner Console](https://console.hetzner.cloud/), select your project, and go to **Servers** → **Add Server**. 2. Configure the server: | Setting | Recommendation | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Location** | Any region that fits your users. EU locations include Falkenstein (`FSN1`), Nuremberg (`NBG1`), and Helsinki (`HEL1`). | | **Image** | **Ubuntu 24.04** | | **Type** | **CX23** (2 vCPU, 4 GB RAM, 40 GB disk) for testing, or **CX33** (4 vCPU, 8 GB RAM, 80 GB disk) for production. These are Hetzner [Cost-Optimized](https://www.hetzner.com/cloud/cost-optimized/) plans on shared x86 CPUs. | | **Networking** | Enable a **Primary IPv4** address. IPv6 is optional and free. | | **SSH key** | Select the key you uploaded earlier. | | **Name** | e.g. `insforge-server` | 3. Optional add-ons: * **Backups** — daily automatic disk snapshots with seven rotating slots ([Hetzner docs](https://docs.hetzner.com/cloud/servers/getting-started/enabling-backups)) * **Firewall** — you can attach one now or create it in the next step 4. Click **Create & Buy now**. > 💡 **Plan note:** Hetzner also offers ARM-based **CAX** servers. InsForge publishes multi-arch images, but this guide assumes **CX** (x86) unless you have verified every container image pulls on your plan. > 💡 **Pricing note:** Server prices depend on location and plan. A Primary IPv4 address is billed separately ([€0.50/month excluding VAT](https://docs.hetzner.com/cloud/servers/primary-ips/overview)). See [Hetzner Cloud pricing](https://www.hetzner.com/cloud) for current rates. ### 2. Configure a Hetzner Cloud Firewall Hetzner Cloud Firewalls are free and filter traffic before it reaches your server ([overview](https://docs.hetzner.com/cloud/firewalls/getting-started/creating-a-firewall)). 1. In the console, go to **Firewalls** → **Create Firewall**. 2. Add **inbound** rules: | Protocol | Port | Sources | Purpose | | -------- | ---- | ------------------- | ------------------------------------------------------------------------ | | TCP | 22 | Your IP address | SSH | | TCP | 80 | Any IPv4 / Any IPv6 | HTTP (for HTTPS redirect) | | TCP | 443 | Any IPv4 / Any IPv6 | HTTPS (reverse proxy) | | TCP | 7130 | Any IPv4 / Any IPv6 | Optional — direct API/dashboard access before you set up a reverse proxy | 3. Attach the firewall to your server under **Apply to**. 4. Click **Create Firewall**. > ⚠️ **Do not open** ports 5432, 5430, or 7133. In the self-host compose file, PostgreSQL, PostgREST, and Deno bind to `127.0.0.1` on the host and are not meant to be reached from the internet. For production, put Nginx or Caddy in front of InsForge on port 443 and stop exposing 7130 publicly — see [Configure Domain](#6-configure-domain-optional-but-recommended) below and the [deployment security guide](/deployment/deployment-security-guide). ### 3. Connect to Your Server Hetzner servers use `root` as the default SSH user ([connecting docs](https://docs.hetzner.com/cloud/servers/getting-started/connecting-to-the-server)): ```bash theme={null} ssh root@ ``` Copy the IPv4 address from the server overview in the Hetzner Console. ### 4. Install Dependencies #### 4.1 Update System Packages ```bash theme={null} apt update && apt upgrade -y ``` #### 4.2 Install Docker Follow Docker's official Ubuntu install guide: ```text theme={null} https://docs.docker.com/engine/install/ubuntu/ ``` Install the Docker Engine and the **Compose plugin** (`docker-compose-plugin`). Verify: ```bash theme={null} docker --version docker compose version ``` #### 4.3 Install Git Git is required for the update path after the initial install: ```bash theme={null} apt install git -y ``` > 💡 **Shortcut:** Hetzner offers a [Docker CE app](https://docs.hetzner.com/cloud/apps/list/docker-ce/) that preinstalls Docker and the Compose plugin on Ubuntu 24.04. You can select it instead of a plain Ubuntu image if you prefer; the rest of this guide is the same. ### 5. Deploy InsForge #### 5.1 Fetch the Self-Host Files ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge ``` This sparse-checkouts the files the stack reads and writes `JWT_SECRET`, `ENCRYPTION_KEY`, `ROOT_ADMIN_PASSWORD`, `POSTGRES_PASSWORD`, and the API keys into `~/insforge/.env` (mode `600`). Nothing is started yet. > Rather not pipe a script into a shell? Read it first: > > ```bash theme={null} > curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh -o setup.sh > less setup.sh > sh setup.sh ~/insforge > ``` #### 5.2 Configure Environment ```bash theme={null} cd ~/insforge nano .env ``` The secrets are already generated — leave them as they are. Set the URL browsers will use: ```env theme={null} API_BASE_URL=http://:7130 VITE_API_BASE_URL=http://:7130 ``` Optional integrations (all off by default): ```env theme={null} OPENROUTER_API_KEY= # AI features VERCEL_TOKEN= # site deployments GOOGLE_CLIENT_ID= # OAuth providers GOOGLE_CLIENT_SECRET= ``` See `.env.example` for every supported variable. > 💡 Back up `.env` somewhere safe. You need those secrets to migrate or restore this instance. #### 5.3 Start Services ```bash theme={null} docker compose up -d docker compose logs -f ``` Press `Ctrl+C` to exit the log view. #### 5.4 Verify Services ```bash theme={null} docker compose ps ``` You should see four services — `postgres`, `postgrest`, `insforge`, and `deno`. Postgres and Deno report `healthy` when their health checks pass; PostgREST has no health check in this compose file and shows `running`. ### 6. Access Your InsForge Instance #### 6.1 Test the API ```bash theme={null} curl http://:7130/api/health ``` You should get JSON with `"status": "ok"` and `"service": "Insforge OSS Backend"`. #### 6.2 Open the Dashboard In your browser: ```text theme={null} http://:7130 ``` Log in with `ROOT_ADMIN_USERNAME` and `ROOT_ADMIN_PASSWORD` from `.env`. ### 7. Configure Domain (Optional but Recommended) #### 7.1 DNS Point a DNS **A record** at your server's IPv4 address: ```text theme={null} insforge.yourdomain.com → ``` If you use a [Floating IP](https://docs.hetzner.com/cloud/floating-ips/overview) instead of the server's Primary IP, point DNS at the floating address so you can move it between servers later. #### 7.2 Reverse Proxy and TLS Install Nginx: ```bash theme={null} apt install nginx -y ``` Create a site config: ```bash theme={null} nano /etc/nginx/sites-available/insforge ``` ```nginx theme={null} server { listen 80; listen [::]:80; server_name insforge.yourdomain.com; location / { proxy_pass http://127.0.0.1:7130; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } ``` Enable it: ```bash theme={null} ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ nginx -t systemctl reload nginx ``` Obtain a certificate with Certbot: ```bash theme={null} apt install certbot python3-certbot-nginx -y certbot --nginx -d insforge.yourdomain.com ``` Update `.env` with your HTTPS URL: ```env theme={null} API_BASE_URL=https://insforge.yourdomain.com VITE_API_BASE_URL=https://insforge.yourdomain.com ``` Restart InsForge: ```bash theme={null} cd ~/insforge docker compose down docker compose up -d ``` Remove the firewall rule for port **7130** once HTTPS works, so traffic only enters on 443. For Caddy, UFW, SSH hardening, and more detail, see the [deployment security guide](/deployment/deployment-security-guide). ## 🔧 Management & Maintenance ### View Logs ```bash theme={null} cd ~/insforge docker compose logs -f docker compose logs -f insforge ``` ### Stop or Restart ```bash theme={null} docker compose down docker compose restart ``` ### Update InsForge The stack reads Postgres configuration and Deno function sources from this checkout, so updates are more than an image pull: ```bash theme={null} cd ~/insforge git pull origin main sh deploy/setup.sh . docker compose pull && docker compose up -d ``` ### Backup Database ```bash theme={null} cd ~/insforge docker compose exec postgres pg_dump -U postgres insforge > backup_$(date +%Y%m%d_%H%M%S).sql ``` Restore: ```bash theme={null} cat backup_file.sql | docker compose exec -T postgres psql -U postgres -d insforge ``` Hetzner **Backups** (if enabled) snapshot the whole disk. They complement — but do not replace — logical `pg_dump` backups. ### Monitor Resources ```bash theme={null} df -h free -h docker stats ``` ## 🐛 Troubleshooting ### Services Will Not Start ```bash theme={null} docker compose logs df -h free -h systemctl restart docker docker compose up -d ``` ### Cannot Reach the Dashboard * Confirm the Hetzner Firewall allows the port you are using (7130 or 443). * Check `API_BASE_URL` and `VITE_API_BASE_URL` match how you open the site in your browser. * Run `curl http://localhost:7130/api/health` on the server. If that works but the public URL does not, the issue is firewall or DNS — not InsForge. ### Out of Memory Resize to a larger plan in the Hetzner Console (**Rescale**), for example from **CX23** to **CX33**. ## 🔒 Security Best Practices 1. Restrict SSH (port 22) to your IP in the Hetzner Firewall. 2. Use HTTPS in production and stop exposing port 7130 publicly once a reverse proxy is in place. 3. Keep `.env` at mode `600` and back it up securely. 4. Run `apt upgrade` regularly and pull new InsForge images when you update. 5. See the [deployment security guide](/deployment/deployment-security-guide) for UFW, SSH hardening, and automated backups. ## 🆘 Support & Resources * **InsForge docs**: [https://docs.insforge.dev](https://docs.insforge.dev) * **Hetzner docs**: [https://docs.hetzner.com/cloud/](https://docs.hetzner.com/cloud/) * **GitHub Issues**: [https://github.com/InsForge/InsForge/issues](https://github.com/InsForge/InsForge/issues) * **Discord**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) ## 📝 Cost Notes Hetzner bills each server hourly with a monthly price cap. Prices vary by **plan** and **location**. In addition to the server: * **Primary IPv4** — [€0.50/month excluding VAT](https://docs.hetzner.com/cloud/servers/primary-ips/overview) per address * **Backups** — optional add-on at checkout * **Outgoing traffic** — EU Cost-Optimized plans include [20 TB/month](https://docs.hetzner.com/robot/general/traffic/); only outbound traffic counts toward the quota Check [hetzner.com/cloud](https://www.hetzner.com/cloud) for current plan prices before you deploy. *** **Congratulations!** Your InsForge instance is running on Hetzner Cloud. For hardening, backups, and rollback procedures, see the [deployment security guide](/deployment/deployment-security-guide). # VPS deployment and security guide Source: https://docs.insforge.dev/deployment/deployment-security-guide Deploy InsForge on a generic Linux VPS, harden it with firewall, SSH, and TLS best practices, and maintain it with safe updates and rollbacks. # Deployment & Security Guide for VPS Installation **This deploys the InsForge platform itself onto your own server (self-hosting), not the app you built.** If you just want to take your app live, use [Sites](/core-concepts/sites/overview) instead. Read on only if you want to run the InsForge backend on infrastructure you control. This comprehensive guide covers deploying InsForge on a generic VPS (Virtual Private Server) for production, hardening your instance with security best practices, and maintaining it over time with safe updates and rollback procedures. > **Scope**: This guide is provider-agnostic. It works on any Linux VPS — Ubuntu/Debian recommended — from providers such as DigitalOcean, Hetzner, Linode, Vultr, OVH, or a bare-metal server. For cloud-specific guides (AWS EC2, GCP, Azure, Render), see the other guides in this section. *** ## 📋 Table of Contents * [Prerequisites](#prerequisites) * [Part 1 — Deployment](#part-1--deployment) * [Server Requirements](#1-server-requirements) * [Initial Server Setup](#2-initial-server-setup) * [Install Docker & Docker Compose](#3-install-docker--docker-compose) * [Deploy InsForge with Docker Compose](#4-deploy-insforge-with-docker-compose) * [Environment Variable Configuration](#5-environment-variable-configuration) * [Reverse Proxy Setup](#6-reverse-proxy-setup) * [HTTPS / TLS Setup](#7-https--tls-setup) * [Part 2 — Security](#part-2--security) * [Port Management](#8-port-management) * [Firewall Setup (UFW)](#9-firewall-setup-ufw) * [Run Services as a Non-Root User](#10-run-services-as-a-non-root-user) * [SSH Hardening](#11-ssh-hardening) * [Docker Security](#12-docker-security) * [Secrets Management](#13-secrets-management) * [Part 3 — Updating & Maintenance](#part-3--updating--maintenance) * [Pre-Update Backup](#14-pre-update-backup) * [Updating InsForge](#15-updating-insforge) * [Rollback Procedure](#16-rollback-procedure) * [Automated Backups](#17-automated-backups) * [Monitoring & Health Checks](#18-monitoring--health-checks) * [Quick Reference](#quick-reference) * [Troubleshooting](#troubleshooting) *** ## Prerequisites Before starting, ensure you have: * A VPS running **Ubuntu 22.04 LTS** or **Ubuntu 24.04 LTS** (Debian 12 also works) * **Root or sudo access** to the server * A registered **domain name** (recommended for production) * Basic familiarity with the Linux command line and SSH *** ## Part 1 — Deployment ### 1. Server Requirements | Resource | Minimum | Recommended | | ----------- | ------------- | ------------------ | | **CPU** | 2 vCPU | 4 vCPU | | **RAM** | 2 GB | 4 GB+ | | **Storage** | 20 GB SSD | 40 GB+ SSD | | **OS** | Ubuntu 22.04+ | Ubuntu 24.04 LTS | | **Network** | Public IPv4 | Public IPv4 + IPv6 | > 💡 **Tip**: For production workloads with multiple users, start with 4 GB RAM. Monitor usage with `docker stats` and scale vertically as needed. InsForge consists of **4 services** that run together: | Service | Description | Internal Port | | -------------- | ----------------------------- | --------------------- | | **PostgreSQL** | Primary database | 5432 | | **PostgREST** | Auto-generated REST API layer | 3000 (mapped to 5430) | | **InsForge** | Node.js backend + dashboard | 7130 | | **Deno** | Serverless functions runtime | 7133 | *** ### 2. Initial Server Setup #### 2.1 Connect to Your VPS ```bash theme={null} ssh root@your-server-ip ``` #### 2.2 Update System Packages ```bash theme={null} apt update && apt upgrade -y ``` #### 2.3 Create a Deploy User (Non-Root) Never run production services as root. Create a dedicated user: ```bash theme={null} # Create the deploy user and add to sudo group adduser deploy usermod -aG sudo deploy # Switch to the deploy user su - deploy ``` #### 2.4 Set the Timezone ```bash theme={null} sudo timedatectl set-timezone UTC ``` #### 2.5 Enable Automatic Security Updates ```bash theme={null} sudo apt install unattended-upgrades -y sudo dpkg-reconfigure -plow unattended-upgrades ``` *** ### 3. Install Docker & Docker Compose #### 3.1 Install Docker Engine ```bash theme={null} # Add Docker's official GPG key sudo apt install ca-certificates curl gnupg -y sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the Docker repository echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y ``` #### 3.2 Add Deploy User to the Docker Group ```bash theme={null} sudo usermod -aG docker deploy newgrp docker ``` #### 3.3 Verify Docker Installation ```bash theme={null} docker --version docker compose version docker run hello-world ``` > ⚠️ **Security Note**: Adding a user to the `docker` group grants root-equivalent privileges on the host. This is acceptable for a dedicated deploy user but should not be done for general-purpose accounts on shared servers. *** ### 4. Deploy InsForge with Docker Compose #### 4.1 Get the Repository ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh | sh -s ~/insforge ``` Checks out the files the stack reads and generates `JWT_SECRET`, `ENCRYPTION_KEY`, `ROOT_ADMIN_PASSWORD` and `POSTGRES_PASSWORD` into `.env`. Nothing is started. > Rather not pipe a script into a shell? Read it first: > > ```bash theme={null} > curl -fsSL https://raw.githubusercontent.com/InsForge/InsForge/main/deploy/setup.sh -o setup.sh > less setup.sh > sh setup.sh ~/insforge > ``` #### 4.2 Start InsForge ```bash theme={null} cd ~/insforge docker compose up -d ``` #### 4.3 Verify All Services Are Running ```bash theme={null} docker compose ps ``` You should see 4 containers in a `running` or `healthy` state: ```text theme={null} NAME SERVICE STATUS insforge insforge running postgres postgres healthy postgrest postgrest healthy deno deno running ``` #### 4.4 Test the Health Endpoint ```bash theme={null} curl http://localhost:7130/api/health ``` Expected response: ```json theme={null} { "status": "ok", "version": "1.x.x", "service": "Insforge OSS Backend", "timestamp": "2026-..." } ``` *** ### 5. Environment Variable Configuration Edit your `.env` file to configure InsForge for production: ```bash theme={null} nano ~/insforge/.env ``` #### 5.1 Required Variables These **must** be changed from defaults before going to production: ```env theme={null} # ── Security (CRITICAL — generate unique values) ────────────── JWT_SECRET= ENCRYPTION_KEY= ROOT_ADMIN_USERNAME=admin ROOT_ADMIN_PASSWORD= # ── Public URL (must match your domain/IP) ──────────────────── API_BASE_URL=https://insforge.yourdomain.com VITE_API_BASE_URL=https://insforge.yourdomain.com ``` Generate secure secrets right from the terminal: ```bash theme={null} # JWT secret (32+ characters) openssl rand -base64 32 # Encryption key (separate from JWT_SECRET) openssl rand -base64 24 # Admin password openssl rand -base64 18 ``` > ⚠️ **Important**: `JWT_SECRET` and `ENCRYPTION_KEY` should be **different** values. If `ENCRYPTION_KEY` is not set, InsForge falls back to `JWT_SECRET` — but rotating `JWT_SECRET` later will permanently corrupt all stored secrets (API keys, OAuth tokens, etc.). #### 5.2 Database Variables `setup.sh` already generated `POSTGRES_PASSWORD`. Postgres reads it only when it initializes the cluster, so changing it after 4.2 has started the stack does not change the database password — leave it alone unless you are setting up for the first time and have not started anything yet. ```env theme={null} POSTGRES_USER=postgres POSTGRES_DB=insforge ``` #### 5.3 Port Variables Default ports used by InsForge: ```env theme={null} POSTGRES_PORT=5432 POSTGREST_PORT=5430 APP_PORT=7130 AUTH_PORT=7131 DENO_PORT=7133 ``` > 💡 You can change these if they conflict with other services on your VPS. `COMPOSE_PROJECT_NAME` prefixes every container, volume and network: ```env theme={null} COMPOSE_PROJECT_NAME=insforge ``` > ⚠️ Give a second instance on the same host its own value, along with its own ports. Two `.env` files sharing this name means `docker compose up` in one of them adopts and recreates the other's containers. #### 5.4 Required for Deployments These variables are only needed if you plan to use InsForge's **deployment features** (deploying projects via the dashboard). If you don't need deployments, skip this section. ```env theme={null} # ── Deployments ────────────────────────────────────────────── # Project ID used by OpenRouter AI token renewal and Vercel deployments PROJECT_ID=your-project-id ``` > ⚠️ `deploy/docker-compose/docker-compose.yml` does not pass `PROJECT_ID` through to the `insforge` container. Add it to that service's `environment` block to use it. Legacy zip uploads to `POST /api/deployments` also need an S3 bucket — configure it with the `S3_*` variables in 5.5. #### 5.5 Optional Variables ```env theme={null} # ── OAuth Providers ─────────────────────────────────────────── GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_SECRET= DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= LINKEDIN_CLIENT_ID= LINKEDIN_CLIENT_SECRET= X_CLIENT_ID= X_CLIENT_SECRET= APPLE_CLIENT_ID= APPLE_CLIENT_SECRET= # ── AI / LLM ───────────────────────────────────────────────── OPENROUTER_API_KEY= # ── Storage (S3-compatible — leave empty for local storage) ── # For general file storage only (not deployments). If omitted, local # filesystem storage is used automatically. See the "Self-hosted storage" # guide for bring-your-own S3, bundled MinIO/RustFS overlays, and the # S3-compatible gateway. S3_BUCKET= S3_REGION= S3_ACCESS_KEY_ID= S3_SECRET_ACCESS_KEY= # Custom S3-compatible endpoint (MinIO, RustFS, Wasabi, R2, COS, OSS ...); # leave empty for AWS S3 S3_ENDPOINT_URL= S3_FORCE_PATH_STYLE=true # Set to false to proxy object bytes through the backend (required when the # endpoint is not reachable by browsers, e.g. a bundled store on the Docker # network). If you run the MinIO/RustFS overlay, CHANGE its default # credentials (MINIO_ROOT_USER/MINIO_ROOT_PASSWORD or # RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY) before production use. S3_USE_PRESIGNED_URLS= # Max single S3-gateway upload in bytes (default 5368709120 = 5GB) S3_MAX_OBJECT_SIZE_BYTES= # ── Deno Functions ──────────────────────────────────────────── WORKER_TIMEOUT_MS=60000 ``` After editing, restart services to apply changes: ```bash theme={null} cd ~/insforge docker compose down docker compose up -d ``` *** ### 6. Reverse Proxy Setup A reverse proxy sits in front of InsForge, providing TLS termination, HTTP/2, and a clean URL without port numbers. #### Option A: Nginx (Recommended) ##### 6.1 Install Nginx ```bash theme={null} sudo apt install nginx -y ``` ##### 6.2 Create the Site Configuration ```bash theme={null} sudo nano /etc/nginx/sites-available/insforge ``` Paste the following configuration — replace `insforge.yourdomain.com` with your actual domain: ```nginx theme={null} # ── InsForge Backend + Dashboard ────────────────────────────── server { listen 80; listen [::]:80; server_name insforge.yourdomain.com; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Max upload size (match MAX_FILE_SIZE in .env, default 50 MB) client_max_body_size 50M; location / { proxy_pass http://127.0.0.1:7130; proxy_http_version 1.1; # WebSocket support (required for Realtime features) proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # Timeouts for long-running requests (e.g., AI completions) proxy_read_timeout 120s; proxy_send_timeout 120s; } } ``` ##### 6.3 Enable the Site ```bash theme={null} sudo ln -s /etc/nginx/sites-available/insforge /etc/nginx/sites-enabled/ # Remove the default site (optional) sudo rm -f /etc/nginx/sites-enabled/default # Test and reload sudo nginx -t sudo systemctl reload nginx ``` #### Option B: Caddy (Automatic HTTPS) Caddy is a simpler alternative that handles TLS certificates automatically. ##### Install Caddy ```bash theme={null} sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddy -y ``` ##### Configure Caddy ```bash theme={null} sudo nano /etc/caddy/Caddyfile ``` ```caddyfile theme={null} insforge.yourdomain.com { reverse_proxy localhost:7130 header { X-Frame-Options "SAMEORIGIN" X-Content-Type-Options "nosniff" X-XSS-Protection "1; mode=block" Referrer-Policy "strict-origin-when-cross-origin" } request_body { max_size 50MB } } ``` ```bash theme={null} sudo systemctl reload caddy ``` Caddy will automatically obtain and renew Let's Encrypt certificates — no extra steps needed. *** ### 7. HTTPS / TLS Setup > If you chose **Caddy** in Step 6, TLS is already handled automatically. Skip to [Part 2](#part-2--security). #### 7.1 Install Certbot (for Nginx) ```bash theme={null} sudo apt install certbot python3-certbot-nginx -y ``` #### 7.2 Obtain SSL Certificates ```bash theme={null} sudo certbot --nginx -d insforge.yourdomain.com ``` Follow the interactive prompts. Certbot will: 1. Verify domain ownership via HTTP challenge 2. Obtain a signed certificate from Let's Encrypt 3. Automatically update your Nginx configuration to serve HTTPS 4. Set up HTTP → HTTPS redirect #### 7.3 Verify Auto-Renewal Let's Encrypt certificates expire every 90 days. Certbot installs a systemd timer for automatic renewal: ```bash theme={null} # Test renewal (dry run — no actual renewal) sudo certbot renew --dry-run # Check the timer is active sudo systemctl status certbot.timer ``` #### 7.4 Update InsForge Environment for HTTPS After obtaining your certificate, update your `.env` to use HTTPS URLs: ```bash theme={null} cd ~/insforge nano .env ``` ```env theme={null} API_BASE_URL=https://insforge.yourdomain.com VITE_API_BASE_URL=https://insforge.yourdomain.com ``` Restart InsForge to apply: ```bash theme={null} docker compose down docker compose up -d ``` *** ## Part 2 — Security ### 8. Port Management #### Ports That Should Be Open (via Reverse Proxy) | Port | Protocol | Purpose | | ---- | -------- | ------------------------ | | 22 | TCP | SSH (restrict source IP) | | 80 | TCP | HTTP → HTTPS redirect | | 443 | TCP | HTTPS (reverse proxy) | #### Ports That Should Be Closed to the Public These ports are used **only** for internal Docker service-to-service communication. They should **never** be exposed to the internet: | Port | Service | Why Close It | | ---- | ---------- | ---------------------------------------------------------------- | | 5432 | PostgreSQL | Direct DB access — use `docker exec` instead | | 5430 | PostgREST | Internal REST layer — proxied through InsForge | | 7130 | InsForge | API + dashboard, accessed via reverse proxy on 443, not directly | | 7131 | (unused) | Published by compose (`AUTH_PORT`), but no process listens on it | | 7133 | Deno | Internal serverless runtime | > ⚠️ **Critical**: The default `docker-compose.yml` binds ports to `0.0.0.0` (all interfaces), **not** `127.0.0.1`. This means Docker will expose services directly to the internet, **bypassing UFW entirely** (Docker manipulates iptables directly). You **MUST** add the `127.0.0.1:` prefix to every published port in your `docker-compose.yml`: > > ```yaml theme={null} > ports: > - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" # PostgreSQL > - "127.0.0.1:${POSTGREST_PORT:-5430}:3000" # PostgREST > - "127.0.0.1:${APP_PORT:-7130}:7130" # InsForge (API + dashboard) > - "127.0.0.1:${AUTH_PORT:-7131}:7131" # AUTH_PORT (published by compose, unused) > - "127.0.0.1:${DENO_PORT:-7133}:7133" # Deno > ``` > > Without this prefix, anyone on the internet can reach these services directly — including PostgreSQL with default credentials. See [Section 9.2](#92-docker-and-ufw-caveat) for details. *** ### 9. Firewall Setup (UFW) UFW (Uncomplicated Firewall) is the simplest way to manage iptables on Ubuntu. #### 9.1 Install and Configure UFW ```bash theme={null} # Install UFW (usually pre-installed on Ubuntu) sudo apt install ufw -y # Default policy: deny all incoming, allow all outgoing sudo ufw default deny incoming sudo ufw default allow outgoing # Allow SSH (CRITICAL — do this BEFORE enabling UFW!) sudo ufw allow OpenSSH # Allow HTTP and HTTPS (for reverse proxy) sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Enable the firewall sudo ufw enable # Verify rules sudo ufw status verbose ``` Expected output: ```text theme={null} Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere ``` > ⚠️ **Critical**: Always allow SSH **before** enabling UFW, or you will lock yourself out of the server. #### 9.2 Docker and UFW Caveat Docker manipulates iptables directly, which can **bypass UFW rules**. To prevent this: **Option 1 — Bind ports to localhost** (recommended): In your `docker-compose.yml`, prefix ports with `127.0.0.1:`: ```yaml theme={null} ports: - "127.0.0.1:7130:7130" - "127.0.0.1:7131:7131" ``` **Option 2 — Disable Docker's iptables management**: ```bash theme={null} sudo nano /etc/docker/daemon.json ``` ```json theme={null} { "iptables": false } ``` ```bash theme={null} sudo systemctl restart docker ``` > ⚠️ Disabling Docker iptables requires manual network configuration. **Option 1 is preferred** for most setups. #### 9.3 Restrict SSH to Your IP (Optional) For maximum security, restrict SSH access to a known IP address: ```bash theme={null} # Remove the broad SSH rule sudo ufw delete allow OpenSSH # Allow SSH only from your IP sudo ufw allow from YOUR_IP_ADDRESS to any port 22 proto tcp # Verify sudo ufw status ``` *** ### 10. Run Services as a Non-Root User InsForge's Docker image already follows non-root best practices: * The production Dockerfile sets `USER node` (UID 1000), so the application process inside the container runs as a non-root user. * System-level Docker operations are managed by the `deploy` user (created in [Step 2.3](#23-create-a-deploy-user-non-root)), which has access to the Docker socket via the `docker` group. **Verify the container user:** ```bash theme={null} docker compose exec insforge whoami # Expected output: node ``` **Additional hardening:** Add `security_opt` to each service in your `docker-compose.yml` to prevent privilege escalation: ```yaml theme={null} # Add to each service in docker-compose.yml security_opt: - no-new-privileges:true ``` *** ### 11. SSH Hardening #### 11.1 Use SSH Key Authentication ```bash theme={null} # On your LOCAL machine — generate a key pair if you don't have one ssh-keygen -t ed25519 -C "deploy@insforge" # Copy the public key to your server ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip ``` #### 11.2 Disable Password Authentication Once key-based auth is confirmed working: ```bash theme={null} sudo nano /etc/ssh/sshd_config ``` Set the following: ```ini theme={null} PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 ``` Restart SSH: ```bash theme={null} sudo systemctl restart sshd ``` #### 11.3 Install Fail2Ban Fail2Ban automatically bans IPs that show malicious activity (e.g., brute-force SSH): ```bash theme={null} sudo apt install fail2ban -y # Create a local config (survives updates) sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local sudo nano /etc/fail2ban/jail.local ``` Add or ensure these settings are present: ```ini theme={null} [sshd] enabled = true port = ssh filter = sshd maxretry = 5 bantime = 3600 findtime = 600 ``` ```bash theme={null} sudo systemctl enable fail2ban sudo systemctl restart fail2ban # Check banned IPs sudo fail2ban-client status sshd ``` *** ### 12. Docker Security #### 12.1 Keep Docker Updated ```bash theme={null} sudo apt update sudo apt upgrade docker-ce docker-ce-cli containerd.io -y ``` #### 12.2 Limit Container Resources (Optional) Prevent a single container from consuming all resources: ```yaml theme={null} # Add to any service in docker-compose.yml deploy: resources: limits: memory: 2G cpus: '1.0' reservations: memory: 512M ``` #### 12.3 Read-Only Root Filesystem (Advanced) For extra hardening, mount the container filesystem as read-only where possible: ```yaml theme={null} read_only: true tmpfs: - /tmp ``` > ⚠️ This requires testing — some services need writable directories for caches or temporary files. #### 12.4 Restrict CORS Origins By default the backend allows all origins. It reflects the request's `Origin` header back in the response and, for function proxy responses, sets `Access-Control-Allow-Origin: *`. This is convenient for local development but too permissive for production. For a production deployment, restrict the allowed origins to the domains you actually serve (for example your dashboard and app domains), so other sites cannot make credentialed cross-origin requests to your API. *** ### 13. Secrets Management #### Do ✅ * Store secrets in the `.env` file with `chmod 600 ~/insforge/.env` * Use separate values for `JWT_SECRET` and `ENCRYPTION_KEY` * Generate secrets with `openssl rand -base64 32` * Back up your `.env` file to a secure, offline location #### Don't ❌ * Commit `.env` to version control * Reuse the same secret for multiple variables * Use default passwords (`change-this-password`, `postgres`) in production * Share secrets over unencrypted channels *** ## Part 3 — Updating & Maintenance ### 14. Pre-Update Backup **Always back up before updating.** This gives you a recovery path if anything goes wrong. #### 14.1 Back Up the Database For a database dump and `.env` copy in one step, use the shipped backup script: ```bash theme={null} cd ~/insforge ./deploy/backup.sh ``` Or dump manually: ```bash theme={null} cd ~/insforge source .env # Create a timestamped database backup docker compose exec -T postgres pg_dump \ -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-insforge}" \ > backup_$(date +%Y%m%d_%H%M%S).sql # Verify size is reasonable ls -lh backup_*.sql ``` #### 14.2 Back Up Environment and Volumes ```bash theme={null} # Back up .env file cp .env .env.backup_$(date +%Y%m%d) # Back up Docker volumes (optional but recommended) docker run --rm \ -v insforge_postgres-data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/volumes_postgres_$(date +%Y%m%d_%H%M%S).tar.gz /data ``` #### 14.3 Record Current Version ```bash theme={null} # Note the current image versions before updating docker compose images ``` *** ### 15. Updating InsForge #### 15.1 Update the Repository Update the checkout before pulling images: it carries the compose file and the Postgres config. ```bash theme={null} cd ~/insforge git fetch origin main git diff HEAD origin/main -- deploy functions .env.example git merge --ff-only origin/main # Pick up any files this release added sh deploy/setup.sh . ``` Review the diff before merging. New variables in `.env.example` have to be copied into your `.env` by hand. #### 15.2 Pull the Latest Images ```bash theme={null} cd ~/insforge # Pull the latest versions docker compose pull ``` #### 15.3 Apply the Update ```bash theme={null} # Stop current services, start with new images docker compose down docker compose up -d # Watch logs for errors during startup docker compose logs -f --tail=50 ``` Press `Ctrl+C` to stop following logs. #### 15.4 Verify the Update ```bash theme={null} # Check all services are healthy docker compose ps # Test the health endpoint curl http://localhost:7130/api/health # Check the version in the response ``` ### 16. Rollback Procedure If an update causes issues, follow these steps to revert: #### 16.1 Stop the Broken Services ```bash theme={null} cd ~/insforge docker compose down ``` #### 16.2 Pin the Previous Version 1. Write `pin.yml` next to your `.env`, naming the version 14.3 recorded: ```yaml theme={null} services: insforge: image: ghcr.io/insforge/insforge-oss:v2.2.9 ``` 2. Append it to `COMPOSE_FILE` in `.env`, keeping the entries already there: ```env theme={null} COMPOSE_FILE=deploy/docker-compose/docker-compose.yml:pin.yml ``` 3. `docker compose up -d` 4. Remove `:pin.yml` once you are back on a good release. Until you do, section 15's update pulls new images and keeps running the pinned one. #### 16.3 Restore the Database (If Needed) Only restore the database if the update included a database migration that caused issues: ```bash theme={null} cd ~/insforge source .env # Start only PostgreSQL docker compose up -d postgres # Wait for it to be healthy docker compose exec postgres pg_isready -U "${POSTGRES_USER:-postgres}" # Restore from backup cat backup_YYYYMMDD_HHMMSS.sql | \ docker compose exec -T postgres psql \ -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" # Start remaining services docker compose up -d ``` #### 16.4 Restore Environment File (If Changed) ```bash theme={null} cp .env.backup_YYYYMMDD .env docker compose down docker compose up -d ``` *** ### 17. Automated Backups Set up a cron job for daily automated backups. #### 17.1 Run the Backup Script Self-host installs include `deploy/backup.sh` (delivered by `deploy/setup.sh`). It dumps Postgres and copies `.env` into a `backups/` directory under your install root. ```bash theme={null} cd ~/insforge ./deploy/backup.sh ``` By default, backups land in `~/insforge/backups/` and files older than 14 days are removed. Override retention: ```bash theme={null} RETENTION_DAYS=30 ./deploy/backup.sh ``` Restore a database dump: ```bash theme={null} cd ~/insforge set -a && source .env && set +a cat backups/db_YYYYMMDD_HHMMSS.sql | docker compose exec -T postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" ``` #### 17.2 Schedule with Cron ```bash theme={null} crontab -e ``` Add this line for daily backups at 3:00 AM (adjust the path if your install lives elsewhere): ```cron theme={null} 0 3 * * * /home/deploy/insforge/deploy/backup.sh >> /home/deploy/insforge/backups/cron.log 2>&1 ``` #### 17.3 Off-Site Backups (Recommended) For disaster recovery, copy backups to an external location: ```bash theme={null} # Example: sync backups to S3-compatible storage aws s3 sync ~/insforge/backups s3://your-backup-bucket/insforge/ # Example: sync to a remote server rsync -avz ~/insforge/backups/ user@backup-server:/backups/insforge/ ``` *** ### 18. Monitoring & Health Checks #### 18.1 Check Service Status ```bash theme={null} # Container status docker compose ps # Resource usage per container docker stats --no-stream # Disk usage df -h # Memory usage free -h ``` #### 18.2 View Logs ```bash theme={null} # All services docker compose logs -f --tail=100 # Specific service docker compose logs -f insforge docker compose logs -f postgres docker compose logs -f deno ``` #### 18.3 Health Check Endpoint Monitor the health endpoint externally. A simple cron-based check: ```bash theme={null} # Add to crontab for monitoring */5 * * * * curl -sf https://insforge.yourdomain.com/api/health > /dev/null || echo "InsForge is DOWN" | mail -s "InsForge Alert" you@example.com ``` Or use a free uptime monitoring service like [UptimeRobot](https://uptimerobot.com) or [Betterstack](https://betterstack.com) to monitor `https://insforge.yourdomain.com/api/health`. *** ## Quick Reference ### Essential Commands ```bash theme={null} # ── Lifecycle ───────────────────────────────── docker compose up -d # Start all services docker compose down # Stop all services docker compose restart # Restart all services docker compose pull # Pull latest images # ── Diagnostics ─────────────────────────────── docker compose ps # Service status docker compose logs -f # Follow all logs docker compose logs -f insforge # Follow specific service docker stats --no-stream # Resource usage # ── Database (source .env first for vars) ──── source ~/insforge/.env ./deploy/backup.sh # Backup (db + .env) docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-insforge}" > backup.sql # Manual backup cat backup.sql | docker compose exec -T postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" # Restore # ── Updates ─────────────────────────────────── docker compose pull # Pull new images docker compose down && docker compose up -d # Apply update ``` ### Security Checklist * [ ] Deploy user created (non-root) * [ ] SSH key authentication enabled * [ ] SSH password authentication disabled * [ ] Root login disabled * [ ] UFW firewall enabled (ports 22, 80, 443 only) * [ ] Docker ports bound to `127.0.0.1` * [ ] Fail2Ban installed and active * [ ] `JWT_SECRET` changed from default (32+ chars) * [ ] `ENCRYPTION_KEY` set (separate from `JWT_SECRET`) * [ ] `ROOT_ADMIN_PASSWORD` changed from default * [ ] `POSTGRES_PASSWORD` changed from default * [ ] `.env` file permissions set to `600` * [ ] HTTPS enabled via Certbot or Caddy * [ ] Automated daily backups configured * [ ] Unattended security updates enabled *** ## Troubleshooting ### Cannot Connect After Enabling UFW If you're locked out, use your VPS provider's **web console** (out-of-band access) to: ```bash theme={null} sudo ufw allow OpenSSH sudo ufw enable ``` ### Docker Bypasses UFW Docker directly manipulates iptables. Bind ports to `127.0.0.1` in `docker-compose.yml` as described in [Section 9.2](#92-docker-and-ufw-caveat). ### Services Fail to Start ```bash theme={null} # Check logs for the failing service docker compose logs postgres docker compose logs insforge # Verify disk space df -h # Verify memory free -h # Restart Docker daemon sudo systemctl restart docker docker compose up -d ``` ### SSL Certificate Won't Renew ```bash theme={null} # Check Certbot timer sudo systemctl status certbot.timer # Manual renewal sudo certbot renew # Test renewal sudo certbot renew --dry-run ``` ### Port Conflicts ```bash theme={null} # Find what's using a port sudo ss -tlnp | grep :7130 # Change the port in .env APP_PORT=7140 ``` ### Database Connection Issues ```bash theme={null} # Check PostgreSQL is healthy docker compose ps postgres # View PostgreSQL logs docker compose logs postgres # Connect to the database directly docker compose exec postgres psql -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-insforge}" ``` *** ## 🆘 Need Help? * **Documentation**: [https://docs.insforge.dev](https://docs.insforge.dev) * **Discord Community**: [https://discord.com/invite/MPxwj5xVvW](https://discord.com/invite/MPxwj5xVvW) * **GitHub Issues**: [https://github.com/insforge/insforge/issues](https://github.com/insforge/insforge/issues) # Self-hosted storage backends Source: https://docs.insforge.dev/deployment/self-host-storage Configure InsForge Storage for self-hosting: bring your own S3-compatible store, run a bundled MinIO or RustFS, and enable the S3-compatible gateway. Self-hosted InsForge stores files on the **local filesystem** by default (`STORAGE_DIR`, a Docker volume). That works out of the box, but an S3 backend adds presigned or proxied transfers, multipart uploads, and — most importantly — enables the [S3-compatible gateway](/core-concepts/storage/s3-compatibility) at `/storage/v1/s3`, so `aws` CLI, rclone, boto3, and Terraform can talk to your InsForge Storage directly. You have three options: | Option | Setup | Best for | | ---------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------- | | Local filesystem | nothing (default) | Trying things out; the S3 gateway stays disabled | | Bring your own S3-compatible store | env vars only | Production with existing AWS S3, Wasabi, R2, Tencent COS, Aliyun OSS, a remote MinIO/RustFS/Garage/Ceph ... | | Bundled MinIO or RustFS | one compose overlay file | Production on a single host with no external dependencies | ## Bring your own S3-compatible store Set these in your `.env` (all production compose files pass them through to the backend) and restart: ```env theme={null} # Required — any S3-compatible store S3_BUCKET=my-insforge-bucket S3_REGION=us-east-1 S3_ACCESS_KEY_ID=... S3_SECRET_ACCESS_KEY=... # For non-AWS providers: add the endpoint. Leave empty for AWS S3 # (SDK default endpoints; credentials may also come from an IAM role). S3_ENDPOINT_URL=https://s3.my-provider.example S3_FORCE_PATH_STYLE=true ``` The legacy `AWS_S3_BUCKET` / `AWS_REGION` names still work as fallbacks, but the `S3_*` names are preferred — the store doesn't have to be AWS. Storage auto-wires to the bucket on startup — no other configuration. The S3 gateway turns on automatically. Provider notes: * **AWS S3, Wasabi, MinIO (public endpoint)** — works with the defaults. Clients upload/download via presigned URLs directly against the store. * **Cloudflare R2** — set `S3_USE_PRESIGNED_URLS=false`. R2 does not support S3 POST policies, which presigned browser uploads require; proxy mode routes all bytes through the backend instead. * **Tencent COS, Aliyun OSS** — set `S3_FORCE_PATH_STYLE=false` (they require virtual-hosted-style addressing). * **Store on a private network** (a MinIO/RustFS/Ceph the browser can't reach) — set `S3_USE_PRESIGNED_URLS=false`. ### Presigned vs. proxy mode `S3_USE_PRESIGNED_URLS` (default `true`) decides how object bytes reach clients: * **Presigned (default)**: the backend hands clients short-lived URLs signed against `S3_ENDPOINT_URL`; browsers transfer directly with the store. Requires the endpoint to be reachable by browsers and to support POST policies. * **Proxy (`false`)**: strategies point at the backend's own routes and every byte streams through it — the store can stay completely private. Ranged downloads (`Range` headers, media seeking) are supported. Uploads through the REST API/SDK are capped by the storage max-file-size setting (Dashboard → Storage → Settings, default 50 MB); larger objects go through the S3 gateway, which streams and supports multipart up to `S3_MAX_OBJECT_SIZE_BYTES` (default 5 GB per part/put). ## Bundled MinIO Run MinIO next to InsForge with a single overlay. In a `deploy/setup.sh` checkout, append it to `COMPOSE_FILE` in your `.env`: ```env theme={null} COMPOSE_FILE=deploy/docker-compose/docker-compose.yml:docker-compose.minio.yml ``` Then `docker compose up -d` as usual. Building from source instead: ```bash theme={null} docker compose -f docker-compose.prod.yml -f docker-compose.minio.yml up -d ``` The overlay starts `minio`, creates the backing bucket, and points the backend at it in proxy mode. MinIO stays on the internal Docker network — it exposes no host ports and never needs TLS or a domain of its own. Change the default credentials before production use: ```env theme={null} MINIO_ROOT_USER=your-user MINIO_ROOT_PASSWORD=a-long-random-secret ``` ## Bundled RustFS Same shape with [RustFS](https://rustfs.com) (Apache-2.0 licensed, written in Rust) — swap the filename: ```env theme={null} COMPOSE_FILE=deploy/docker-compose/docker-compose.yml:docker-compose.rustfs.yml ``` ```bash theme={null} docker compose -f docker-compose.prod.yml -f docker-compose.rustfs.yml up -d ``` ```env theme={null} RUSTFS_ACCESS_KEY=your-user RUSTFS_SECRET_KEY=a-long-random-secret ``` Switching stores does not migrate existing objects. Set the overlay before you upload anything, or move the objects yourself. Switching an existing deployment from local storage to an S3 backend does not migrate previously uploaded files. Migrate the contents of the `storage-data` volume (e.g. with `mc mirror` or `aws s3 sync`) before switching, or start fresh. ### Dokploy Dokploy takes a single compose file, so overlays don't apply. Two options: 1. **External store** — set `S3_BUCKET`, `S3_ENDPOINT_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_FORCE_PATH_STYLE=true` (and `S3_USE_PRESIGNED_URLS=false` for private endpoints) in Dokploy's environment UI. `deploy/dokploy/docker-compose.yml` passes them through. 2. **MinIO/RustFS on the same Dokploy host** — deploy the store as its own Dokploy service, then point the env vars above at its internal address. ## Using the S3-compatible gateway With any S3 backend configured, the gateway is live at `/storage/v1/s3`: 1. Open **Dashboard → Storage → Settings → S3 Configuration** and create an access key. The secret is shown once. 2. Point any SigV4 client at the gateway with path-style addressing: ```ini theme={null} # ~/.aws/credentials [insforge] aws_access_key_id = INSF... aws_secret_access_key = ... # ~/.aws/config [profile insforge] region = us-east-1 # must match the backend's S3_REGION endpoint_url = https://your-domain.example/storage/v1/s3 s3 = addressing_style = path ``` ```bash theme={null} aws --profile insforge s3 mb s3://my-bucket aws --profile insforge s3 cp ./photo.jpg s3://my-bucket/photo.jpg aws --profile insforge s3 sync ./dist s3://my-bucket/dist ``` Uploads made through the gateway appear immediately in the REST API and Dashboard. See [S3-compatible gateway](/core-concepts/storage/s3-compatibility) for supported operations and limits. ## Reference | Variable | Default | Meaning | | ------------------------------------------- | ------------------------- | --------------------------------------------------------- | | `S3_BUCKET` | *(empty = local storage)* | Backing bucket; setting it selects the S3 provider | | `S3_REGION` | `us-east-2` | Signing region (also the gateway's expected SigV4 region) | | `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` | *(empty)* | Store credentials | | `S3_ENDPOINT_URL` | *(empty = AWS)* | Custom S3-compatible endpoint | | `S3_FORCE_PATH_STYLE` | `true` | Path-style addressing (`false` for COS/OSS) | | `S3_USE_PRESIGNED_URLS` | `true` | `false` = proxy mode: bytes stream through the backend | | `S3_MAX_OBJECT_SIZE_BYTES` | 5 GB | Max single S3-gateway upload / part size | | `MAX_FILE_SIZE` | 50 MB | REST API upload cap (also configurable in the Dashboard) | The `AWS_*` variables (`AWS_S3_BUCKET`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_CLOUDFRONT_*`) are cloud-project and legacy names: the first two work as fallbacks for `S3_BUCKET`/`S3_REGION`, the credentials double as CloudWatch/CloudFront credentials, and CloudFront only applies to AWS S3 without a custom endpoint. Self-hosted deployments should stick to `S3_*`. ### Upgrading from an `AWS_*` configuration Older versions of `deploy/docker-compose/docker-compose.yml` did not pass storage variables at all, and the deployment guide recommended hand-adding `AWS_S3_BUCKET` (and friends) to the `insforge` service. If you did that: the current compose file passes only the `S3_*` names. Before adopting it, rename the variables in your `.env` to `S3_BUCKET` / `S3_REGION` / `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` — or keep your hand-edited `environment` block. The backend itself still honors `AWS_*` as fallbacks, so any value that reaches the container keeps working; the risk is only a compose file that no longer forwards them, which would silently fall back to local storage. # Next.js Source: https://docs.insforge.dev/examples/framework-guides/nextjs Create an InsForge project and build a full-stack Next.js app with the CLI and MCP, using AI editors like Cursor for schemas, queries, and deploys. Learn how to create an InsForge project and build a Next.js app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Next.js app with TypeScript and Tailwind CSS 3.4. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a page at /sports that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `lib/insforge.ts`: ```typescript lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key', }); ``` **Sports page** at `app/sports/page.tsx`: ```typescript app/sports/page.tsx theme={null} import { insforge } from '@/lib/insforge'; export default async function SportsPage() { const { data: sports, error } = await insforge.database .from('sports') .select(); if (error) { return (

Error

{error.message}

); } return (

Sports

{sports && sports.length > 0 ? (
{sports.map((sport: { id: string; name: string }) => (

{sport.name}

ID: {sport.id}

))}
) : (

No sports found.

)}
); } ``` ## 5. Start the app Run the development server, go to [http://localhost:3000/sports](http://localhost:3000/sports) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Nuxt Source: https://docs.insforge.dev/examples/framework-guides/nuxt Create an InsForge project and build a full-stack Nuxt app with the CLI and MCP, using AI editors like Cursor for schemas, queries, and deploys. Learn how to create an InsForge project and build a Nuxt app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Nuxt app with TypeScript. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a page that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **Runtime config** at `nuxt.config.ts`: ```typescript nuxt.config.ts theme={null} export default defineNuxtConfig({ runtimeConfig: { public: { insforgeBaseUrl: process.env.NUXT_PUBLIC_INSFORGE_BASE_URL, insforgeAnonKey: process.env.NUXT_PUBLIC_INSFORGE_ANON_KEY } } }) ``` **Server API route** at `server/api/sports.get.ts`: ```typescript server/api/sports.get.ts theme={null} import { createClient } from '@insforge/sdk'; export default defineEventHandler(async (event) => { const config = useRuntimeConfig() const client = createClient({ baseUrl: config.public.insforgeBaseUrl, anonKey: config.public.insforgeAnonKey }) const { data, error } = await client.database .from('sports') .select('*') if (error) { throw createError({ statusCode: 500, statusMessage: error.message || 'Failed to fetch sports' }) } return data }) ``` **Sports page** at `pages/sports.vue`: ```vue pages/sports.vue theme={null} ``` ## 5. Start the app Run the development server, go to [http://localhost:3000/sports](http://localhost:3000/sports) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # React Source: https://docs.insforge.dev/examples/framework-guides/react Create an InsForge project and build a full-stack React app with the CLI and MCP, using AI editors like Cursor for schemas, queries, and deploys. Learn how to create an InsForge project and build a React app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new React app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key', }); ``` **Sports component** at `src/components/Sports.tsx`: ```typescript src/components/Sports.tsx theme={null} import { useEffect, useState } from 'react'; import { insforge } from '../lib/insforge'; interface Sport { id: string; name: string; } export function Sports() { const [sports, setSports] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchSports() { const { data, error } = await insforge.database .from('sports') .select(); if (error) { setError(error.message); } else { setSports(data || []); } setLoading(false); } fetchSports(); }, []); if (loading) { return (

Loading...

); } if (error) { return (

Error

{error}

); } return (

Sports

{sports.length > 0 ? (
{sports.map((sport) => (

{sport.name}

ID: {sport.id}

))}
) : (

No sports found.

)}
); } ``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Svelte Source: https://docs.insforge.dev/examples/framework-guides/svelte Create an InsForge project and build a full-stack Svelte app with the CLI and MCP, using AI editors like Cursor for schemas, queries, and deploys. Learn how to create an InsForge project and build a Svelte app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Svelte app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key' }); ``` **Sports component** at `src/lib/components/Sports.svelte`: ```svelte src/lib/components/Sports.svelte theme={null}

Sports

{#if loading}

Loading...

{:else if error}

Error

{error}

{:else if sports.length > 0}
{#each sports as sport (sport.id)}

{sport.name}

ID: {sport.id}

{/each}
{:else}

No sports found.

{/if}
``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # Vue Source: https://docs.insforge.dev/examples/framework-guides/vue Create an InsForge project and build a full-stack Vue app with the CLI and MCP, using AI editors like Cursor for schemas, queries, and deploys. Learn how to create an InsForge project and build a Vue app using AI tools like Cursor. ## 1. Create an InsForge project Create a new InsForge project at [insforge.dev](https://insforge.dev). ## 2. Connect InsForge Two pieces: * **CLI link** — see the [Quickstart](/quickstart) to run `npx @insforge/cli link --project-id ` so the agent can read your project ID and keys. * **MCP setup** — see [MCP Setup](/mcp-setup) for the per-editor config (Cursor, Claude Code, Windsurf, Codex, VS Code). With both wired up the agent can read schemas, run queries, and deploy code from your editor. ## 3. Build your app with one prompt In Cursor or your AI assistant, use this prompt: ``` Create a new Vue app with TypeScript and Vite. Add Tailwind CSS 3.4 for styling. Install the InsForge SDK and set up the client configuration. In my InsForge database, create a sports table with id and name columns. Add sample data: basketball, soccer, and tennis. Make it publicly readable. Create a component that fetches and displays all sports from the database. ``` Your AI will generate the complete application including database setup and UI. No need to write code manually - the AI creates everything for you. ## 4. What the AI generates Your AI assistant will automatically create files like these. You don't need to touch them manually. **InsForge client** at `src/lib/insforge.ts`: ```typescript src/lib/insforge.ts theme={null} import { createClient } from '@insforge/sdk'; export const insforge = createClient({ baseUrl: 'https://your-project.us-east.insforge.app', anonKey: 'your-anon-key' }); ``` **Sports component** at `src/components/Sports.vue`: ```vue src/components/Sports.vue theme={null} ``` ## 5. Start the app Run the development server, go to [http://localhost:5173](http://localhost:5173) in a browser and you should see the list of sports. ```bash theme={null} npm run dev ``` ## Next: Extend your app with more prompts Try these prompts to add more features to your app: ``` Add a form to create new sports and save them to the database. Include validation and error handling. ``` ``` Add user authentication with sign up and login pages. Only allow authenticated users to add new sports. ``` ``` Add a favorites feature where users can mark their favorite sports. Store favorites in a user_favorites table with user_id and sport_id. ``` ``` Add images to each sport using InsForge Storage. Allow users to upload sport images and display them in the grid. ``` ``` Add an AI chat feature that can answer questions about sports. Use InsForge AI with streaming responses. ``` # InsForge cookbook and framework examples Source: https://docs.insforge.dev/examples/overview Browse InsForge examples for Next.js, React, Vue, Nuxt, and Svelte, plus AI prompts, setup guides, and community projects to build full-stack apps. A collection of practical examples and guides for building with InsForge - the fastest way to build full-stack applications with PostgreSQL, authentication, storage, AI, and serverless functions. ## Quick start To get started with any example in this cookbook: 1. **Browse examples** - Find the framework or use case that matches your needs 2. **Follow the guide** - Each example includes AI prompts and setup instructions 3. **Get the code** - See what the AI generates for you 4. **Build and customize** - Use the examples as starting points for your projects ## What's inside ### Framework guides Ready-to-use guides that show how to build with InsForge using popular frameworks and AI tools. Build a Next.js app using AI prompts Build a React app using AI prompts Build a Vue app using AI prompts Build a Nuxt app using AI prompts Build a Svelte app using AI prompts ### Community showcase Community-built applications that demonstrate real-world implementations of InsForge. Explore projects built with InsForge *** ## Contributing Have a project built with InsForge? We'd love to feature it! Submit your project or example Share your work with the community # InsForge FAQ: databases, schemas, edge functions, and SDK Source: https://docs.insforge.dev/faq Answers to common InsForge questions on database calls, edge functions, custom compute, querying non-public schemas from the SDK, and RLS. No. When you read or write a table, no function runs at all, so it isn't an edge function. In InsForge your code talks to the backend in three different ways, and they're easy to mix up: | | How it's triggered | Does it keep running? | What it's for | | ------------------------------------------- | ------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------ | | **Database call** (auto-generated REST API) | Your client sends an SDK or REST request | No, it's fully managed | Reading and writing table rows | | **Edge Function** | An HTTP request, a cron schedule, or a database trigger | No, it runs once and exits | Custom endpoints, webhooks, trigger logic, calling external services | | **Custom Compute** | You start a long-running process | Yes, it stays up | Queue workers, AI inference loops, websockets, anything that holds state | **Database call.** Define a table and InsForge instantly gives you a set of REST endpoints (like `GET /api/database/records/{table}`) and a typed SDK. Calling `select` or `insert` reads and writes the database directly, with nothing to deploy and nothing running. This is all you need for ordinary create/read/update/delete. See [Database](/core-concepts/database/overview). **Edge Function.** Reach for one when the auto-generated API isn't enough and you want your own server-side logic: a payment webhook, an auth hook, code that fires when a row is `INSERT`ed / `UPDATE`d / `DELETE`d, or a scheduled job. The point is that it runs once per request or event and then exits. See [Edge Functions](/core-concepts/functions/overview). **Custom Compute.** Use this when you need a process that stays up, like a queue worker or an AI inference loop. An edge function can't do this because it doesn't run continuously. See [Custom Compute](/core-concepts/compute/overview). Quick rule: just moving data in and out? That's the database (auto REST). Writing logic that runs and finishes? Edge function. Need something running all the time? Custom compute. By default all of your tables live in `public`. You only have another schema if you created one yourself with `CREATE SCHEMA` (InsForge's own internal schemas, like `auth` and `storage`, aren't exposed to the data API, so `.schema()` and `?schema=` can't reach them; as project admin you can still read them with raw SQL, e.g. `insforge db query` or the dashboard SQL editor). Once you have one, you can read and write it from the dashboard, the REST API, the CLI, and the SDK. The examples below use a schema you created called `my_schema`. **Dashboard.** Open **Database** and use the schema selector at the top of the sidebar. Any schema you created is listed alongside `public`, and picking it browses that schema's tables. **REST API.** The records endpoint takes the target schema either as a query param or as a PostgREST profile header. Reads use `Accept-Profile`, writes and RPC use `Content-Profile`: ```bash theme={null} # read: ?schema= param, or an Accept-Profile header curl "$PROJECT_URL/api/database/records/mytable?schema=my_schema" \ -H "Authorization: Bearer $TOKEN" # write: send Content-Profile curl -X POST "$PROJECT_URL/api/database/records/mytable" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Profile: my_schema" \ -H "Content-Type: application/json" \ -d '{"name": "hello"}' ``` **CLI.** The CLI reads and writes any schema through `db query`, just schema-qualify the table: ```bash theme={null} insforge db query "SELECT * FROM my_schema.mytable" ``` **SDK.** Chain `.schema()` before the query builder (supported by `@insforge/sdk`). It maps to the same `Accept-Profile` / `Content-Profile` header, so reads, writes, and RPC all route to the schema you name: ```javascript theme={null} // read const { data } = await client.database .schema('my_schema') .from('mytable') .select('*') // write await client.database .schema('my_schema') .from('mytable') .insert([{ name: 'hello' }]) // RPC await client.database.schema('my_schema').rpc('my_function', { day: '2026-01-01' }) ``` One more step for API access: a custom schema is only routable, not readable. The `anon` and `authenticated` roles have no privileges on it until you grant them, no matter who owns the tables, so calls come back empty or permission-denied until you do. Grant each role you expose, then add RLS: ```sql theme={null} GRANT USAGE ON SCHEMA my_schema TO anon, authenticated; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA my_schema TO anon, authenticated; ``` Row visibility is then gated by RLS as usual. Project-admin ownership only lets the admin manage and directly query the tables (for example from the dashboard SQL editor); it does not give the API roles access. New tables have RLS **on** by default. When you create a table (from the dashboard, `POST /api/database/tables`, or the SDK), RLS is enabled unless you explicitly pass `rlsEnabled: false`. There is no field to toggle RLS on the update-table-schema endpoint (`PATCH /api/database/tables/{table}/schema`) — it only handles columns, foreign keys, and renames. To change RLS on an **existing** table, run one SQL statement: ```sql theme={null} -- turn RLS off ALTER TABLE public.mytable DISABLE ROW LEVEL SECURITY; -- turn RLS back on ALTER TABLE public.mytable ENABLE ROW LEVEL SECURITY; ``` Run that SQL any way you run admin SQL, all of which require project-owner / admin access: ```bash theme={null} # one-off, via the CLI insforge db query "ALTER TABLE public.mytable DISABLE ROW LEVEL SECURITY" # or track it as a migration npx @insforge/cli db migrations new disable-rls-on-mytable # put the ALTER TABLE statement in the generated .sql file, then: npx @insforge/cli db migrations up --all ``` You can also run it from the dashboard SQL editor, the MCP `run-raw-sql` tool, or the raw SQL REST endpoint (`POST /api/database/advance/rawsql/unrestricted`). Turning RLS **off** removes all row-level filtering: any role with table privileges (such as `authenticated`, and `anon` where granted) can read and write every row through the data API. Prefer writing RLS policies over disabling RLS. Admin requests made with the API Key (`ik_...`) bypass RLS either way. Turning RLS **on** for a table that has no policies applies PostgreSQL's default-deny: `anon` and `authenticated` lose all access to it through the data API (every `SELECT`/`INSERT`/`UPDATE`/`DELETE` is blocked) until you add at least one policy. Add the policies you need before, or right after, enabling RLS. Not under that name. InsForge's equivalent is your project's **API Key** (it starts with `ik_`), the full-access admin key. Every project has two keys: * **Anon Key**: public, for the browser. Requests run as the `anon` role, gated by RLS. This is the one that hits `permission denied for schema storage`. * **API Key**: full-access admin key, server-only. Bypasses RLS. Find the API Key in the dashboard under **Project Settings → General** (the **API Key** row, marked "full access control... do not expose in your frontend"), or run `npx @insforge/cli secrets get API_KEY`. Use it from trusted server code through `createAdminClient`, never the browser: ```javascript theme={null} import { createAdminClient } from '@insforge/sdk' const admin = createAdminClient({ baseUrl: process.env.INSFORGE_URL, apiKey: process.env.INSFORGE_API_KEY, // admin key (ik_...), bypasses RLS }) const { data, error } = await admin.storage .from('post-images') .upload('posts/post-123/cover.jpg', fileObject) ``` Keep it in a server-only env var, never one exposed to the browser (no `NEXT_PUBLIC_`, `VITE_`, or `PUBLIC_` prefix). Access is shared at the **organization** level, not per project. You invite someone to the organization that owns your projects, and they get access to every project inside it. There is no separate "share just this one project" flow. To invite someone: 1. In the dashboard, open the organization that owns the project using the org switcher in the top-left. 2. Click **Members** in the left sidebar. 3. Click **Invite Member**, enter their email, and pick a role: * **Administrator** has full control: manage projects, plus invite, remove, and change the roles of other members. * **Developer** has normal access to the organization's projects but cannot manage members. 4. They get an email invite that is valid for 7 days. When they sign in to InsForge **with that same email address** and accept it, they join the organization with the role you chose. To add another admin specifically, choose the **Administrator** role when inviting, or change their role later from the Members list. Only Administrators can invite or manage members. Handing the organization over to a new **Owner** entirely is a separate action from inviting members. To do that, open **Organization Settings** and use **Transfer Ownership** (only the current owner can start it, and the recipient must be a verified InsForge user who accepts the emailed request). Pausing only happens on the Free plan, for two reasons: * **Inactivity.** A free project is paused after 7 days with no requests. We email a heads-up first, and any request resets the 7-day clock. * **Usage limit.** If your organization goes over the Free usage limits, its projects stay paused until you upgrade. Your data stays intact either way. To stop projects from pausing at all, upgrade the organization to Pro. See [Pricing](/pricing). Open the project in the dashboard and click **Restore Project**. It comes back in a few minutes with your data intact. A couple of cases to know: * You can restore a free project from the dashboard for up to **30 days** after it pauses. After that it's archived and you can only download the database backup and storage files (still no data loss). * If it was paused because the organization hit its usage limit, **Upgrade to Pro** to restore it. Still stuck? Ask in our [Discord](https://discord.com/invite/DvBtaEc9Jz) for the fastest response. `npx @insforge/cli login` opens a browser to sign in. On a headless machine, a remote server, or CI, use a user API key instead. No browser needed. The quickest way is the setup prompt from the dashboard, which signs in and links the project for you: Open your project in the dashboard and go to the **Install** page. Under **Install in Agent**, click the agent you use, then open the **CLI** tab. Copy the setup prompt and paste it into your agent. It signs the CLI in and links the project in one step. The prompt fills in a login command scoped to your account, followed by the link command: ```bash theme={null} npx @insforge/cli login --user-api-key npx @insforge/cli link --project-id ``` If you only need the key, for example to run the CLI in CI, open your account menu and go to **Profile → API Keys**, then create a key (set an expiry, or **Never**). Store it as a CI secret and run `login --user-api-key` with it. Add `--json` for machine-readable output. The key grants full access to your account, so keep it secret and rotate it if it leaks. It's an environment variable you only set when you self-host InsForge and want to use [Custom Compute](/core-concepts/compute/overview). Custom Compute runs your long-lived containers on [Fly.io](https://fly.io), so a self-hosted instance needs your own Fly account: set `FLY_API_TOKEN` (a Fly API token from `fly tokens create org`) and `FLY_ORG` (your Fly org slug from `fly orgs list`) in your `.env`, then restart. Both are required, and until they're set, compute endpoints return `503 COMPUTE_NOT_CONFIGURED`. On InsForge Cloud you never touch this. Compute is managed for you, and the rest of the platform (database, auth, storage, edge functions) needs no Fly token at all. Not really. This assistant answers from InsForge's public docs, so it can't see your project: it can't debug an error, read your data, or check your configuration. Take anything specific to your own project to your coding agent instead. Connected to InsForge through the CLI or MCP, your agent can read your live backend, schema, data, and logs and debug the problem directly. Just describe it in plain words. For a backend health and error report you can run yourself, use `npx @insforge/cli diagnose`. See [Diagnostics & advisor](/agent-native/diagnostics). Every cloud project has a direct Postgres connection string, handy for `psql`, a database GUI, an ORM (Prisma, Drizzle), or an external service like [Better Auth](/integrations/better-auth) that needs its own Postgres. Print it with the CLI: ```bash theme={null} npx @insforge/cli db connection-string ``` You can also grab it from the dashboard under **Project Settings → Connect → Connection String** (cloud projects only). It returns a URL shaped like: ```text theme={null} postgresql://postgres:@..database.insforge.app:5432/insforge?sslmode=require ``` Add `--json` to get `{ "connectionURL": "..." }` for scripts. The command works for **cloud projects only** — on a self-hosted instance Postgres is exposed directly by your `docker-compose` setup, so use the local Postgres credentials (the `DATABASE_URL` / `POSTGRES_*` values from your `.env`) instead. The string connects as the privileged `postgres` role, so it isn't limited by row-level security and it embeds that role's password. Treat it like a secret: keep it server-side and never ship it to the browser. Restore your existing database over your project's Postgres connection string (the same URL from the question above). Standard PostgreSQL client tools — `pg_dump`, `pg_restore`, and `psql` — connect to it directly, so an import is just a dump-then-restore. No special InsForge command is involved. 1. Dump your local (or other) database: ```bash theme={null} # Custom format (recommended — smaller and supports parallel restore) pg_dump -Fc your_local_db > local.dump # ...or a plain SQL file pg_dump your_local_db > local.sql ``` 2. Get your InsForge connection string (cloud projects only): ```bash theme={null} npx @insforge/cli db connection-string ``` 3. Restore into InsForge: ```bash theme={null} # Custom-format dump pg_restore --no-owner --single-transaction -d "postgresql://postgres:@..database.insforge.app:5432/insforge?sslmode=require" local.dump # ...or a plain SQL file psql --single-transaction "postgresql://postgres:@..database.insforge.app:5432/insforge?sslmode=require" < local.sql ``` A few things to keep in mind: * Pass `--no-owner` so restored objects are owned by the `postgres` role rather than roles that only exist in your source database. * The connection string connects as the privileged `postgres` role (it bypasses row-level security), so treat it as a secret and run these commands server-side only. * This works for **cloud projects only**. On a self-hosted instance, restore against your local Postgres credentials (the `DATABASE_URL` / `POSTGRES_*` values from your `.env`) instead. * A restore writes into your live database and can overwrite existing objects. Take a manual backup first — see [Database backups and restore](/core-concepts/database/backups). * `--single-transaction` runs the restore as one transaction, so if a statement fails (a conflicting object, a missing extension or role, a constraint error) the whole import rolls back instead of leaving the database half-restored. * Imported tables do **not** get InsForge's managed access automatically. A raw restore skips the `anon` / `authenticated` grants and row-level security InsForge applies, so imported tables are **not** reachable through the REST API or SDK — and any access rules from your source dump don't carry InsForge's RLS protection — until you grant access and add RLS policies for each table (see [Database](/core-concepts/database/overview)). If you'd rather version schema changes in git, see [Database migrations](/core-concepts/database/migrations). There's currently no self-serve way to take down a deployed [Site](/core-concepts/sites/overview) — there's no `deployments delete` command and no dashboard action for it. Deployed sites are hosted externally, so even deleting your project (`npx @insforge/cli projects delete --project `) tears down your backend resources — database, storage, and backend branches — but does *not* remove the hosted site. In practice this rarely matters. If you do need a deployed site taken down, ask the InsForge team in [Discord](https://discord.com/invite/DvBtaEc9Jz). Two related actions that are *not* the same as removing a live site: * **Cancel a build that's still running:** `npx @insforge/cli deployments cancel ` stops an in-progress deployment; it does not take down a site that is already live. * **Replace what's live:** redeploy over the same site with `npx @insforge/cli deployments deploy ./frontend` — the newest ready deployment serves the URL. # Auth0 Authentication Source: https://docs.insforge.dev/integrations/auth0 Add Auth0 enterprise authentication and SSO to your InsForge backend. Step-by-step setup for JWT validation, RLS policies, and user sync. ## Overview [Auth0](https://auth0.com) is an authentication and authorization platform that supports social logins, enterprise federation, and passwordless authentication. This guide shows how to integrate Auth0 with InsForge in a Next.js application. Auth0 handles authentication, while InsForge manages data authorization through Row Level Security (RLS) policies. On each server request, the app signs a fresh InsForge JWT from the Auth0 session using your InsForge secret, so InsForge accepts it natively. * [Live Demo](https://auth0auth.insforge.site) — A sample app using Auth0 authentication with InsForge * [Source Code](https://github.com/InsForge/insforge-integration/tree/main/auth/auth0) — GitHub repository for the sample app ## Prerequisites * An InsForge project (self-hosted or cloud) * An [Auth0](https://auth0.com) account and tenant * A Next.js application (or any framework — adjust the client code accordingly) ## Step 1: Create an Auth0 Application 1. Log in to your [Auth0 Dashboard](https://manage.auth0.com) 2. Go to **Applications** > **Applications** > **Create Application** 3. Choose **Regular Web Application** and give it a name (if prompted to select a technology, choose **Next.js** or skip — it only affects which quickstart guide Auth0 shows you) 4. In the **Settings** tab, configure: * **Allowed Callback URLs**: `http://localhost:3000/auth/callback` * **Allowed Logout URLs**: `http://localhost:3000` 5. Note down the **Domain**, **Client ID**, and **Client Secret** ## Step 2: Set Up Your InsForge Project Create a new project or link an existing one: ```bash theme={null} # Create a new project npx @insforge/cli create # Or link an existing project npx @insforge/cli link --project-id ``` Then get your project credentials: ```bash theme={null} # Get the JWT Secret npx @insforge/cli secrets get JWT_SECRET ``` Note down the **URL** and **Anon Key** from the InsForge dashboard. You'll use the JWT Secret from the CLI output in a later step to sign tokens for InsForge. ## Step 3: Set Up Your Application Install the required dependencies: ```bash theme={null} npm install @auth0/nextjs-auth0 @insforge/sdk jsonwebtoken npm install --save-dev @types/jsonwebtoken ``` Add environment variables to `.env.local`: ```env theme={null} # Auth0 AUTH0_SECRET='use [openssl rand -hex 32] to generate a 32 bytes value' APP_BASE_URL='http://localhost:3000' AUTH0_DOMAIN='YOUR_AUTH0_DOMAIN' AUTH0_CLIENT_ID='YOUR_CLIENT_ID' AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET' # InsForge NEXT_PUBLIC_INSFORGE_URL='YOUR_INSFORGE_URL' NEXT_PUBLIC_INSFORGE_ANON_KEY='YOUR_INSFORGE_ANON_KEY' INSFORGE_JWT_SECRET='YOUR_INSFORGE_JWT_SECRET' ``` ## Step 4: Set Up InsForge Integration Ask your agent to complete the following steps: ### 1. Set up Auth0 authentication ```text theme={null} Set up Auth0 for my Next.js app — Auth0 client, middleware, and provider. ``` This creates the Auth0 client (`lib/auth0.ts`), middleware (`middleware.ts`), and Auth0Provider wrapper (`app/layout.tsx`). ### 2. Create the InsForge client utility ```text theme={null} Create the InsForge client utility that uses the Auth0 session to sign a JWT for InsForge. ``` This creates a server-side utility (`lib/insforge.ts`) that gets the Auth0 user via `auth0.getSession()`, signs a JWT with the InsForge secret, and passes it as `edgeFunctionToken`. ### 3. Create the database schema ```text theme={null} Create a todos table with RLS. Columns: id, user_id, title, is_complete, created_at. Users should only be able to access their own todos. ``` This creates the `requesting_user_id()` helper function (since Auth0 user IDs are strings, not UUIDs) and a `todos` table with Row Level Security policies. ### 4. Build the todo list page ```text theme={null} Build a todo list page with full CRUD — create, read, update, and delete todos. ``` This creates a page that uses the InsForge client to manage todos. RLS ensures users only see their own data. ## Step 5: Run Your Application ```bash theme={null} # Install dependencies if you haven't already npm install npm run dev ``` Open `http://localhost:3000` and sign up with a new user through Auth0. Since authentication is handled entirely by Auth0, you will **not** see any users in the InsForge dashboard under **Auth > Users**. User records are managed in the [Auth0 Dashboard](https://manage.auth0.com) — check **User Management > Users** there to confirm the sign-up was successful. InsForge Auth Users — empty because Auth0 manages users # Better Auth Self-Hosted Auth Source: https://docs.insforge.dev/integrations/better-auth Run Better Auth in your own Postgres alongside InsForge. Same-origin sessions, HS256 bridge JWT, and Row Level Security in one app. ## Overview [Better Auth](https://better-auth.com) is a TypeScript-first, self-hosted auth library that stores users in your own Postgres. The InsForge CLI scaffolds a small **bridge route** that reads Better Auth's session cookie and signs an HS256 JWT with InsForge's secret, so PostgREST and Row Level Security accept the request natively. Better Auth's tables live in a dedicated `better_auth` schema. PostgREST exposes only `public`, so user emails and sessions are hidden from the data API by construction. ## Prerequisites * An InsForge project (self-hosted or cloud) * A Postgres database for Better Auth's tables — self-hosted InsForge shares the same Postgres for free; cloud needs a connection string to a Postgres you control ## Step 1: Scaffold the Project ```bash theme={null} npx @insforge/cli link --project-id --auth better-auth ``` Run this in your Next.js project. If you don't have one yet, create one first (`npx create-next-app@latest`) — or spin one up in your InsForge dashboard and link to it. The CLI drops in a Better Auth server + React client, the bridge route at `/api/insforge-token`, working `/sign-up` and `/sign-in` pages, a `useInsforgeClient` hook, and a bootstrap migration that creates the `better_auth` schema and the `requesting_user_id()` SQL helper. `.env.local` is pre-filled with your project's URL, anon key, JWT secret, and `DATABASE_URL` (fetched from your linked project). ## Step 2: What `--auth better-auth` Already Did After dropping the scaffold files in place, the CLI automatically ran `npm install` and `npm run setup`, which chains: 1. **`insforge db migrations up --to 0001`** — creates the `better_auth` schema, `pgcrypto`, and the `requesting_user_id()` function that extracts the `sub` claim from `request.jwt.claims`. 2. **`better-auth migrate`** — creates `user`, `session`, `account`, `verification` tables. With `search_path` scoped to `better_auth, public`, they land in `better_auth.*`. 3. **`insforge db migrations up --all`** — picks up any further migrations you add (for your own RLS-protected tables). **Setup failed?** If the CLI logged `npm run setup failed`, the most common cause is `DATABASE_URL` pointing at an unreachable Postgres. Fix `.env.local` and re-run `npm run setup` — it's idempotent. ## Step 3: Run Your Application ```bash theme={null} npm run dev ``` Open `http://localhost:3000/sign-up`, create a user, and you'll be redirected to `/`. The new user lives in `better_auth.user` — switch the Studio schema dropdown to `better_auth` to see it (Studio reaches `better_auth.*` through its admin route, even though PostgREST hides it from the data API): InsForge Studio with the better_auth schema selected, showing the user table populated with rows from sign-up **Why is InsForge's Auth > Users empty?** Authentication is handled entirely by Better Auth, so user records live in `better_auth.user` — not in InsForge's native `auth.users` table. InsForge only sees the JWT claim (`sub`) that RLS uses to scope data to the correct user. ## Adding Your Own RLS-Protected Tables Ask your agent to complete the following: ### 1. Create an RLS-protected table ```text theme={null} Create a posts table with RLS. Columns: id, user_id, title, body, created_at. Better Auth user IDs are strings, so user_id is text and FKs to better_auth."user"(id). Users should only see and modify their own rows. ``` This creates a migration with the table and an RLS policy that scopes every query to the signed-in user. ### 2. Build the page ```text theme={null} Build a posts page with full CRUD using the useInsforgeClient hook from src/lib/insforge.ts. ``` Sign up as a second user in an incognito window — they see an empty list, never the first user's rows. That's RLS working through the bridged JWT. ## Further Reading * [Scaffold source](https://github.com/InsForge/insforge-templates/tree/main/auth-providers/better-auth) — the template files the CLI drops into your project * [Better Auth + InsForge skill reference](https://github.com/InsForge/insforge-skills/blob/main/skills/insforge-integrations/references/better-auth.md) — Vite / React-only setups, plugins (organization, twoFactor, magicLink), email transport via `client.emails.send`, realtime, cross-origin gotchas, and a Common Mistakes table * [Better Auth plugins](https://better-auth.com/docs/plugins) — plugins that add tables (`organization`, `twoFactor`, `apiKey`, `passkey`, …) write to whatever schema BA's pool sees in `search_path` — i.e., `better_auth` — so they inherit the same data-API isolation as the core four tables automatically. # Clerk Authentication Source: https://docs.insforge.dev/integrations/clerk Add Clerk drop-in auth and user management to your InsForge app. Configure JWT verification, RLS policies, and synced user records in minutes. ## Overview [Clerk](https://clerk.com) is an authentication and user management platform that provides pre-built UI components and APIs for sign-up, sign-in, and user profiles. This guide shows how to integrate Clerk with InsForge using Clerk's **JWT Templates** feature. Clerk signs tokens with InsForge's JWT secret, so InsForge accepts them natively. * [Live Demo](https://clerkauth.insforge.site) — A sample todo app using Clerk authentication with InsForge * [Source Code](https://github.com/InsForge/insforge-integration/tree/main/auth/clerk) — GitHub repository for the sample app ## Prerequisites * An InsForge project (self-hosted or cloud) * A [Clerk](https://clerk.com) account and application ## Step 1: Set Up Your InsForge Project Create a new project or link an existing one: ```bash theme={null} # Create a new project npx @insforge/cli create # Or link an existing project npx @insforge/cli link --project-id ``` Then get your project credentials: ```bash theme={null} # Get the JWT Secret npx @insforge/cli secrets get JWT_SECRET ``` To find your **Project URL** and **Anon Key**, open your project in the [InsForge Dashboard](https://insforge.dev), click the **Connect** button in the top-right corner, and switch to the **API Keys** tab: InsForge dashboard — Connect Project modal, API Keys tab showing Project URL and Anon Key Copy the **Project URL** and **Anon Key** — you'll paste these into `.env.local` in Step 3. The CLI prints the JWT Secret as `JWT_SECRET=` — you'll use only the `` part (everything after the `=`) when creating the Clerk JWT Template in the next step. ## Step 2: Create a JWT Template in Clerk 1. Go to your [Clerk Dashboard](https://dashboard.clerk.com) 2. Navigate to **Configure** > **Sessions** > **JWT Templates** 3. Click **New template** and select **Blank** 4. Name it `insforge` 5. Toggle on **Custom signing key** 6. Set the **Signing algorithm** to `HS256` 7. Paste your InsForge **JWT Secret** into the **Signing key** field * Paste **only the value**, not the `JWT_SECRET=` prefix. For example, if the CLI output is `JWT_SECRET=a1b2c3d4e5f6...`, paste only `a1b2c3d4e5f6...`.