# Configuration reference

> Config struct fields, ~/.config/tigerfs/config.yaml, TIGERFS_* env vars, precedence, TLS enforcement, and mount-specific overrides.

- 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/config/config.go`
- `internal/tigerfs/config/config_test.go`
- `internal/tigerfs/cmd/config.go`
- `docs/spec.md`
- `internal/tigerfs/db/tls.go`

---

---
title: "Configuration reference"
description: "Config struct fields, ~/.config/tigerfs/config.yaml, TIGERFS_* env vars, precedence, TLS enforcement, and mount-specific overrides."
---

TigerFS loads a single `config.Config` value per process via Viper (`internal/tigerfs/config`), merges defaults with `~/.config/tigerfs/config.yaml` and environment variables, then applies command-specific overrides (notably on `mount`) before passing the struct into `db.NewClient` and `fs.Operations`. Use `tigerfs config show` to inspect the effective merged settings; never read Viper directly outside the `cmd` package.

## Configuration flow

```mermaid
flowchart TB
  subgraph sources [Sources lowest to highest]
    D[Init defaults in config.Init]
    F["config.yaml in GetDefaultConfigDir"]
    E["TIGERFS_* / PG* / TIGER_* env"]
    R["Root persistent flags bound in PersistentPreRunE"]
  end
  subgraph load [Per command]
    I[config.Init in root PersistentPreRunE]
    L[config.Load into Config struct]
  end
  subgraph mount [Mount only]
    M[Mount RunE patches cfg fields]
    C[db.NewClient enforceSSLMode + pool]
    O[fs.Operations + FUSE/NFS adapter]
  end
  D --> F --> E --> R --> I --> L
  L --> M --> C --> O
```

<Note>
`config.Init()` runs on every command via the root command’s `PersistentPreRunE`. Subcommands call `config.Load()` to obtain a `*config.Config`. Application code should use that struct, not `viper` directly.
</Note>

## Precedence

Later layers override earlier ones for keys Viper manages:

| Priority | Source | Examples |
|----------|--------|----------|
| 1 (lowest) | Built-in defaults | `port: 5432`, `dir_listing_limit: 1000` |
| 2 | Config file | `~/.config/tigerfs/config.yaml` |
| 3 | Environment | `TIGERFS_LOG_LEVEL`, `PGHOST`, `TIGER_SERVICE_ID` |
| 4 | Root persistent CLI flags | `--log-level`, `--config-dir` (bound to Viper before `Init`) |
| 5 | Mount-time patches | `--schema`, `--insecure-no-ssl`, `--user-id` (applied to `cfg` after `Load`) |
| 6 (connection) | Connection string | Password in URL; `sslmode` transformed at connect |

**`TIGERFS_*` vs `PG*`:** For the same logical key (e.g. port), `TIGERFS_PORT` wins over `PGPORT` when both are set.

**Password resolution** (separate from Viper; in `db.resolvePassword`):

1. Password embedded in the connection string  
2. `PGPASSWORD`  
3. `TIGERFS_PASSWORD`  
4. `password_command` from config  
5. `~/.pgpass` (pgx automatic)

## Config file location and format

### Path resolution

| Platform / variable | Directory |
|---------------------|-----------|
| Default (Unix) | `~/.config/tigerfs/` |
| `XDG_CONFIG_HOME` set | `$XDG_CONFIG_HOME/tigerfs/` |
| Windows `APPDATA` | `%APPDATA%/tigerfs/` |

File name: `config.yaml`. A missing file is not an error.

```bash
tigerfs config path    # print path; note if file missing
tigerfs config show    # merged effective config (YAML, secrets omitted)
tigerfs config validate
```

<Warning>
`config.Init()` always calls `viper.AddConfigPath(GetDefaultConfigDir())`, which follows `XDG_CONFIG_HOME` / `APPDATA` / home — not the `config_dir` field after load. To relocate the config directory, set `XDG_CONFIG_HOME` (or `APPDATA` on Windows). The `config_dir` / `TIGERFS_CONFIG_DIR` value is stored on `Config` and shown in `config show`, but does not change where Viper searches for `config.yaml` today.
</Warning>

### YAML shape

Keys are **flat snake_case** matching `mapstructure` tags on `Config`. Example:

```yaml
# Connection
host: localhost
port: 5432
user: myuser
database: mydb
default_schema: public
pool_size: 10
pool_max_idle: 5
password_command: "op read op://vault/db/password"
insecure_no_ssl: false

# Backend
default_backend: tiger
default_mount_dir: /tmp

# Filesystem
dir_listing_limit: 1000
dir_writing_limit: 100000
trailing_newlines: true
no_filename_extensions: false
attr_timeout: 1s
entry_timeout: 1s
default_format: tsv
binary_encoding: raw
max_pipeline_depth: 10

# Query safety
query_timeout: 30s
dir_filter_limit: 100000

# Metadata caches (catalog vs structural)
metadata_refresh_interval: 10s
structural_metadata_refresh_interval: 5m

# NFS write buffers (macOS)
nfs_streaming_threshold: 10485760
nfs_max_random_write_size: 104857600
nfs_cache_reaper_interval: 30s
nfs_cache_idle_timeout: 5m

# DDL
ddl_grace_period: 30s

# Logging
log_level: warn
log_file: ""
log_format: text
log_sql_params: false

# Identity / undo
user_id: agent-7
auto_savepoint_interval: 30m
undo_list_limit: 100

# Linux FUSE
legacy_fuse: false
```

Do not put passwords in the config file; use `.pgpass`, env vars, or `password_command`.

## Environment variables

Viper uses prefix `TIGERFS` with `AutomaticEnv()` (e.g. `TIGERFS_DIR_LISTING_LIMIT` → `dir_listing_limit`). Duration env values accept Go duration strings (`30s`, `1m`, `5m`).

### PostgreSQL standard (bound in `config.Init`)

| Variable | Config field |
|----------|----------------|
| `PGHOST` | `host` |
| `PGPORT` | `port` |
| `PGUSER` | `user` |
| `PGDATABASE` | `database` |
| `PGPASSWORD` | (password resolution only) |

### Tiger Cloud (headless / Docker)

| Variable | Config field |
|----------|----------------|
| `TIGER_SERVICE_ID` | `tiger_service_id` |
| `TIGER_PUBLIC_KEY` | `tiger_public_key` |
| `TIGER_SECRET_KEY` | `tiger_secret_key` |
| `TIGER_PROJECT_ID` | `tiger_project_id` |

### Common `TIGERFS_*` variables

| Environment variable | Field | Default |
|---------------------|-------|---------|
| `TIGERFS_CONFIG_DIR` | `config_dir` | from `GetDefaultConfigDir()` |
| `TIGERFS_DEFAULT_SCHEMA` | `default_schema` | `""` (inherit PG `current_schema`) |
| `TIGERFS_DIR_LISTING_LIMIT` | `dir_listing_limit` | `1000` |
| `TIGERFS_DIR_WRITING_LIMIT` | `dir_writing_limit` | `100000` |
| `TIGERFS_DIR_FILTER_LIMIT` | `dir_filter_limit` | `100000` |
| `TIGERFS_QUERY_TIMEOUT` | `query_timeout` | `30s` |
| `TIGERFS_MAX_PIPELINE_DEPTH` | `max_pipeline_depth` | `10` (`0` = unlimited) |
| `TIGERFS_TRAILING_NEWLINES` | `trailing_newlines` | `true` |
| `TIGERFS_NO_FILENAME_EXTENSIONS` | `no_filename_extensions` | `false` |
| `TIGERFS_ATTR_TIMEOUT` | `attr_timeout` | `1s` |
| `TIGERFS_ENTRY_TIMEOUT` | `entry_timeout` | `1s` |
| `TIGERFS_METADATA_REFRESH_INTERVAL` | `metadata_refresh_interval` | `10s` |
| `TIGERFS_STRUCTURAL_METADATA_REFRESH_INTERVAL` | `structural_metadata_refresh_interval` | `5m` |
| `TIGERFS_DEFAULT_FORMAT` | `default_format` | `tsv` |
| `TIGERFS_BINARY_ENCODING` | `binary_encoding` | `raw` |
| `TIGERFS_POOL_SIZE` | `pool_size` | `10` |
| `TIGERFS_POOL_MAX_IDLE` | `pool_max_idle` | `5` |
| `TIGERFS_PASSWORD` | (password resolution) | — |
| `TIGERFS_PASSWORD_COMMAND` | `password_command` | — |
| `TIGERFS_DEFAULT_BACKEND` | `default_backend` | `""` |
| `TIGERFS_DEFAULT_MOUNT_DIR` | `default_mount_dir` | `/tmp` |
| `TIGERFS_INSECURE_NO_SSL` | `insecure_no_ssl` | `false` |
| `TIGERFS_USER_ID` | `user_id` | `""` |
| `TIGERFS_AUTO_SAVEPOINT_INTERVAL` | `auto_savepoint_interval` | `30m` (`0` disables) |
| `TIGERFS_UNDO_LIST_LIMIT` | `undo_list_limit` | `100` |
| `TIGERFS_LOG_LEVEL` | `log_level` | `warn` |
| `TIGERFS_LOG_FILE` | `log_file` | `""` (stderr) |
| `TIGERFS_LOG_FORMAT` | `log_format` | `text` |
| `TIGERFS_LOG_SQL_PARAMS` | `log_sql_params` | `false` |
| `TIGERFS_LEGACY_FUSE` | `legacy_fuse` | `false` |
| NFS / DDL keys | `nfs_*`, `ddl_grace_period` | see defaults table |

## `Config` struct reference

All fields live in `internal/tigerfs/config/config.go`. Grouped by concern:

### Connection and TLS

| Field | YAML / env | Default | Role |
|-------|------------|---------|------|
| `Host` | `host` / `PGHOST` | empty | Server hostname |
| `Port` | `port` / `PGPORT` / `TIGERFS_PORT` | `5432` | TCP port |
| `User` | `user` / `PGUSER` | empty | DB user |
| `Database` | `database` / `PGDATABASE` | empty | Database name |
| `Password` | — | empty | Rarely set via file; prefer env/pgpass |
| `DefaultSchema` | `default_schema` | `""` | Flatten schema to mount root |
| `PoolSize` | `pool_size` | `10` | Max pool connections |
| `PoolMaxIdle` | `pool_max_idle` | `5` | Min idle connections |
| `PasswordCommand` | `password_command` | empty | Shell command → password on stdout |
| `InsecureNoSSL` | `insecure_no_ssl` / `--insecure-no-ssl` | `false` | Skip remote TLS enforcement |

Tiger Cloud credential fields: `TigerCloudServiceID`, `TigerCloudPublicKey`, `TigerCloudSecretKey`, `TigerCloudProjectID` (env `TIGER_*`).

### Backend and mount paths

| Field | Default | Role |
|-------|---------|------|
| `DefaultBackend` | `""` | `tiger` or `ghost` for bare service IDs |
| `DefaultMountDir` | `/tmp` | Base dir when `tiger:ID` / `ghost:ID` omit mountpoint |

### Filesystem behavior

| Field | Default | Role |
|-------|---------|------|
| `DirListingLimit` | `1000` | Max rows for `ls`, `.by/`, exports without explicit limit |
| `DirWritingLimit` | `100000` | Bulk import row cap |
| `TrailingNewlines` | `true` | Append `\n` on column/count reads |
| `NoFilenameExtensions` | `false` | Disable type-based extensions (`.json`, `.txt`, …) |
| `AttrTimeout` | `1s` | FUSE attribute cache (Linux only) |
| `EntryTimeout` | `1s` | FUSE entry cache (Linux only) |
| `MaxPipelineDepth` | `10` | Hide deeper pipeline capabilities (`0` = unlimited) |
| `DefaultFormat` | `tsv` | Row-as-file default (`tsv`, `csv`, `json`) |
| `BinaryEncoding` | `raw` | BYTEA encoding (`raw`, `base64`, `hex`) |

### Query safety

| Field | Default | Role |
|-------|---------|------|
| `QueryTimeout` | `30s` | Per-connection `statement_timeout` + context timeout |
| `DirFilterLimit` | `100000` | Row threshold before `.filter/` value listing is restricted |

### Metadata TTLs

| Field | Default | Role |
|-------|---------|------|
| `MetadataRefreshInterval` | `10s` | Catalog cache (schemas, tables, views) |
| `StructuralMetadataRefreshInterval` | `5m` | PKs, permissions, row counts |

### NFS (macOS write path)

| Field | Default | Role |
|-------|---------|------|
| `NFSStreamingThreshold` | 10 MiB | Buffer size → streaming commit |
| `NFSMaxRandomWriteSize` | 100 MiB | Max random-write buffer |
| `NFSCacheReaperInterval` | `30s` | Stale entry check interval |
| `NFSCacheIdleTimeout` | `5m` | Idle force-commit |

### DDL, logging, identity, FUSE mode

| Field | Default | Role |
|-------|---------|------|
| `DDLGracePeriod` | `30s` | Completed DDL session visibility |
| `LogLevel` | `warn` | Zap level (`debug`, `info`, `warn`, `error`) |
| `LogFile` | empty | File path; empty → stderr |
| `LogFormat` | `text` | `text` or `json` |
| `LogSQLParams` | `false` | Log bind parameters (sensitive) |
| `UserID` | empty | Per-mount undo/log identity |
| `AutoSavepointInterval` | `30m` | Inactivity auto-savepoint (`0` off) |
| `UndoListLimit` | `100` | Default `.undo/` listing cap |
| `LegacyFuse` | `false` | Linux: legacy FUSE tree vs shared `fs.Operations` |
| `ConfigDir` | `GetDefaultConfigDir()` | Recorded config directory path |

## TLS enforcement

Applied in `db.enforceSSLMode` when opening the pool (`db.NewClient`), using `cfg.InsecureNoSSL`.

```text
                    enforceSSLMode(connStr, InsecureNoSSL)
                              |
         +--------------------+--------------------+
         |                    |                    |
    localhost            remote host          insecure_no_ssl
  (127.0.0.1, ::1,      sslmode missing        true → no change
   unix socket)         or disable/prefer      + warn log
         |                    |
  no sslmode →            → sslmode=require
  sslmode=disable         (require/verify-* kept)
  explicit sslmode kept
```

<ParamField body="insecure_no_ssl" type="bool">
When `true` (`--insecure-no-ssl`, `TIGERFS_INSECURE_NO_SSL`, or YAML), remote connections keep the connection string’s `sslmode` unchanged and log a warning. Default is enforced TLS for non-localhost hosts.
</ParamField>

| Host class | No `sslmode` in URI | `sslmode=disable` / `prefer` | `require` / `verify-ca` / `verify-full` |
|------------|-------------------|------------------------------|----------------------------------------|
| Localhost / Unix socket | Adds `disable` | Unchanged | Unchanged (explicit override allowed) |
| Remote | Adds `require` | Replaced with `require` | Unchanged |

Localhost detection: `localhost`, `127.0.0.1`, `::1`, empty host, or host paths starting with `/`.

## Mount-specific overrides

Each `tigerfs mount` process loads config once, then patches the struct before `mountFilesystem`. These flags override file/env for that mount only:

| Flag | Config field | Behavior |
|------|--------------|----------|
| `--schema` | `DefaultSchema` | Set when non-empty |
| `--no-filename-extensions` | `NoFilenameExtensions` | Set `true` when passed |
| `--query-timeout` | `QueryTimeout` | Applied when `> 0` |
| `--dir-filter-limit` | `DirFilterLimit` | Applied when `> 0` |
| `--max-pipeline-depth` | `MaxPipelineDepth` | Applied only if flag **changed** (allows `0`) |
| `--legacy-fuse` | `LegacyFuse` | Linux legacy FUSE backend |
| `--insecure-no-ssl` | `InsecureNoSSL` | Disables TLS enforcement |
| `--user-id` | `UserID` | Flag → `TIGERFS_USER_ID` → value from config file |
| `--auto-savepoint-interval` | `AutoSavepointInterval` | Applied when `!= 0` (`>= 0` required) |
| `--undo-list-limit` | `UndoListLimit` | Applied when `> 0` |

<Info>
`--max-ls-rows` is declared on the mount command (default `10000`) but is **not** wired to `DirListingLimit` in `mount.go`; use `dir_listing_limit` in config or `TIGERFS_DIR_LISTING_LIMIT` instead (default `1000`).
</Info>

Root persistent flags (all subcommands):

| Flag | Viper key | Default |
|------|-----------|---------|
| `--config-dir` | `config_dir` | `GetDefaultConfigDir()` |
| `--log-level` | `log_level` | `warn` |
| `--log-file` | `log_file` | stderr |
| `--log-format` | `log_format` | `text` |
| `--log-sql-params` | `log_sql_params` | `false` |

`--debug` is not a separate config key; use `--log-level debug`.

### Per-mount identity

`UserID` tags undo/log entries and drives per-user filters on `.log/` and `.undo/.by/user_id/`. It is session-scoped (lost on remount). Precedence at mount: `--user-id` → `TIGERFS_USER_ID` → `user_id` from config file → anonymous.

## Validation rules

`tigerfs config validate` checks the on-disk file (syntax + unmarshal) and:

| Field | Rule |
|-------|------|
| `port` | 1–65535 |
| `pool_size` | ≥ 1 |
| `pool_max_idle` | ≥ 0 |
| `dir_listing_limit` | ≥ 1 |
| `default_format` | `tsv`, `csv`, or `json` |
| `binary_encoding` | `raw`, `base64`, or `hex` |
| `log_level` | `debug`, `info`, `warn`, or `error` |

## Inspecting configuration

<Steps>
<Step title="Show effective config">
Run after exporting env vars or editing the file:

```bash
tigerfs config show
```

Sensitive fields (`password`, Tiger secret keys) are omitted from output.
</Step>
<Step title="Validate on-disk file">
```bash
tigerfs config validate
```
</Step>
<Step title="Mount with overrides">
```bash
export TIGERFS_QUERY_TIMEOUT=45s
tigerfs mount --schema public --user-id agent-7 \
  postgres://user@db.example.com/mydb /mnt/db
```
</Step>
</Steps>

<Check>
`config show` reflects Viper merge (defaults + file + env + root flags). Mount-only flag patches apply only to that mount process after `Load()`.
</Check>

## Related pages

<CardGroup>
<Card title="CLI reference" href="/cli-reference">
Cobra commands, mount flags, and argument patterns beyond config keys.
</Card>
<Card title="Connection reference" href="/connection-reference">
`postgres://` URIs, `.pgpass`, pool behavior, and connection-string `sslmode` details.
</Card>
<Card title="Cloud backends" href="/cloud-backends">
`default_backend`, `tiger:` / `ghost:` prefixes, and Tiger Cloud credentials.
</Card>
<Card title="Consistency and caching" href="/consistency-and-caching">
How `metadata_refresh_interval` and FUSE `attr_timeout` / `entry_timeout` affect freshness.
</Card>
<Card title="Platform backends" href="/platform-backends">
FUSE vs NFS: which config keys apply per platform.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
TLS errors, stale metadata, and `--log-level debug`.
</Card>
</CardGroup>
