# Cloud backends

> tiger: and ghost: prefix resolution, create/fork/list flows, default_backend config, and Tiger CLI credential requirements.

- 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

- `internal/tigerfs/backend/resolve.go`
- `internal/tigerfs/backend/tiger.go`
- `internal/tigerfs/backend/ghost.go`
- `internal/tigerfs/cmd/create.go`
- `internal/tigerfs/cmd/fork.go`
- `docs/adr/013-backend-prefix-scheme.md`

---

---
title: "Cloud backends"
description: "tiger: and ghost: prefix resolution, create/fork/list flows, default_backend config, and Tiger CLI credential requirements."
---

TigerFS connects to Tiger Cloud and Ghost by shelling out to their CLIs (`tiger`, `ghost`) through `internal/tigerfs/backend`. A `tiger:ID` or `ghost:ID` reference is parsed by `backend.Resolve()`, the backend returns a `postgres://` connection string, and mount/create/fork commands use that string while the mount registry stores `cli_backend` and `service_id` for later `info` and `fork` lookups.

## Prefix scheme

All cloud-backed references use an explicit prefix (ADR-013). `backend.Resolve()` is the single parser:

| Input pattern | `Resolve()` result | Caller behavior |
|---------------|-------------------|-----------------|
| `tiger:ID` | `TigerBackend`, ID after colon | Shell out to `tiger` CLI |
| `ghost:ID` | `GhostBackend`, ID after colon | Shell out to `ghost` CLI |
| `tiger:` or `ghost:` (empty ID) | Backend + empty ID | Auto-generate name (create/fork only) |
| `postgres://...` | `nil` backend, full URI | Direct PostgreSQL connection |
| Any other string | `nil` backend, bare string | `create`/`fork` use `default_backend`; `mount` does **not** |

**Path vs name:** Only arguments that **start with** `.` or `/` are filesystem paths (`isPath()` in fork; mount arg 2 is always a path). Names may contain slashes (e.g. `project/my-db`) without being treated as paths.

<Warning>
`mount` does not apply `default_backend` to bare IDs. You must use `tiger:ID`, `ghost:ID`, or `postgres://...`. A single argument without a prefix is interpreted as a **mountpoint** (connection from config/env), not a cloud service ID.
</Warning>

```text
  User ref                    backend.Resolve()          Next step
  ---------                   -----------------          ---------
  tiger:abc123        -->     TigerBackend, "abc123"     tiger db connection-string ...
  ghost:mydb          -->     GhostBackend, "mydb"       ghost connect ...
  postgres://h/db     -->     nil, full URI              connect directly
  my-db (create)      -->     nil, "my-db"               ForName(default_backend)
```

## Backend interface

`internal/tigerfs/backend` defines one interface implemented by `TigerBackend` and `GhostBackend`:

| Method | Purpose |
|--------|---------|
| `GetConnectionString(ctx, id)` | Resolve service ID → `postgres://` (with credentials) |
| `GetInfo(ctx, id)` | Live service metadata |
| `Create(ctx, opts)` | Provision new service |
| `Fork(ctx, sourceID, opts)` | Clone existing service |

`ForName("tiger"|"ghost")` maps config/registry `cli_backend` values to implementations. Empty name returns `ErrNoBackend`; unknown names error.

Credentials are **not** stored in TigerFS config for cloud mounts. Each operation invokes the backend CLI; connection strings are used immediately and registry entries store **sanitized** database URLs only.

## Tiger Cloud vs Ghost

| Behavior | Tiger CLI | Ghost CLI |
|----------|-----------|-----------|
| Connection string | `tiger db connection-string <id> --with-password` | `ghost connect <id>` (password from `~/.pgpass`) |
| Service info | `tiger service get <id> --output json` | `ghost list --json` (filter by ID; no per-ID get) |
| Create | `tiger service create [--name <n>] --output json --with-password` | `ghost create [--name <n>] --wait --json`, then `ghost connect` |
| Fork | `tiger service fork <id> --now [--name <n>] --output json --with-password` | `ghost fork <id> [--name <n>] --wait --json`, then `ghost connect` |
| Wait behavior | Waits by default (~30m); TigerFS does not pass `--no-wait` | Requires explicit `--wait` (TigerFS always passes it) |
| Fork strategy | Always `--now` | No strategy flag (current state) |
| Create/fork conn string | Included in JSON when `--with-password` | Fetched in a second `ghost connect` call |

TigerFS does not validate service names; the backend CLI owns format and error messages.

## Mount flow

```mermaid
sequenceDiagram
  participant User
  participant Mount as tigerfs mount
  participant Resolve as backend.Resolve
  participant BE as TigerBackend / GhostBackend
  participant CLI as tiger / ghost CLI
  participant Reg as mounts.json registry

  User->>Mount: tiger:ID [MOUNTPOINT]
  Mount->>Resolve: Resolve(connStr)
  Resolve-->>Mount: Backend, serviceID
  Mount->>BE: GetConnectionString(ctx, serviceID)
  BE->>CLI: connection-string / connect
  CLI-->>BE: postgres://...
  BE-->>Mount: connStr
  Mount->>Mount: mountFilesystem(connStr)
  Mount->>Reg: Register(cli_backend, service_id)
```

**Arguments:**

- Two args: `CONNECTION` + `MOUNTPOINT` (connection may be `tiger:`, `ghost:`, or `postgres://`).
- One arg with prefix: connection ref; mountpoint defaults to `{default_mount_dir}/{id}` (default `/tmp`).

On success, `registerMount()` writes `service_id` and `cli_backend` (`tiger` or `ghost`) to the mount registry for `info`, `fork`, and `status`.

## Create flow

```bash
tigerfs create [BACKEND:]NAME [MOUNTPOINT] [--no-mount] [--json]
```

| `NAME` argument | Backend | Service name |
|-----------------|---------|--------------|
| `tiger:my-db` | Tiger | `my-db` |
| `ghost:my-db` | Ghost | `my-db` |
| `my-db` | `default_backend` | `my-db` |
| `tiger:` or omitted (with default) | From prefix/config | Auto-generated |

After `b.Create()`:

- Without `--no-mount`: spawns detached `tigerfs mount {cli}:{serviceID} {mountpoint}` (`startMountProcess`).
- Mountpoint: explicit arg, else `{default_mount_dir}/{name}`.
- `--no-mount`: prints `tigerfs mount {cli}:{id} /path/...` hint.

<ParamField body="--no-mount" type="boolean">
Create the cloud service but do not start a background mount process.
</ParamField>

<ParamField body="--json" type="boolean">
Emit create result including `backend`, `connection_string`, and `no_mount`.
</ParamField>

## Fork flow

```bash
tigerfs fork SOURCE [DEST] [--name NAME] [--no-mount] [--json]
```

**SOURCE resolution (first argument):**

| SOURCE form | Resolution |
|-------------|------------|
| Path (starts with `.` or `/`) | Mount registry lookup → `cli_backend` + `service_id` |
| `tiger:ID` / `ghost:ID` | `backend.Resolve()` |
| Bare name | `default_backend` + name as service ID |
| `postgres://` mount (no cloud metadata) | Error: cannot fork — no cloud service |

**DEST resolution (optional second argument):**

| DEST form | Service name | Mountpoint |
|-----------|--------------|------------|
| Path | `--name` or basename of path | Absolute path |
| Bare name | DEST (or `--name`) | `{baseDir}/{name}` |
| Omitted | Auto (CLI) | After fork: `{baseDir}/{result.Name}` |

`baseDir` is `default_mount_dir`, or the parent directory of the source mountpoint when SOURCE was a path.

Fork requires a cloud-backed source. Raw `postgres://` mounts have empty `cli_backend` in the registry and cannot be forked through TigerFS.

## List, status, and info

| Command | Scope |
|---------|--------|
| `tigerfs list` | Active **local** mountpoints (one per line, scripting) |
| `tigerfs status` | Human-readable mount registry (PID, database, backend, uptime) |
| `tigerfs info [MOUNTPOINT]` | Registry entry + live `GetInfo()` from backend CLI when `cli_backend` and `service_id` are set |

TigerFS does not list cloud services globally. Use `tiger` / `ghost` CLIs for provider-side inventory; Ghost `GetInfo` internally runs `ghost list --json` and filters by ID.

## default_backend configuration

<ParamField body="default_backend" type="string">
Cloud backend for **bare names** on `create` and `fork` only. Values: `tiger`, `ghost`, or empty (unset).
</ParamField>

<ParamField body="default_mount_dir" type="string">
Base directory for auto-derived mountpoints. Default: `/tmp`.
</ParamField>

```yaml
# ~/.config/tigerfs/config.yaml
default_backend: tiger   # or ghost
default_mount_dir: /tmp
```

Environment: `TIGERFS_DEFAULT_BACKEND`, `TIGERFS_DEFAULT_MOUNT_DIR`.

| Scenario | Backend source |
|----------|----------------|
| `tigerfs create tiger:my-db` | `tiger:` prefix |
| `tigerfs create my-db` | `default_backend` (error if unset) |
| `tigerfs fork tiger:abc` | `tiger:` prefix |
| `tigerfs fork /mnt/prod` | Registry `cli_backend` on that mount |
| `tigerfs mount tiger:ID` | Prefix (not `default_backend`) |
| `tigerfs mount postgres://...` | Direct (no backend) |

There is no `--backend` flag; the prefix (or config for bare names on create/fork) is the only selector.

## CLI prerequisites and credentials

### Install CLIs

| Backend | Install hint (from TigerFS errors) |
|---------|-------------------------------------|
| Tiger | https://github.com/timescale/tiger-cli |
| Ghost | https://ghost.build |

TigerFS checks `exec.LookPath` before every backend call. Missing binary → `ErrCLINotFound` with install URL.

### Authenticate

TigerFS delegates auth to the backend CLI. On auth-related stderr (`not authenticated`, `login`, `unauthorized`, `401`), errors map to `ErrNotAuthenticated` with:

- Tiger: `tiger auth login`
- Ghost: `ghost auth login` (TigerFS message; Ghost docs may use `ghost login`)

**Tiger Cloud — interactive:**

```bash
tiger auth login
```

**Tiger Cloud — headless / CI (Tiger CLI reads these; bind via config or env):**

```bash
export TIGER_PUBLIC_KEY=<public-key>
export TIGER_SECRET_KEY=<secret-key>
export TIGER_PROJECT_ID=<project-id>
tiger auth login
```

Config equivalents: `tiger_public_key`, `tiger_secret_key`, `tiger_project_id` in `~/.config/tigerfs/config.yaml` (passed through Viper; used by `tiger auth login`, not stored as DB passwords by TigerFS).

**Ghost:**

- Run Ghost login flow so `ghost connect` succeeds.
- Ensure `~/.pgpass` contains credentials Ghost expects (Ghost does not use Tiger’s `--with-password`).

### Legacy config mount path

If `mount` is invoked with **no** connection argument (mountpoint only), `db.ResolveConnectionString()` may use legacy `tiger_service_id` / `TIGER_SERVICE_ID` to call `TigerBackend.GetConnectionString()`. Prefer explicit `tiger:ID` on the command line for new workflows.

## Mount registry

Cloud-backed mounts persist metadata in `~/.config/tigerfs/mounts.json` (XDG paths on other platforms):

| Field | Meaning |
|-------|---------|
| `service_id` | Tiger or Ghost service/database ID |
| `cli_backend` | `tiger` or `ghost` |
| `database` | Sanitized connection string (no password) |
| `auto_created` | TigerFS created mountpoint dir (removed on unmount) |

`fork` from a mountpoint path and `info` depend on these fields. Direct `postgres://` mounts leave `cli_backend` and `service_id` empty.

## Error handling

| Condition | Sentinel / message |
|-----------|-------------------|
| CLI not in PATH | `ErrCLINotFound` + install URL |
| Auth failure | `ErrNotAuthenticated` + `run '<cli> auth login' first` |
| No backend for bare create/fork | `no backend specified` + config hint |
| Fork non-cloud mount | `cannot fork — filesystem has no cloud service` |
| Service missing | `service not found` (from CLI stderr) |
| Other CLI failure | `{cli} CLI error: ...` (stderr preserved) |

Passwords never land in TigerFS YAML for cloud backends. Registry and logs use `db.SanitizeConnectionString()`.

## Command quick reference

<CodeGroup>

```bash title="Mount cloud service"
tigerfs mount tiger:SERVICE_ID /mnt/prod
tigerfs mount ghost:DB_ID              # mountpoint → /tmp/DB_ID
```

```bash title="Create and auto-mount"
tigerfs create tiger:my-project
tigerfs create ghost:my-project /mnt/dev
tigerfs create my-project              # needs default_backend
tigerfs create tiger: --no-mount
```

```bash title="Fork"
tigerfs fork /mnt/prod my-fork
tigerfs fork tiger:abc123 /mnt/staging
tigerfs fork /mnt/prod --name experiment --no-mount
```

```bash title="Inspect"
tigerfs list
tigerfs status
tigerfs info /mnt/prod
tigerfs info --json
```

</CodeGroup>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First mount with `postgres://` or `tiger:` and success signals.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full cobra command surface for mount, create, fork, list, status, and info.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`default_backend`, `default_mount_dir`, Tiger env vars, and precedence.
</Card>
<Card title="Connection reference" href="/connection-reference">
`postgres://` URIs, TLS, and password resolution after the CLI returns a conn string.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Auth errors, stale mounts, and errno recovery.
</Card>
</CardGroup>
