# SQLite

SQLite stores the board in one database on your server. The existing API reads
cards and saves moves, fields, descriptions, criteria, comments and artifacts.
Each write creates an immutable revision with its actor and message. No Git
checkout is required. Choose authentication separately: the normal GitHub login
works with SQLite, and token access remains an option for your own installation.

[Compare storage methods](/docs/deployment/storage) for advantages, tradeoffs
and the available CLI, skills and API tools.

## Use GitHub login with SQLite and Git sources

One login can open both GitHub repositories and configured SQLite databases.
GitHub sources use repository permissions; databases use their own access list.
In the server's `agents.yaml`:

```yaml
board:
  auth:
    provider: github
  server:
    bind: "0.0.0.0:4350"
    origins: ["https://board.example.com"]
    hosted:
      public_url: "https://api.example.com"
      github_client_id: "YOUR_GITHUB_CLIENT_ID"
  sources:
    - id: roadmap
      title: Roadmap
      store:
        kind: sqlite
        path: /var/data/boards/roadmap.sqlite
      access:
        readers: ["github:1234"]
        writers: ["github:5678"]
```

Set `FOLIO_BOARD_GITHUB_CLIENT_SECRET` and `FOLIO_BOARD_SESSION_KEY` as in
[Authentication](/docs/deployment/auth). The client uses
`kanban.api: {url: "https://api.example.com", auth: github}`. Sign in normally,
then add the database in **Profile → Settings → Sources**. Repository sources
and SQLite cards share the same canvas and filters.

Access entries use stable provider subjects, such as `github:1234`, rather than
changeable usernames. Find your GitHub id with `gh api user --jq .id`.
Writers can also read. Access defaults to nobody; `"*"` explicitly allows every
signed-in account in that list. Each read and write checks the source's current
permissions. Database writes record the signed-in person's identity. Database
paths stay on the server; clients select the stable source id.

Add more database entries to `board.sources`. An optional `seed: board` imports
that source's Markdown once. Neither cards nor their revisions are sent to
GitHub merely because GitHub provides the login.

## Configure standalone token access

In the server's `agents.yaml`:

```yaml
board:
  auth:
    provider: token
  store:
    kind: sqlite
    path: data/board.sqlite
  server:
    bind: "127.0.0.1:4350"
    origins: ["http://localhost:3000"]
```

The database path is relative to the project directory; an absolute path also
works. A new database starts with empty Ideas, Todo, In progress, In review and
Done columns. Add `board.source: board` to import an existing Markdown board
once. That folder needs `board.yaml` and its cards; it does not need Git.
Hidden files are excluded and symlinks are refused. On later starts the
database is authoritative and the seed folder is not read.

`folio-agents board` and the board skills still operate Markdown files; they
do not edit this database. The import is one-time, so later CLI edits to the
seed folder do not change SQLite. Use the browser or HTTP API after importing.

Start the server:

```bash
folio-board-server api --project-dir .
```

Local mode prints a bearer token. In the site's `docs.yaml`, select token
authentication explicitly:

```yaml
kanban:
  source: fixtures/board/board
  routes:
    public: "/board"
  api:
    url: "http://127.0.0.1:4350"
    auth: token
```

Build the page with `folio-agents build`. **Connect to your board** accepts the
token. The page shows cards only after an authorized backend read. The token
stays in the tab's session storage, scoped to the API address. It is never
placed in a query string or saved with browser preferences. Cards identify
their database source, so the existing `source:` and `project:` filters work.

Create cards directly through the SQLite API, including on an empty database:

```bash
curl -X POST http://127.0.0.1:4350/v1/cards \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  --data '{"id":"first-idea","markdown":"---\ntitle: First idea\nstatus: ideas\n---\n"}'
```

The id is a lowercase slug. The server validates the Markdown and board
relationships, refuses duplicate ids, and creates one atomic revision.

## Run it behind HTTPS

Use a reverse proxy with TLS and configure the server's public address:

```yaml
board:
  auth:
    provider: token
  store:
    kind: sqlite
    path: /var/lib/folio/board.sqlite
    token_env: FOLIO_BOARD_TOKEN
  server:
    bind: "0.0.0.0:4350"
    origins: ["https://board.example.com"]
    hosted:
      public_url: "https://api.example.com"
```

Set `FOLIO_BOARD_TOKEN` in the server environment to a random hex token of at
least 32 characters. The server requires it for HTTPS mode and does not print
it. `--token` can also supply it. The site's `kanban.api.url` becomes
`https://api.example.com`; keep `auth: token`. No OAuth client id or GitHub
secret is needed. Set the browser origin exactly, without a trailing slash.

Standalone token mode serves one database workspace. The bearer grants access
to that workspace; writes use the SQLite service identity. `board.server.read_only:
true` opens an initialized database without allowing changes. The API checks
the configured host, the allowed browser origins and the bearer independently.

## Keep the database on persistent storage

Mount a persistent volume at the database directory. Container rebuilds and
server restarts reopen the same file. Hosting with an ephemeral filesystem
does not preserve the board across redeployments.

SQLite uses WAL journaling, a busy timeout and transactions for writes. A
revision and all its file changes commit together. A stale revision or file
hash refuses the entire write; the normal API conflict response applies.
Unchanged file contents are shared between revisions.

Use SQLite's [online backup](https://www.sqlite.org/backup.html) mechanism for
backups while the server is running. Keep the database and its WAL files on
the same persistent volume.

Filters, named filters, appearance and layout remain browser preferences.
The database persists board content and revision history; it does not sync
personal settings between devices. Backend synchronization appears in Settings.

## Deploy the database with its backend

[Render and Docker](/docs/deployment/containers) keeps SQLite and authorization
on persistent storage, serves MCP and includes the remote CLI. Render uses a
paid disk; Docker Compose uses a named volume. [API tokens](/docs/deployment/tokens)
connect tools without replacing the normal web login.
