# Demo environment

> scripts/demo workflows (Docker vs macOS native), seed data, demo.sh start/shell/stop, and sample exploration commands.

- Repository: timescale/tigerfs
- GitHub: https://github.com/timescale/tigerfs
- Human docs: https://grok-wiki.com/public/docs/timescale-tigerfs-60719456a5c3
- Complete Markdown: https://grok-wiki.com/public/docs/timescale-tigerfs-60719456a5c3/llms-full.txt

## Source Files

- `scripts/demo/demo.sh`
- `scripts/demo/README.md`
- `scripts/demo/init.sql`
- `scripts/demo/seed.sh`
- `test/integration/demo_data.go`

---

---
title: "Demo environment"
description: "scripts/demo workflows (Docker vs macOS native), seed data, demo.sh start/shell/stop, and sample exploration commands."
---

The `scripts/demo/` tree provides a self-contained TigerFS playground: PostgreSQL loads relational seed data from `init.sql`, `demo.sh` mounts the database, and `seed.sh` creates synthesized file-first apps through the real `.build/` mount path.

## Layout

```text
scripts/demo/
  demo.sh                 # start | stop | status | restart | shell
  init.sql                # data-first tables (PostgreSQL init)
  seed.sh                 # file-first apps (via mount)
  README.md
  docker/
    docker-compose.yml    # postgres + tigerfs (FUSE)
    Dockerfile            # TigerFS image + tooling
  mac/
    docker-compose.yml    # postgres only (TigerFS on host)
```

## Runtime modes

`demo.sh` picks a mode from flags or the host OS. Darwin defaults to macOS native NFS; other platforms default to Docker FUSE.

| Mode | Flag | PostgreSQL | TigerFS | Mount path | Connection string |
|------|------|------------|---------|------------|-------------------|
| Docker | `--docker` (default on Linux) | Container | Container (FUSE) | `/mnt/db` (inside `tigerfs`) | `postgres://demo:demo@postgres:5432/demo` |
| macOS native | `--mac` (default on macOS) | Container on `localhost:5432` | Host binary (`bin/tigerfs`) | `/tmp/tigerfs-demo` | `postgres://demo:demo@localhost:5432/demo` |

Both modes mount with `tigerfs mount --insecure-no-ssl` (demo-only TLS bypass for localhost/Docker). Pass `--debug` on any command to add `--log-level debug` to the mount.

```mermaid
sequenceDiagram
  participant User
  participant demo_sh as demo.sh
  participant Compose as docker compose
  participant PG as postgres (timescaledb-ha:pg18)
  participant TFS as TigerFS
  participant Seed as seed.sh

  User->>demo_sh: start
  demo_sh->>Compose: up -d
  Compose->>PG: init.sql on first boot
  demo_sh->>PG: wait pg_isready
  demo_sh->>TFS: mount CONN_STR MOUNTPOINT
  demo_sh->>Seed: seed.sh MOUNTPOINT
  Seed->>TFS: .build/* + file writes
```

## Commands

Run all commands from `scripts/demo/` (repository-relative paths below assume that cwd).

<Steps>
<Step title="Start">
```bash
./demo.sh start
# or: ./demo.sh start --docker
# or: ./demo.sh start --mac
```

Starts PostgreSQL, waits for init (`PostgreSQL init process complete` in logs, then `pg_isready`), mounts TigerFS, and runs `seed.sh`. Exits with an error if the mount is already active.
</Step>
<Step title="Explore">
On macOS, use the host mount directly:

```bash
ls /tmp/tigerfs-demo/
cat /tmp/tigerfs-demo/users/1.json
```

In Docker mode, enter the container at the mount root:

```bash
./demo.sh shell
# working directory: /mnt/db
ls
cat users/1.json
```
</Step>
<Step title="Stop">
```bash
./demo.sh stop
```

Docker: `umount` inside the container, then `docker compose down`. macOS: `pkill` tigerfs for the mountpoint, `umount` / `diskutil unmount force` on `/tmp/tigerfs-demo`, then stop the postgres container.
</Step>
</Steps>

| Command | Behavior |
|---------|----------|
| `start` | Compose up, wait for DB, mount, seed file-first apps |
| `stop` | Unmount TigerFS, tear down containers / processes |
| `status` | Report container and mount state |
| `restart` | `stop`, pause, `start` |
| `shell` | Interactive shell at mount root (`/mnt/db` or `/tmp/tigerfs-demo`) |

<Warning>
`start` refuses to run if TigerFS is already mounted at the mode’s mountpoint. Run `./demo.sh stop` first.
</Warning>

## Seed data

Demo content splits into two phases: SQL at database init, filesystem writes after mount.

### Data-first (`init.sql`)

Loaded by PostgreSQL via `docker-entrypoint-initdb.d/init.sql` on first container start (`timescale/timescaledb-ha:pg18`, user/password/database `demo`).

| Object | Rows / notes |
|--------|----------------|
| `users` | 1,000 — `SERIAL` PK, composite index on `(last_name, first_name)` |
| `categories` | 10 — `TEXT` slug PK |
| `products` | 200 — `SERIAL` PK, FK to `categories` |
| `orders` | 8,000 — `UUID` PK with `uuidv7()` default |
| `product_inventory` | ~600 — composite PK `(product_id, warehouse)` |
| `product_views` | ~50,000 — TimescaleDB hypertable with columnstore |
| `active_users` | Updatable view on `users` |
| `order_summary` | JOIN view (orders + users + products + categories) |

Also creates schema `tigerfs`, enables TimescaleDB, and indexes for `.by/` navigation (`idx_products_category`, `idx_orders_*`, `idx_users_name`, `idx_orders_status_date`, etc.).

File-first apps are intentionally **not** in `init.sql`; comments in that file point to `seed.sh`.

### File-first (`seed.sh`)

Argument: mount path (`seed.sh <mountpoint>`). Invoked by `demo.sh` after a successful mount.

| App | `.build/` spec | Content |
|-----|----------------|---------|
| `blog/` | `markdown,history` | 5 posts in `tutorials/`, `deep-dives/`; savepoints; history via rewrites |
| `docs/` | `markdown,history` | 4 pages in `getting-started/`, `reference/` |
| `snippets/` | `txt,history` | 3 plain-text files in `meetings/` |

Each app uses `echo 'format,history' > "$MOUNT/.build/<name>"` then `mkdir` and `cat` heredocs into `.md` or `.txt` files. Blog and docs pages are written twice to populate `.history/`.

## Sample exploration

### Data-first tables

```bash
# Row read
cat users/1.json

# Pipeline / export
cat products/.by/category/electronics/.export/json

# Sampling
ls orders/.sample/10/

# Composite index path (users)
ls users/.by/last_name.first_name/Smith/

# Views
cat active_users/.export/json
ls order_summary/.first/5/
```

### File-first apps

```bash
ls blog/
cat blog/hello-world.md
cat docs/getting-started/installation.md
cat snippets/todo.txt

# History (blog and docs)
ls docs/.history/getting-started/installation.md/
ls blog/.savepoint/
```

On macOS after `./demo.sh start --mac`, prefix paths with `/tmp/tigerfs-demo/`. In Docker `./demo.sh shell`, paths are relative to `/mnt/db`.

## Docker image details

The `tigerfs` service image (`scripts/demo/docker/Dockerfile`) builds `cmd/tigerfs` on Go 1.25, installs FUSE3, `psql`, and optional agent tooling: Node.js, `@anthropic-ai/claude-code`, `tiger-cli`, and `ghost`. The container runs **privileged** for FUSE.

<Info>
To use Claude Code inside the Docker demo, set `ANTHROPIC_API_KEY` before `start`, then `./demo.sh shell` and run `claude`.
</Info>

Compose wires `ANTHROPIC_API_KEY` from the host environment into the `tigerfs` service.

## Prerequisites

| Mode | Requirements |
|------|----------------|
| Docker | Docker with Compose v2, daemon running, privileged FUSE in container |
| macOS | macOS, Docker Desktop, Go 1.21+ (builds `bin/tigerfs` if missing) |

## Integration tests vs demo dataset

`test/integration/demo_data.go` seeds a **smaller, deterministic** dataset for automated tests (`DefaultDemoConfig`: 100 users, 20 products, 200 orders, fixed `BaseTimestamp`). It mirrors the same table shapes and `.by/` indexes but is not invoked by `demo.sh`. Full demo scale and `uuidv7()` orders live only in `init.sql`.

Use the demo environment for manual exploration; use `seedDemoData` in integration tests for CI-stable assertions.

## Troubleshooting

| Symptom | Mitigation |
|---------|------------|
| Port `5432` in use (macOS) | Stop local PostgreSQL (`brew services stop postgresql`), inspect with `lsof -i :5432` |
| Mount stuck at `/tmp/tigerfs-demo` | `./demo.sh status --mac`; `diskutil unmount force /tmp/tigerfs-demo`; then `./demo.sh stop` |
| Docker mount failed | Check `docker compose -f scripts/demo/docker/docker-compose.yml ps` and container logs |
| `Go is not installed` (macOS) | Install Go 1.21+ or pre-build `bin/tigerfs` at repo root |

PostgreSQL init runs only on a **fresh** volume. To reload `init.sql`, remove the compose volume (`docker compose down -v`) before `start`.

## Verification signals

After `./demo.sh start`:

- **macOS:** `mount | grep /tmp/tigerfs-demo` shows the NFS mount; `ls /tmp/tigerfs-demo` lists `users`, `products`, `categories`, `orders`, `blog`, `docs`, `snippets`.
- **Docker:** `./demo.sh status` reports containers `RUNNING` and mount `MOUNTED` at `/mnt/db`; `docker compose exec tigerfs ls /mnt/db` lists the same top-level dirs.

`seed.sh` ends with a summary of created apps; missing directories usually mean mount or seed failed earlier in `start`.

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First mount and explore workflows that reference `demo.sh` on Docker and macOS.
</Card>
<Card title="Installation" href="/installation">
Platform prerequisites (FUSE, NFS, Docker) before running the demo.
</Card>
<Card title="File-first workspaces" href="/file-first-workspaces">
How `blog/`, `docs/`, and `snippets/` map to backing tables and `.build/` specs.
</Card>
<Card title="Data-first exploration" href="/data-first-exploration">
Row reads, `.by/`, `.export/`, `.sample/`, and pipeline paths on seeded tables.
</Card>
<Card title="History, savepoints, and undo" href="/history-savepoints-undo">
`.history/`, `.savepoint/`, and version browsing on seeded markdown apps.
</Card>
<Card title="Synthesized apps" href="/synthesized-apps">
`.build/` format strings (`markdown,history`, `txt,history`) used by `seed.sh`.
</Card>
</CardGroup>
