# infrapage > Your project's live numbers — stars, uptime, users — on one page you share. Like Linktree for your infrastructure. Open source and self-hostable. --- ## [Introduction](https://github.com/hauju/infrapage/docs/getting-started/introduction) # What is infrapage? **infrapage** is a self-hosted, widget-based infrastructure dashboard. Think of it as "Linktree for project infrastructure" — a single page that brings together GitHub repos, uptime monitors, analytics, CI/CD pipelines, and more into a beautiful, shareable dashboard. > **Tip:** infrapage is fully open source and designed to be self-hosted. You own your data and can customize everything. ## Key Features **Widget-Based** Build dashboards from 17+ widget types covering GitHub, uptime monitoring, analytics, and more. **Drag & Drop Editor** Arrange widgets with a visual grid editor. Resize, reorder, and delete with intuitive controls. **Custom Domains** Map custom domains to any page for a branded experience. **SSR + Hydration** Pages load fast with server-side rendering. Each widget hydrates independently — slow APIs never block the page. **Dark & Light Themes** Built-in theme support with smooth transitions. Respects system preferences by default. **Self-Hosted** Deploy with Docker or build from source. Uses MongoDB for storage and optional Valkey for caching. ## How It Works infrapage uses a **two-phase loading** approach for optimal performance: 1. **Server-Side Render** — The page shell and widget skeletons render immediately on the server. 2. **Client Hydration** — Each widget independently fetches its data and renders as the response arrives. This means your dashboard page loads instantly, and individual widgets fill in as their APIs respond. ## Tech Stack - **Framework:** Dioxus 0.7 (Rust fullstack — compiles to both server binary and WASM client) - **Styling:** Tailwind CSS + DaisyUI - **Database:** MongoDB - **Auth:** FerrisKey OIDC (optional) - **Caching:** Valkey/Redis (optional) ## Next Steps **Quick Start** Get infrapage running locally in 5 minutes. **Widget Overview** Learn how the widget system works. --- ## [Quick Start](https://github.com/hauju/infrapage/docs/getting-started/quickstart) # Quick Start Get infrapage up and running locally. You can use Docker for the fastest setup or build from source if you prefer. ## Prerequisites You'll need: - **MongoDB** — A running MongoDB instance (local or cloud) - **Docker** (recommended) or **Rust toolchain** (for building from source) #### Docker 1. **Start MongoDB** If you don't have MongoDB running, start it with Docker: ```bash docker run -d --name mongo -p 27017:27017 mongo:7 ``` 2. **Run infrapage** ```bash docker run -d \ --name infrapage \ -p 8080:8080 \ -e MONGODB_URI=mongodb://host.docker.internal:27017 \ ghcr.io/hauju/infrapage:latest ``` 3. **Open the Dashboard** Navigate to [http://localhost:8080/dashboard](http://localhost:8080/dashboard) to start building your first page. #### From Source 1. **Install Dioxus CLI** ```bash curl -sSL http://dioxus.dev/install.sh | sh ``` 2. **Clone the Repository** ```bash git clone https://github.com/hauju/infrapage.git cd infrapage ``` 3. **Configure Environment** Create a `.env` file: ```bash MONGODB_URI=mongodb://localhost:27017 MONGODB_DATABASE=infrapage ``` 4. **Start the Dev Server** ```bash dx serve --platform web ``` This starts the server with hot reload at [http://localhost:8080](http://localhost:8080). > **Note:** When the FerrisKey authentication environment variables (`FERRISKEY_URL`, `FERRISKEY_REALM`, `FERRISKEY_CLIENT_ID`, `FERRISKEY_CLIENT_SECRET`) are not all set, infrapage runs in **open mode** — all admin endpoints are accessible without login. This is perfect for local development and private deployments. ## What's Next? **Add Your First Widget** Learn how to add widgets to your dashboard page. **Configuration** Explore all available environment variables. --- ## [Widget System](https://github.com/hauju/infrapage/docs/widgets/overview) # Widget System Widgets are the building blocks of infrapage. Every dashboard page is composed of widgets arranged in a grid. The system is designed to be extensible — each widget type fetches data from a different source and renders it as a card. ## Three-Layer Architecture Every widget in infrapage consists of three layers: 1. **Widget Instance (Config)** Stored in MongoDB as part of the page document. Contains the widget's **kind**, **configuration parameters** (like a GitHub repo URL or an API endpoint), and **grid position** (x, y, width, height). ```json { "kind": "GitHubRepo", "config": { "owner": "hauju", "repo": "infrapage" }, "w": 1, "h": 1, "x": 0, "y": 0 } ``` 2. **Widget Data (Fetched)** The runtime data fetched from external APIs based on the widget's configuration. For a GitHub repo widget, this would include star count, language, description, and last updated time. Each widget kind has its own data struct — the `WidgetData` enum dispatches to the correct fetcher. 3. **Widget Component (Render)** A Dioxus component that takes the fetched data and renders it as a DaisyUI card. Each widget kind has a dedicated component in `src/components/widgets/`. ## Two-Phase Loading Public pages use a two-phase approach for optimal performance: 1. **SSR Phase** — `use_server_future` fetches the page metadata from MongoDB. The HTML shell renders immediately with widget skeletons (loading placeholders). 2. **Hydration Phase** — Each widget independently calls `fetch_widget_data()` via `use_resource`. Widgets render as their data arrives. > **Tip:** This architecture means a slow external API (like a rate-limited GitHub endpoint) never blocks the page shell or faster widgets from rendering. ## Widget Sizes Widgets support three size presets in the 4-column grid: | Size | Grid Columns | Description | |------|-------------|-------------| | **Normal** | 1 column | Default compact card | | **Wide** | 2 columns | Extended view with more detail | | **Large** | 4 columns | Full-width with comprehensive data | Not every widget supports all sizes — each kind defines which sizes are allowed. ## Grid Editor In edit mode, infrapage uses **GridStack.js** for drag-and-drop widget arrangement: - **Drag** widgets to reposition them - **Resize** using edge handles (east and south) - **Delete** by dragging to the trash zone - **Double-click** to edit widget configuration The grid layout is saved to MongoDB when you exit edit mode. --- ## [Available Widgets](https://github.com/hauju/infrapage/docs/widgets/available-widgets) # Available Widgets infrapage ships with 18 widget types across several categories. Each widget fetches data from an external source and displays it as a card on your dashboard. ## Visibility at a glance Every widget has a visibility setting — **Public** (visible to everyone), **Private** (admin only), or **Hidden** (not rendered). When you add a new widget, infrapage picks a safe default based on what the widget exposes: - Widgets showing **public signals** (repo stars, uptime, deploys, community) default to **Public** so you can build in public. - Widgets showing **revenue, infra metrics, error rates, user counts, or customer feedback** default to **Private** — flip them public only after you've reviewed what they render. You can change the visibility of any widget at any time from the widget's edit modal. ## GitHub **GitHub Repo** Displays repository information including stars, forks, language, and description. Configure with owner and repo name. **Default: Public** — public GitHub metadata. **GitHub Pipeline** Shows the latest CI/CD pipeline status for a repository. Displays workflow name, status, and last run time. **Default: Public** — build status is usually shareable. **GitHub Commit Graph** Renders a commit activity graph similar to GitHub's contribution chart. Best displayed at Wide or Large size. **Default: Public** — activity counts only, no sensitive content. ## Monitoring **Uptime Monitor** Simple HTTP uptime check. Sends a HEAD request to the configured URL and shows response time and status. **Default: Public** — status pages are typically public. **Uptime Kuma** Integrates with an Uptime Kuma instance to show monitor status, uptime percentage, and response times. **Default: Public** — intended for public status pages. **Beszel** Connects to a Beszel server monitoring instance displaying CPU, memory, and disk usage metrics. **Default: Private** — infrastructure metrics can reveal capacity and outage patterns. ## Analytics **Umami Stats** Displays website analytics from a self-hosted Umami instance — visitors, pageviews, and bounce rate. **Default: Public** — common as social proof, but set to Private if your traffic is sensitive. **Google Search Console** Shows search performance data including clicks, impressions, and average position. **Default: Private** — search queries can contain PII and reveal traffic sources. ## Community **Discord Server** Shows Discord server info including member count, online members, and server name. **Default: Public** — server info is typically shareable. **X Followers** Shows an X (Twitter) account's follower count, live from the official X API via your own developer app's Bearer Token. **Default: Public** — follower counts are already public on x.com. **Polar Overview** Displays Polar.sh monetization data — MRR, subscribers, and revenue metrics. **Default: Private** — revenue data; opt in to Public explicitly if you share it. ## Error Tracking **Sentry Overview** Integrates with Sentry to display recent error counts, unresolved issues, and project health status. **Default: Private** — error rates can signal product health negatively; review before exposing. ## Authentication **Zitadel Users** Shows user count and recent registrations from a connected Zitadel identity provider. **Default: Private** — user counts and growth trends are business-sensitive. ## Deployments **Vercel Deployment** Displays the latest Vercel deployment status for a project, including branch, commit SHA, and preview URL. **Default: Public** — deploy status is typically public. ## Custom **Custom Link** A simple link card with a title, description, and URL. Useful for linking to external dashboards or documentation. **Default: Public** — fully user-controlled content. **Status Badge** Displays a status badge from any badge URL (shields.io, custom badges, etc.). **Default: Public** — pure status indicator. ## SeggWat Integration **SeggWat Feedback** Shows recent user feedback from a SeggWat instance. **Default: Private** — customer feedback may contain names or PII. **SeggWat Ratings** Displays aggregated ratings and satisfaction scores. **Default: Private** — satisfaction metrics are usually internal. **SeggWat Summary** Overview of SeggWat feedback metrics and trends. **Default: Private** — derived from confidential feedback. --- ## [Adding Widgets](https://github.com/hauju/infrapage/docs/guides/adding-widgets) # Adding Widgets This guide walks you through adding widgets to a dashboard page using the admin interface. ## Create a Page Before adding widgets, you need a page to put them on. 1. **Open the Dashboard** Navigate to `/dashboard` in your browser. If authentication is configured, you'll need to log in first. 2. **Create a New Page** Click the **"New Page"** button. Enter a title and a URL slug (e.g., `my-project`). The page will be accessible at `/p/my-project`. 3. **Open the Page Editor** Click on your new page to open the page editor. ## Add a Widget 1. **Enter Edit Mode** Click the **"Edit"** button in the page editor toolbar. The grid will switch to edit mode with dashed outlines around existing widgets. 2. **Click Add Widget** Click the **"+"** button to open the widget catalog. You'll see all available widget types organized by category. 3. **Choose a Widget Type** Select the widget type you want to add. For example, choose **"GitHub Repo"** to show repository information. 4. **Configure the Widget** Fill in the required configuration fields. For a GitHub Repo widget, enter the repository owner and name. Select a **size** for the widget (Normal, Wide, or Large) based on how much space you want it to occupy. 5. **Save** Click **"Add"** to place the widget on the grid. You can then drag it to reposition or resize it. ## Arrange Widgets In edit mode, you can: - **Drag** widgets to move them around the grid - **Resize** by pulling the edge handles - **Delete** by dragging a widget to the trash zone at the bottom - **Edit** by double-clicking a widget to change its configuration > **Tip:** Widget positions are saved automatically when you click **"Save Layout"** or exit edit mode. ## Quick Add Some widget types support **quick add** from the widget catalog. These come with sensible defaults so you can add them with a single click and configure later. > **Note:** Widgets that require integration tokens (like GitHub or Polar) need the corresponding integration to be configured in **Settings** before they can fetch data. --- ## [Custom Domains](https://github.com/hauju/infrapage/docs/guides/custom-domains) # Custom Domains Map your page to your own domain so visitors see `status.myproject.com` instead of `infrapage.io/p/my-project`. SSL is provisioned automatically — there's nothing to configure beyond a single DNS record. > **Note:** Custom domains are a **Pro** feature. Upgrade your account before adding one. ## Setup 1. **Add your domain** Open the **page editor** for the page you want to map and find the **Custom Domains** section. Enter your domain and click **Add**. Use a subdomain such as `status.yourdomain.com` — apex domains (e.g. `yourdomain.com` with no subdomain) aren't supported. The domain starts in the **Pending Verification** state. 2. **Add the CNAME record** After adding the domain, infrapage shows you a **CNAME target**. Go to your DNS provider and create a CNAME record pointing your subdomain at that target: ``` status.yourdomain.com CNAME ``` The exact target is unique to this instance, so always copy it from the page editor rather than guessing. 3. **Verify DNS** Back in the editor, click **Verify DNS**. infrapage checks that your CNAME is live, then requests an SSL certificate for the domain. The status moves to **Provisioning SSL** and, once the certificate is issued, to **Active**. From that point your page is served on your domain over HTTPS. > **Warning:** DNS changes can take up to 24 hours to propagate. If **Verify DNS** doesn't succeed immediately, wait for propagation and try again — the domain stays pending until the CNAME is live. ## Status reference | Status | Meaning | |--------|---------| | **Pending Verification** | Domain added; waiting for you to point the CNAME and verify. | | **Provisioning SSL** | DNS verified; the SSL certificate is being issued. | | **Active** | Live and serving your page over HTTPS. | | **Failed** | Something went wrong — re-check the CNAME record and verify again. | ## Multiple domains Each page can have one custom domain. To run several branded pages, set a different domain on each page. ## Removing a custom domain Delete the domain in the page editor's **Custom Domains** section. The page stays reachable at its original `/p/slug` URL. --- ## [Integrations](https://github.com/hauju/infrapage/docs/guides/integrations) # Integrations infrapage connects to external services through integration tokens. Configure these in the admin **Settings** page to enable data fetching for widgets. ## GitHub Used by: **GitHub Repo**, **GitHub Pipeline**, **GitHub Commit Graph** widgets. 1. **Create a Personal Access Token** Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) and create a **fine-grained token** with read access to the repositories you want to display. 2. **Add to infrapage** Navigate to **Settings > Integrations** in the admin dashboard and paste your GitHub token. Alternatively, set the `GITHUB_TOKEN` environment variable to seed it on first startup. > **Tip:** The GitHub token is stored encrypted in the `integration_secrets` MongoDB collection. It is never exposed to the client. ## Polar Used by: **Polar Overview** widget. Configure your Polar API token to display monetization data like MRR and subscriber counts. 1. **Create an Organization Access Token** In the [Polar dashboard](https://polar.sh/dashboard), open the organization you want to track and go to **Settings → General → Developers**, then click **Create token**. Tokens are scoped to a single organization — you'll need one token per Polar org. 2. **Add to infrapage** Navigate to **Settings > Integrations** in the admin dashboard and paste your Polar token. Alternatively, set the `POLAR_TOKEN` environment variable to seed it on first startup. To track multiple orgs, add one credential per organization. ## Discord Used by: **Discord Server** widget. The Discord widget uses public server invite data — no bot token is required. Just configure the widget with your server's invite code or ID. ## X (Twitter) Used by: **X Followers** widget. The widget reads a profile's public follower count from the official X API (`GET /2/users/by/username/:username`) using the app-only Bearer Token from your own X developer app. X's API is pay-per-use — each lookup bills a fraction of a cent to your developer account — and infrapage caches fetches for six hours, so the cost stays at cents per month. 1. **Create an X Developer App** Sign in at the [X developer portal](https://developer.x.com/en/portal/dashboard), add credits if your account has none, and create a Project with an App inside it. 2. **Generate a Bearer Token** Open the app's **Keys and tokens** tab, generate the **Bearer Token**, and copy it — it won't be shown again. Note: only this app-only Bearer Token is supported; user-context tokens (`/users/me`) are not needed. 3. **Add to infrapage** Navigate to **Settings > Integrations > X (Twitter)** and paste the Bearer Token, then click **Test** to verify the connection (one billed lookup of the official @X account). When adding the widget, enter the handle to display — usually your own. ## Umami Analytics Used by: **Umami Stats** widget. Configure the widget with: - Your Umami instance URL - Website ID - API credentials (if authentication is enabled on your Umami instance) ## Sentry Used by: **Sentry Overview** widget. Configure with your Sentry organization slug, project slug, and an auth token with `project:read` scope. ## Uptime Kuma Used by: **Uptime Kuma** widget. Configure with your Uptime Kuma instance URL. The widget reads from Uptime Kuma's public status page API. ## Beszel Used by: **Beszel** widget. Configure with your Beszel server URL and authentication credentials to display system metrics. ## Google Search Console Used by: **Google Search Console** widget. Requires OAuth credentials for the Google Search Console API. Configure with your site URL and API credentials. > **Note:** Integration tokens are stored encrypted in MongoDB. Each integration kind has one document in the `integration_secrets` collection. --- ## [Requirements](https://github.com/hauju/infrapage/docs/self-hosting/requirements) # Requirements infrapage is designed to be lightweight and easy to deploy. Here's what you need. ## Required **MongoDB** infrapage uses MongoDB as its primary database. Any MongoDB 5.0+ instance works — local, Atlas, or self-hosted. Three collections are used: `pages`, `integration_secrets`, and `users`. **Runtime** Either **Docker** (recommended) or a **Rust toolchain** to build from source. The Docker image is the easiest way to deploy. Building from source requires Rust nightly and the Dioxus CLI. ## Optional **Valkey / Redis** Used for session storage and widget data caching. Without it, sessions are stored in-memory (lost on restart) and widgets refetch on every request. Any Redis-compatible server works (Redis, Valkey, KeyDB, DragonflyDB). **Zitadel** OIDC identity provider for authentication. Without it, infrapage runs in open mode — all admin endpoints are accessible without login. Any OIDC provider could work with minor adjustments, but Zitadel is the tested integration. ## System Requirements | Resource | Minimum | Recommended | |----------|---------|-------------| | **RAM** | 128 MB | 256 MB | | **CPU** | 1 core | 2 cores | | **Disk** | 100 MB | 500 MB | | **Network** | Outbound HTTPS | Outbound HTTPS | > **Tip:** infrapage is very lightweight. The compiled binary is a single executable that serves both the API and the WASM client. Memory usage is typically under 100 MB. ## Ports By default, infrapage listens on port **8080** on all interfaces (`0.0.0.0`). Configure with `INFRAPAGE_HOST` and `INFRAPAGE_PORT` environment variables. --- ## [Configuration](https://github.com/hauju/infrapage/docs/self-hosting/configuration) # Configuration infrapage is configured through environment variables. Set them in a `.env` file or pass them to your Docker container. ## Database | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `MONGODB_URI` | Yes | — | MongoDB connection string (e.g., `mongodb://localhost:27017`) | | `MONGODB_DATABASE` | No | `infrapage` | Database name to use | ## Authentication | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `FERRISKEY_URL` | For auth | — | FerrisKey base API URL (e.g., `http://localhost:3333` or `https://auth.example.com/api`) | | `FERRISKEY_ISSUER_URL` | No | Derived from `FERRISKEY_URL` | FerrisKey public OIDC issuer base URL (e.g., `https://auth.example.com`) | | `FERRISKEY_REALM` | For auth | — | FerrisKey realm name (e.g., `infrapage`) | | `FERRISKEY_CLIENT_ID` | For auth | — | FerrisKey OIDC client ID | | `FERRISKEY_CLIENT_SECRET` | For auth | — | FerrisKey confidential client secret (used for both authorization-code exchange and the client-credentials grant for user lookups) | | `BASE_URL` | For auth | — | App base URL for redirects (e.g., `https://app.example.com`) | | `SECURE_COOKIES` | No | `false` | Set to `true` for HTTPS production deployments | | `TRUST_PROXY_HEADERS` | No | `false` | Set to `true` only when infrapage is behind a trusted reverse proxy that strips client-supplied forwarding headers | > **Note:** If any of the `FERRISKEY_*` variables is **not** set, authentication is completely disabled. All admin endpoints will be accessible without login. This is useful for local development and private deployments behind a VPN. ## Session Storage | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `VALKEY_URI` | No | — | Redis/Valkey URI for persistent session storage and widget caching (e.g., `redis://localhost:6379`) | Without `VALKEY_URI`, sessions are stored in-memory and will be lost when the server restarts. ## Integration Tokens | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `GITHUB_TOKEN` | No | — | GitHub personal access token — seeded into the database on first run | | `POLAR_TOKEN` | No | — | Polar API token — seeded into the database on first run | > **Tip:** These environment variables only seed the token into MongoDB on the very first startup. After that, manage tokens through the **Settings** page in the admin dashboard. ## Server | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `INFRAPAGE_HOST` | No | `0.0.0.0` | Server bind address | | `INFRAPAGE_PORT` | No | `8080` | Server port | ## Example .env File ```bash # Required MONGODB_URI=mongodb://localhost:27017 MONGODB_DATABASE=infrapage # Optional: Authentication FERRISKEY_URL=http://localhost:3333 # FERRISKEY_ISSUER_URL is optional; set it when your API URL and issuer URL differ. # FERRISKEY_ISSUER_URL=https://auth.example.com FERRISKEY_REALM=infrapage FERRISKEY_CLIENT_ID=infrapage-dashboard FERRISKEY_CLIENT_SECRET=your-client-secret BASE_URL=https://app.example.com SECURE_COOKIES=true TRUST_PROXY_HEADERS=true # Optional: Session storage VALKEY_URI=redis://localhost:6379 # Optional: Integration tokens (seeded on first run) GITHUB_TOKEN=ghp_xxxxxxxxxxxx POLAR_TOKEN=polar_xxxxxxxxxxxx # Optional: Server config INFRAPAGE_HOST=0.0.0.0 INFRAPAGE_PORT=8080 ``` --- ## [Deployment](https://github.com/hauju/infrapage/docs/self-hosting/deployment) # Deployment infrapage can be deployed as a Docker container or as a standalone binary. This guide covers both approaches. ## Docker Compose (Recommended) The easiest way to deploy infrapage with all dependencies: ```yaml version: "3.8" services: infrapage: image: ghcr.io/hauju/infrapage:latest ports: - "8080:8080" environment: MONGODB_URI: mongodb://mongo:27017 MONGODB_DATABASE: infrapage VALKEY_URI: redis://valkey:6379 INFRAPAGE_HOST: "0.0.0.0" INFRAPAGE_PORT: "8080" depends_on: - mongo - valkey restart: unless-stopped mongo: image: mongo:7 volumes: - mongo_data:/data/db restart: unless-stopped valkey: image: valkey/valkey:8 volumes: - valkey_data:/data restart: unless-stopped volumes: mongo_data: valkey_data: ``` 1. **Save the Compose File** Save the above as `docker-compose.yml` in a new directory. 2. **Create Environment File** Create a `.env` file next to it with any additional configuration (auth tokens, etc.). 3. **Start Services** ```bash docker compose up -d ``` 4. **Verify** Open [http://localhost:8080/dashboard](http://localhost:8080/dashboard) to confirm everything is running. ## Reverse Proxy For production deployments, put a reverse proxy in front of infrapage for TLS termination and custom domain support. #### Caddy Caddy provides automatic HTTPS with Let's Encrypt: ``` app.example.com { reverse_proxy infrapage:8080 } # Custom domain for a page status.myproject.com { reverse_proxy infrapage:8080 } ``` #### nginx ```nginx server { listen 443 ssl; server_name app.example.com; ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem; location / { proxy_pass http://infrapage:8080; 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; } } ``` > **Warning:** When using a reverse proxy, make sure to set `SECURE_COOKIES=true` and configure `BASE_URL` with your public HTTPS URL. The `Host` header must be forwarded correctly for custom domains to work. ## Production Tips - **Enable authentication** — Set up Zitadel or another OIDC provider to protect admin endpoints - **Use Valkey/Redis** — Persistent sessions and widget caching significantly improve the experience - **Set `SECURE_COOKIES=true`** — Required when serving over HTTPS - **Monitor MongoDB** — infrapage creates indexes automatically, but monitor collection sizes as your dashboard grows - **Back up MongoDB** — The `pages` collection contains all your dashboard configurations