Skip to main content

Server CLI (flapi)

flapi is the server binary. It loads a configuration file, opens an embedded DuckDB instance, and serves the REST API + MCP endpoints described by your endpoint YAMLs.

The same binary is also the packager: flapi pack folds an entire config tree (YAML + SQL templates + small data files) into a new self-contained executable, suitable for scp-style deployment.

For the upstream reference document, see docs/CLI_REFERENCE.md on the flAPI repository. For the client CLI (flapii, which talks to a running server's configuration API), see flapii CLI.

Flags

FlagDescriptionDefault
-c, --config <path>Path to the flapi.yaml configuration fileflapi.yaml
-p, --port <int>Port for the web server (overrides server.http-port)from config
--log-level <level>One of debug, info, warning, errorinfo
--validate-configValidate the configuration file and exitoff
--config-serviceEnable the Configuration Service API (/api/v1/_config/*)off
--config-service-token <token>Bearer token required by the Configuration Serviceauto-generated when missing
--no-telemetryDisable startup/shutdown telemetry eventsoff

Environment variables

The server reads several environment variables. The general precedence rule is CLI flag > environment variable > built-in default, so any of these may also be passed via the matching CLI flag.

VariablePurpose
FLAPI_CONFIGFallback for -c / --config. Lets you point at flapi.yaml purely via the environment.
FLAPI_LOG_LEVELFallback for --log-level. Invalid values exit non-zero with a single-line error — typos like FLAPI_LOG_LEVEL=DEBUG (uppercase) surface immediately rather than silently defaulting to info.
FLAPI_CONFIG_SERVICE_TOKENFallback for --config-service-token.
FLAPI_NO_TELEMETRYSet to 1, true, or yes to disable telemetry (same as --no-telemetry).
SOURCE_DATE_EPOCHMtime stamped on every entry by flapi pack. Set to a fixed value to make pack output bit-identical across runs (see reproducible builds).
CODESIGN_IDENTITYmacOS only. Identity passed to codesign --sign after flapi pack. Defaults to - (ad-hoc).

Beyond these, any environment variable whose name matches a regex in template.environment-whitelist may be substituted inside flapi.yaml and SQL templates via {{env.VAR_NAME}}. That is how secrets like DB_PASSWORD reach the running server — they live in the deployment environment, never in the config tree itself.

Self-packaging subcommands

The flapi binary can fold an entire config tree into itself, producing one self-contained executable deployable via scp. Three subcommands implement the round-trip:

SubcommandWhat it does
flapi pack --in <dir> --out <bin> [--allow-secrets] [--macos-append]Bundle <dir> into a new self-contained binary at <bin>
flapi infoPrint EOCD offset, bundle size, and entry list of the running binary (exit 1 + "Bundle: none" when unbundled)
flapi unpack --to <dir>Dump the running binary's bundle back to a directory

flapi pack

flapi pack --in <config-dir> --out <new-binary> [--allow-secrets] [--macos-append]
OptionRequiredDescription
--in <dir>yesDirectory containing flapi.yaml and friends. Walked recursively.
--out <path>yesPath for the bundled output binary. Overwritten if it exists.
--allow-secretsnoBypass the default secret deny list. Testing only — production users must never set this.
--macos-appendnomacOS only. Use the legacy trailing-bytes layout instead of the reserved __FLAPI/__bundle segment. The result is not notarisable — for local debugging only.

Re-pack idempotence. If the host binary already has a trailing bundle, pack strips it from the copy (not the running binary) before writing the new one, so repeated invocations don't grow the output. Same goes for the macOS reserved-segment path: the segment is overwritten in place.

flapi info

flapi info

Prints the EOCD offset, bundle size, and entry list of the running binary. Exits non-zero with "Bundle: none (filesystem mode)" when no bundle is present (Linux/Windows binaries built without pack having been run; macOS binaries with an empty placeholder segment).

$ ./flapi-prod info
Binary: /opt/flapi/flapi-prod
Bundle offset: 70123456
Bundle size: 12534 bytes
Entries (17):
flapi.yaml (1024 bytes)
sqls/customers.yaml (412 bytes)
...

flapi unpack

flapi unpack --to <dir>

Writes every bundle entry to <dir> (creating intermediate directories as needed), preserving paths. Useful for diffing a deployed bundle against a development tree or for audit.

Worked example

# 1. Pack a config tree into a new bundled binary
flapi pack --in ./examples --out flapi-prod

# 2. Inspect what's bundled
./flapi-prod info

# 3. (Optional) extract for debugging or audit
./flapi-prod unpack --to /tmp/extracted

# 4. One-file deploy
scp flapi-prod user@host:/opt/flapi/
ssh user@host '/opt/flapi/flapi-prod' # serves bundled config from any cwd

Secret deny list

flapi pack enforces a default deny list and refuses files whose relative path matches any of:

  • *.env at any depth (e.g. .env, config/.env)
  • secrets/ segment at any depth (e.g. secrets/db.token, nested/secrets/api.key)
  • *.pem at any depth
  • *.key at any depth

Hitting any of these aborts the pack with a non-zero exit and a clear error naming the offending file. The --allow-secrets flag exists for testing (it lets the integration test suite verify the deny list itself); production users must never use it.

The rationale: a bundled binary that contains a database password is itself a secret. The whole single-artifact value disappears the moment that artifact becomes sensitive. Credentials come from environment variables at runtime — AWS_*, GOOGLE_*, AZURE_*, FLAPI_CONFIG_SERVICE_TOKEN, and {{env.VAR}} substitution inside whitelisted YAML.

Reproducible builds

Set SOURCE_DATE_EPOCH before flapi pack and the output is byte-identical across runs:

SOURCE_DATE_EPOCH=1700000000 flapi pack --in config --out a
SOURCE_DATE_EPOCH=1700000000 flapi pack --in config --out b
sha256sum a b # identical

The archive itself is deterministic by construction (entries sorted, fixed mtime, no tar-block padding), so once you fix the source binary, the output is a pure function of the input tree.

macOS notarisation

By default flapi pack on macOS writes the archive into a reserved __FLAPI/__bundle Mach-O segment that was allocated at link time (16 MiB default; knob FLAPI_RESERVED_BUNDLE_MIB at CMake configure time). The segment is overwritten in place, then flapi pack re-invokes:

codesign --force --sign "${CODESIGN_IDENTITY:--}" <out>

The result is suitable for xcrun notarytool submit. If the archive doesn't fit (e.g. you tried to bundle a 32 MiB data file into a 16 MiB segment), flapi pack exits non-zero with an error message mentioning FLAPI_RESERVED_BUNDLE_MIB so you know which knob to rebuild flAPI with.

The legacy --macos-append flag falls back to the Linux/Windows trailing-bytes layout. codesign --verify will fail on that output because the trailing data is outside what codesign considers signable; flapi pack warns to stderr and continues. Use --macos-append only for local debugging; the binary is intentionally not notarisable.

See also

Examples

# Development: debug logging, examples config
./flapi -c examples/flapi.yaml --log-level debug

# Production-style run
./flapi -c /etc/flapi/flapi.yaml --log-level info

# Validate config in CI and exit non-zero on failure
./flapi --validate-config -c flapi.yaml

# Enable the runtime Configuration Service
./flapi -c flapi.yaml --config-service --config-service-token "$FLAPI_CONFIG_SERVICE_TOKEN"

# 12-factor: configure everything from the environment
export FLAPI_CONFIG=/etc/flapi/flapi.yaml
export FLAPI_LOG_LEVEL=info
export FLAPI_CONFIG_SERVICE_TOKEN=...
./flapi

Signal handling

  • SIGINT (Ctrl+C) triggers a graceful shutdown: stop accepting new connections, drain in-flight requests, close database connections, exit cleanly.
  • Other signals are not handled explicitly.

Exit codes

CodeMeaning
0Success (normal exit, or --validate-config passed, or flapi pack / info / unpack succeeded)
1General error: invalid arguments, configuration error, startup failure, validation failure, pack refused a secret-looking file, codesign failed on reserved-segment output, or flapi info ran on a binary without a bundle

Next Steps

🍪 Cookie Settings