# Deploy a hosted board

A hosted board is two pieces: the kanban, static on GitHub Pages, and
`folio-board-server` on Render, which signs people in with GitHub and commits
their edits to the board's branch. One GitHub App does both jobs: people sign
in with it, and the server writes the repository as it. Nothing here costs
money for an ephemeral GitHub-only service. Durable authorization and SQLite
require a paid persistent disk; see [Render and Docker](/docs/deployment/containers).
GitHub refresh runs automatically when supported by the App.

```text
GitHub Pages (the kanban) ── Sign in with GitHub ──▶ Render: /auth/login ──▶ GitHub
        ▲                                                  │
        └──────── #board-code=<one-time code> ◀────────────┘ /auth/callback
kanban ── Authorization: Bearer <session> ──▶ Render: /v1/… ── commit, push ──▶ board branch
```

**Who gets in**

  Anyone can sign in and connect their repositories. A selected repository
  must be authorized for both the person and the App. Push access enables
  editing; read access opens the board without edits. Each commit
  carries the signed-in person as its author and the server as its
  committer. A session lasts 8 hours.

## Set it up once

### Create the GitHub App

GitHub → Settings → Developer settings → GitHub Apps → **New GitHub App**.

| Field | Value |
| --- | --- |
| GitHub App name | Any free name, e.g. `folio-board` |
| Homepage URL | The site, e.g. `https://owner.github.io/repo/` |
| Redirect URI | `https://<service>.onrender.com/auth/callback` |
| Expire user authorization tokens | On |
| Request user authorization during installation | Off |
| Webhook → Active | Off |
| Repository permissions → Contents | Read and write |
| Where can it be installed | Any account |

Metadata: Read-only comes with Contents. Leave every other permission at No
access. After creating it, note the **Client ID**, generate a **client
secret**. Account mode uses each person's user access token and does not need
a private key. An existing App must also allow installation by **Any account**.

### Connect your repositories

The App's settings → **Install App** → your account → **Only select
repositories** → the repositories you want to open. Each new user does this for their own account. The screen must list read and write
access to code; if it does not, the permission was not saved.

### Create the service on Render

Render → New → **Blueprint** → connect GitHub and this repository. Render
reads `render.yaml`, builds the `Dockerfile` and proposes the `folio-board`
service on the free plan. Folio Docs comes from its public repository, so the
build needs no credentials.

| Setting | Value |
| --- | --- |
| `FOLIO_BOARD_MULTI_REPO` | `true`: each person chooses their own repository |
| `FOLIO_BOARD_GITHUB_APP_SLUG` | The App URL slug, `folio-board` for the deployed App |
| `FOLIO_BOARD_ORIGIN` | The site's origin, e.g. `https://owner.github.io` |
| `FOLIO_BOARD_PUBLIC_URL` | The service's URL, `https://<service>.onrender.com` |
| `FOLIO_BOARD_GITHUB_CLIENT_ID` | The App's client id |
| `FOLIO_BOARD_GITHUB_CLIENT_SECRET` | The App's client secret |
| `FOLIO_BOARD_SESSION_KEY` | Generated by Render; changing it signs everyone out |

If Render names the service differently, set `FOLIO_BOARD_PUBLIC_URL` and
the App's Redirect URI to the URL it gives. Account mode is the default on existing services too; set
`FOLIO_BOARD_MULTI_REPO=false` only for a dedicated single-repository server.

For a dedicated single-repository server, set `FOLIO_BOARD_MULTI_REPO=false`, set
`FOLIO_BOARD_REPO`, `FOLIO_BOARD_REF` and `FOLIO_BOARD_PATH`, and supply the
App key or `FOLIO_BOARD_GITHUB_TOKEN`.

### Point the kanban at the server

In the site's `docs.yaml`, then rebuild and publish the site:

```yaml
kanban:
  api:
    url: https://<service>.onrender.com
```

The published board now offers **Sign in with GitHub**.

### Check it

`https://<service>.onrender.com/auth/login?return_to=<a page of the site>`
answers with a redirect to GitHub, and `/v1/board` without a session answers
`401`. Sign in with a second GitHub account that cannot access this project's
repository: it must still reach the repository selector. Connect one of that
account's repositories, select its board branch and directory, and open it.
A save must reach only the selected repository, under that account's name.
Verify that the second account cannot open the first account's private repo.

**The free plan sleeps**

  After a while without requests Render stops the service, and the next
  request waits while it starts. Free services have ephemeral storage; their
  authorization database disappears on replacement. Use a persistent disk to
  preserve SQLite boards, API tokens and encrypted credentials across deployments.
  Expired browser sessions reconnect through GitHub.

## When it fails, the log says why

Render → the service → **Logs**. The server stops at start with one line that
names what to fix, and it logs every sign-in GitHub refuses.

| The log says | Fix |
| --- | --- |
| `… is not set; a hosted server reads it from the environment` | Set the variable it names in Render |
| `neither FOLIO_BOARD_GITHUB_APP_KEY, FOLIO_BOARD_GITHUB_APP_KEY_FILE nor …` | Add the secret file `github-app.pem` |
| `the GitHub App private key is not an RSA PEM` | Paste the whole `.pem`, both `-----` lines included |
| `GitHub refused the App's JWT (401)` | The client id and the private key are not the same App's |
| `the GitHub App is not installed on owner/name (404)` | Install it on that repository, or add it under Repository access |
| `the GitHub App cannot write owner/name` | Contents: Read and write, then accept the request on the installation |
| `sign-in: GitHub refused it: …` | The Redirect URI differs from `FOLIO_BOARD_PUBLIC_URL/auth/callback`, or the client secret is wrong |
| Repository list is empty | Connect the App to repositories accessible to this GitHub account |
| A second account cannot authorize or install the App | Change the App installation setting to Any account |
| Selector never appears after deploy | Remove `FOLIO_BOARD_MULTI_REPO=false` in Render, redeploy, and sign in again |

In the browser, a kanban that stays **read-only** gives its reason in the
status mark's tooltip, and a page that never offers the sign-in has no
`kanban.api` in its build.

## Persistent container deployments

The current Docker entrypoint runs `folio-board-server container`, serializing
its configuration from environment values. It includes the remote CLI and MCP.
The committed Render Blueprint adds a paid persistent disk for SQLite, token
hashes and encrypted credentials. Review [Render and Docker](/docs/deployment/containers)
before applying it to an existing service.
