# Running InsForge — Self-Hosted, Docker, and Cloud

> How to get InsForge running: the Docker Compose stack (backend, Postgres, Deno subhosting), the frontend dashboard (React/Vite, cloud vs. self-host modes), deploy targets (Render, AWS EC2, Azure, GCE), and the environment variables an operator must configure before the server boots.

- Repository: InsForge/InsForge
- GitHub: https://github.com/InsForge/InsForge
- Human wiki: https://grok-wiki.com/public/wiki/insforge-insforge-357039661319
- Complete Markdown: https://grok-wiki.com/public/wiki/insforge-insforge-357039661319/llms-full.txt

## Source Files

- `docker-compose.override.yml`
- `deploy/docker-compose/docker-compose.yml`
- `Dockerfile`
- `deploy/Dockerfile.deno`
- `deploy/docker-compose/.env.example`
- `frontend/src/App.tsx`
- `docs/deployment/README.md`
- `GITHUB_OAUTH_SETUP.md`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [deploy/docker-compose/docker-compose.yml](deploy/docker-compose/docker-compose.yml)
- [deploy/docker-compose/.env.example](deploy/docker-compose/.env.example)
- [docker-compose.override.yml](docker-compose.override.yml)
- [Dockerfile](Dockerfile)
- [deploy/Dockerfile.deno](deploy/Dockerfile.deno)
- [deploy/zeabur/template.yml](deploy/zeabur/template.yml)
- [deploy/docker-deploy.md](deploy/docker-deploy.md)
- [deploy/zeabur/README.md](deploy/zeabur/README.md)
- [frontend/src/App.tsx](frontend/src/App.tsx)
- [frontend/src/helpers.ts](frontend/src/helpers.ts)
- [docs/deployment/README.md](docs/deployment/README.md)
- [docs/deployment/deploy-to-render.md](docs/deployment/deploy-to-render.md)
- [docs/deployment/deploy-to-aws-ec2.md](docs/deployment/deploy-to-aws-ec2.md)
- [GITHUB_OAUTH_SETUP.md](GITHUB_OAUTH_SETUP.md)
</details>

# Running InsForge — Self-Hosted, Docker, and Cloud

This page explains every way to get InsForge running: the four-container Docker Compose stack that makes up a self-hosted installation, the React/Vite frontend that switches between cloud and self-hosted modes at runtime, the environment variables an operator must supply before the server boots, and the supported deployment targets including Render, AWS EC2, Google Cloud Compute Engine, and Zeabur.

Whether you are spinning up a local development copy, hosting it on a VPS, or pushing it to a managed cloud platform, the entry point is always the same compose file and the same set of environment variables. Understanding how those pieces fit together is the fastest path to a working, production-ready install.

---

## The Four-Service Stack

InsForge runs as four cooperating Docker containers. Each service listens on its own port and depends on the others in a well-defined order.

```text
┌─────────────────────────────────────────────────────┐
│                 insforge-network (bridge)            │
│                                                     │
│  postgres :5432 ──► postgrest :5430                 │
│       │                  │                          │
│       └──────────────────┼──► insforge :7130 / 7131 │
│       │                  │                          │
│       └──────────────────┴──► deno :7133            │
└─────────────────────────────────────────────────────┘
```

| Service | Image | Default port | Role |
|---|---|---|---|
| `postgres` | `ghcr.io/insforge/postgres-all:latest` | `5432` | Persistent PostgreSQL 15 database |
| `postgrest` | `postgrest/postgrest:v12.2.12` | `5430` | Auto-generated REST API from the database schema |
| `insforge` | `ghcr.io/insforge/insforge-oss:v1.5.0` | `7130` (API), `7131` (auth) | Node.js backend + compiled React frontend |
| `deno` | `ghcr.io/insforge/deno-runtime:latest` | `7133` | Deno 2 edge-function runtime |

Sources: [deploy/docker-compose/docker-compose.yml:1-163]()

### postgres

The custom `postgres-all` image bundles the standard Postgres 15 engine with extensions (`pg_cron`, `http`, `pgcrypto`) and runs DB-level initialization on first boot. The JWT secret is written into the database's settings so PostgREST and the application can share it without extra configuration.

A named volume `postgres-data` persists all data across container restarts.

Sources: [deploy/docker-compose/docker-compose.yml:2-18](), [deploy/zeabur/template.yml:147-173]()

### postgrest

PostgREST v12.2.12 generates a full REST API directly from the `public` schema of the InsForge database. The `anon` Postgres role controls unauthenticated access. Schema-reload notifications travel over the `pgrst` channel (`PGRST_DB_CHANNEL`), so table changes are reflected without restarting the container.

PostgREST is reachable inside the network at `http://postgrest:3000` and is exposed on host port `5430` by default.

Sources: [deploy/docker-compose/docker-compose.yml:22-45]()

### insforge (main application)

The `insforge` container starts the Node.js backend on port `7130` and the auth service on port `7131`. On every startup the container runs pending database migrations before starting the server:

```dockerfile
CMD ["sh", "-c", "cd backend && npm run migrate:up && cd .. && exec npm start"]
```

The compiled React frontend is served as static files from inside the same image. The Dockerfile is a five-stage build: it strips the package version for cache stability, installs all dev dependencies, builds all packages, strips down to production dependencies, and finally copies compiled output into a minimal Alpine/Node 20 image running as the unprivileged `node` user.

Sources: [Dockerfile:154](), [Dockerfile:97-154]()

### deno

The Deno service runs a lightweight HTTP server on port `7133`. When an incoming request matches `/:slug`, it fetches the corresponding function's source code from the `functions.definitions` table in Postgres, decrypts any secrets stored in `system.secrets`, and executes the code in an isolated Web Worker. Workers have a configurable timeout (`WORKER_TIMEOUT_MS`, default 60 000 ms) and are terminated after each request.

The health endpoint at `GET /health` returns the Deno version and engine details:

```
GET http://localhost:7133/health
→ { "status": "ok", "runtime": "deno", "version": "2.x.x", ... }
```

Sources: [deploy/zeabur/template.yml:373-615](), [deploy/docker-compose/docker-compose.yml:113-149]()

---

## Startup Sequence and Health Checks

```mermaid
sequenceDiagram
    participant O as Operator
    participant PG as postgres
    participant PGR as postgrest
    participant APP as insforge
    participant DN as deno

    O->>PG: docker compose up -d
    PG-->>PG: init extensions + roles (first boot)
    PG-->>O: healthy (pg_isready)
    PG->>PGR: dependency satisfied
    PGR-->>O: healthy (TCP :3000)
    PG->>APP: dependency satisfied
    PGR->>APP: dependency satisfied
    APP-->>APP: npm run migrate:up
    APP-->>O: healthy (GET /api/health)
    PG->>DN: dependency satisfied
    DN-->>O: healthy (GET /health)
```

All four services join the same `insforge-network` bridge. The compose file enforces health-check conditions (`service_healthy`) for every dependency, so the application will not start until the database and PostgREST are ready.

Sources: [deploy/docker-compose/docker-compose.yml:53-57](), [deploy/docker-compose/docker-compose.yml:118-120]()

---

## Environment Variables

Copy `.env.example` and edit it before starting the stack. The table below covers every variable the compose file reads.

### Required (must change for production)

| Variable | Default | Purpose |
|---|---|---|
| `JWT_SECRET` | `dev-secret-please-change-in-production` | Signs JWTs shared by the backend, PostgREST, and the Deno runtime. Must be ≥ 32 characters. |
| `ADMIN_EMAIL` | `admin@example.com` | Email for the bootstrapped admin account. |
| `ADMIN_PASSWORD` | `change-this-password` | Password for the bootstrapped admin account. |

### Database (optional — defaults shown)

| Variable | Default |
|---|---|
| `POSTGRES_USER` | `postgres` |
| `POSTGRES_PASSWORD` | `postgres` |
| `POSTGRES_DB` | `insforge` |
| `ENCRYPTION_KEY` | Falls back to `JWT_SECRET` |

`ENCRYPTION_KEY` is used for AES-GCM encryption of secrets stored in the `system.secrets` table. If omitted it inherits `JWT_SECRET`.

### Ports (change to avoid conflicts when running multiple instances)

| Variable | Default | Service |
|---|---|---|
| `APP_PORT` | `7130` | InsForge backend + frontend |
| `AUTH_PORT` | `7131` | Auth sub-service |
| `POSTGREST_PORT` | `5430` | PostgREST REST API |
| `POSTGRES_PORT` | `5432` | PostgreSQL |
| `DENO_PORT` | `7133` | Deno edge functions |

### URL configuration (required when behind a reverse proxy)

| Variable | Purpose |
|---|---|
| `API_BASE_URL` | Server-side base URL; tells the backend what public URL it is reachable at. |
| `VITE_API_BASE_URL` | Baked into the frontend bundle at compile time; tells the React app where to make API calls. |

**Note:** `VITE_API_BASE_URL` is a Vite build argument. For the pre-built Docker image it must be set before the image is built, or set at runtime (the compose file passes it through).

### Integrations (all optional)

| Variable | Purpose |
|---|---|
| `OPENROUTER_API_KEY` | LLM routing for AI agent features |
| `STRIPE_TEST_SECRET_KEY` / `STRIPE_LIVE_SECRET_KEY` | Payment catalog sync |
| `VERCEL_TOKEN` / `VERCEL_TEAM_ID` / `VERCEL_PROJECT_ID` | Self-hosted site deployments to Vercel |
| `WORKER_TIMEOUT_MS` | Maximum time (ms) for a Deno edge function (default `60000`) |

### OAuth providers (all optional)

Each supported provider needs a client ID and secret. The callback URL pattern is `http://<your-domain>/api/auth/oauth/<provider>/callback`.

| Provider | Variables |
|---|---|
| Google | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` |
| GitHub | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` |
| Microsoft | `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET` |
| Discord | `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET` |
| LinkedIn | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET` |
| X (Twitter) | `X_CLIENT_ID`, `X_CLIENT_SECRET` |
| Apple | `APPLE_CLIENT_ID`, `APPLE_CLIENT_SECRET` |

For GitHub OAuth the authorization callback URL must be set in the GitHub developer settings and match the backend route exactly. See [GITHUB_OAUTH_SETUP.md]() for the full walkthrough.

Sources: [deploy/docker-compose/.env.example:1-73](), [deploy/docker-compose/docker-compose.yml:61-109]()

---

## Quick-Start: Local Docker Compose

```bash
# 1. Fetch the compose file and example env
wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/docker-compose.yml
wget https://raw.githubusercontent.com/insforge/insforge/main/deploy/docker-compose/.env.example
mv .env.example .env

# 2. Edit at minimum JWT_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD
$EDITOR .env

# 3. Start all four services
docker compose up -d

# 4. Open the dashboard
open http://localhost:7130
```

After a few seconds the app is accessible at `http://localhost:7130`. The migration step runs automatically inside the container before the server starts.

Sources: [deploy/docker-deploy.md:1-30]()

### Running multiple instances on the same host

Each project needs its own env file, unique ports, and a unique compose project name:

```bash
cp .env .env.project2
# Edit .env.project2: change APP_PORT, AUTH_PORT, DENO_PORT, POSTGREST_PORT, POSTGRES_PORT, JWT_SECRET
docker compose --env-file .env.project2 -p project2 up -d
```

The `-p` flag gives each instance completely isolated containers, volumes, and networks. Each instance's data is fully independent.

Sources: [deploy/docker-deploy.md:31-89]()

---

## The Application Image: Multi-Stage Build

The production `Dockerfile` has five stages to keep image size small and build cache efficient:

| Stage | Base | What it does |
|---|---|---|
| `package-prep` | `node:20-alpine` | Strips `version` from `package.json` so downstream layers are cache-stable across releases |
| `deps` | `package-prep` | Runs `npm ci` with both dev and prod dependencies |
| `build` | `deps` | Compiles all packages (`shared-schemas` → `backend` → `frontend`) |
| `prod-deps` | `node:20-alpine` | Runs `npm ci --omit=dev` for a minimal dependency tree |
| `runner` | `node:20-alpine` | Copies compiled output and prod `node_modules`; runs as non-root `node` user with `tini` as PID 1 |

A `dev` stage (used by the override compose file) skips the build steps and mounts source code as volumes instead.

Sources: [Dockerfile:1-181]()

---

## Frontend: Cloud vs. Self-Hosted Mode

The React app checks the browser's origin at runtime and renders a different root component depending on whether it is running in the cloud or self-hosted:

```tsx
// frontend/src/App.tsx
function App() {
  if (isCloudHosting()) {
    return <CloudHostingDashboard />;
  }
  return <SelfHostingDashboard />;
}
```

`isCloudHosting()` returns `true` when `window.location.origin` ends with `.insforge.app`:

```ts
// frontend/src/helpers.ts
export function isCloudHosting(): boolean {
  return window.location.origin.endsWith('.insforge.app');
}
```

This means the identical built image serves the cloud-managed dashboard when deployed to `*.insforge.app` and the full self-hosted dashboard everywhere else. No feature flags or build-time switches are needed.

Sources: [frontend/src/App.tsx:1-13](), [frontend/src/helpers.ts:1-7]()

---

## Deploy Targets

### Zeabur (one-click)

The `deploy/zeabur/template.yml` defines all four services as a Zeabur template. After a one-click deploy you configure `PUBLIC_DOMAIN`, `ADMIN_EMAIL`, `ADMIN_PASSWORD`, and optionally `OPENROUTER_API_KEY` and Stripe keys in the Zeabur dashboard. All secrets are wired automatically between services via template variable references (`${JWT_SECRET}`, `${PGPASSWORD}`, etc.).

```bash
npx zeabur@latest template deploy -f deploy/zeabur/template.yml
```

Sources: [deploy/zeabur/template.yml:1-50](), [deploy/zeabur/README.md:1-27]()

### Render

Render requires a bit more manual setup: create a managed PostgreSQL database, run the initialization SQL scripts against it, then deploy the `insforge` container as a Web Service. The recommended spec is at least the Starter plan ($7/month for the database, free tier for testing). Render auto-provisions TLS and can auto-deploy from commits.

Sources: [docs/deployment/deploy-to-render.md:1-80]()

### AWS EC2

Run the Docker Compose stack on an EC2 instance (Ubuntu 24.04, `t3.medium` minimum — 2 vCPU, 4 GB RAM). Open inbound TCP ports 80, 443, 7130, and 7131 in the security group. An Elastic IP is recommended so the address survives reboots. Place Nginx or Caddy in front of the containers for TLS termination.

Sources: [docs/deployment/deploy-to-aws-ec2.md:1-60]()

### Google Cloud Compute Engine

The same Docker Compose approach applies on a GCE VM. The deployment guide mirrors the EC2 steps: provision a VM, install Docker, copy the compose file and env, run `docker compose up -d`, and add a reverse proxy for HTTPS.

Sources: [docs/deployment/README.md:29-33]()

### Azure

An Azure VM guide is listed as "Coming Soon" in the deployment index.

Sources: [docs/deployment/README.md:37-45]()

---

## Minimum Infrastructure Requirements

| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 2 GB | 4 GB |
| Disk | 20 GB | 30 GB |
| PostgreSQL | 15+ | 15+ |
| Docker Engine | Any recent | 24+ |

Sources: [docs/deployment/README.md:67-76]()

---

## Summary

InsForge self-hosting always starts with the same compose file (`deploy/docker-compose/docker-compose.yml`). Before the server boots, an operator must set `JWT_SECRET`, `ADMIN_EMAIL`, and `ADMIN_PASSWORD` — everything else has safe defaults for local use. The main app container runs database migrations on every startup and then serves the backend API alongside the compiled React frontend, which automatically detects whether it is running on `*.insforge.app` (cloud mode) or elsewhere (self-hosted mode) based solely on the browser origin. For cloud deployment, both Zeabur (one-click template) and Render, EC2, and GCE (Docker Compose on a VM) are documented paths; all of them ultimately run the same four-container stack with the same environment variables.

Sources: [deploy/docker-compose/docker-compose.yml:49-111](), [frontend/src/helpers.ts:1-7]()
