You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fold flapi's config tree (flapi.yaml, endpoint YAMLs, SQL templates,
data files) into the binary itself, so the deployable unit is one
file you can scp and run. The packager is part of the same binary: flapi pack --in flapi.yaml --out flapi-prod produces a new
self-contained executable from the running one.
Filesystem mode remains the default when no bundle is present —
existing operators are unaffected.
Why now
One artifact deploys.scp flapi-prod user@host is the whole
deploy. No config-tree drift across environments.
Container images shrink. No COPY sqls/ step in Dockerfiles.
Reproducible builds. Same config tree + binary input →
byte-identical bundled output (SOURCE_DATE_EPOCH, sorted entries).
Closes 12-factor gaps. Two adjacent env-var gaps
(FLAPI_CONFIG, FLAPI_LOG_LEVEL) get closed at the same time so
the resulting single-binary is operable purely via environment
variables.
Core decisions (full rationale in the spike README):
ZIP appended after the executable. ELF/PE tolerate trailing
bytes; ZIP's EOCD is designed to self-locate via reverse scan from
EOF.
Reuse IFileProvider (already at src/include/vfs_adapter.hpp).
New EmbeddedArchiveFileProvider is a sibling of LocalFileProvider
and DuckDBVFSProvider. No churn in config_loader, template
processor, or endpoint handlers.
embed:// DuckDB FileSystem so SQL templates that say read_csv('embed://data/cities.csv') work. Same in-memory entry
map serves both the file provider and DuckDB.
libarchive (over libzip / minizip-ng / bit7z) — vcpkg-available,
ZIP + memory I/O is its stable subset.
macOS notarised is in-scope. Reserved-segment variant
(-Wl,-sectcreate,__FLAPI,__bundle,<placeholder.bin>) overwritten
by pack + codesign --force --sign after. Trailing-append remains
available for ad-hoc / non-notarised use behind --macos-append.
12-factor scope is narrow this round: add FLAPI_CONFIG and FLAPI_LOG_LEVEL only. (FLAPI_PORT / FLAPI_HOST and a full
credential-env-var audit are explicitly deferred.)
Secrets stay out of the bundle. Default exclude list
(*.env, secrets/*, *.pem, *.key) is mandatory; pack
exits non-zero if a match is found. Override only via --allow-secrets (testing). Credentials continue to come from env
at runtime (AWS_*, GOOGLE_*, AZURE_*, FLAPI_CONFIG_SERVICE_TOKEN).
TDD red/green. Each sub-issue lands a failing test first, then
the implementation. No "sneak it in" merges.
Out of scope
ZIP64 (config trees are kilobytes; defensive read-side handling can
be added later if needed).
Hot reload of bundled configs (a bundled binary is immutable by
design).
Embedding secrets in the bundle (explicitly prohibited).
FLAPI_PORT / FLAPI_HOST and a full credential env-var audit.
✅ All existing tests still pass (make test, make integration-test).
✅ flapi pack --in examples/ --out flapi-prod produces a binary
that, run from any cwd with no source tree present, serves the same
endpoints (including DuckDB embed:// reads).
Summary
Fold flapi's config tree (
flapi.yaml, endpoint YAMLs, SQL templates,data files) into the binary itself, so the deployable unit is one
file you can
scpand run. The packager is part of the same binary:flapi pack --in flapi.yaml --out flapi-prodproduces a newself-contained executable from the running one.
Filesystem mode remains the default when no bundle is present —
existing operators are unaffected.
Why now
scp flapi-prod user@hostis the wholedeploy. No config-tree drift across environments.
COPY sqls/step in Dockerfiles.byte-identical bundled output (SOURCE_DATE_EPOCH, sorted entries).
(
FLAPI_CONFIG,FLAPI_LOG_LEVEL) get closed at the same time sothe resulting single-binary is operable purely via environment
variables.
Design (proven by spike)
Working cross-OS proof-of-concept lives at
jrosskopf/research/2026-05-21-flapi-self-packaging,
incl. Linux/macOS/Windows CI and a DuckDB
read_csv('embed://…')end-to-end test.
Core decisions (full rationale in the spike README):
bytes; ZIP's EOCD is designed to self-locate via reverse scan from
EOF.
IFileProvider(already atsrc/include/vfs_adapter.hpp).New
EmbeddedArchiveFileProvideris a sibling ofLocalFileProviderand
DuckDBVFSProvider. No churn inconfig_loader, templateprocessor, or endpoint handlers.
embed://DuckDB FileSystem so SQL templates that sayread_csv('embed://data/cities.csv')work. Same in-memory entrymap serves both the file provider and DuckDB.
ZIP + memory I/O is its stable subset.
Scope decisions
(
-Wl,-sectcreate,__FLAPI,__bundle,<placeholder.bin>) overwrittenby
pack+codesign --force --signafter. Trailing-append remainsavailable for ad-hoc / non-notarised use behind
--macos-append.FLAPI_CONFIGandFLAPI_LOG_LEVELonly. (FLAPI_PORT/FLAPI_HOSTand a fullcredential-env-var audit are explicitly deferred.)
(
*.env,secrets/*,*.pem,*.key) is mandatory;packexits non-zero if a match is found. Override only via
--allow-secrets(testing). Credentials continue to come from envat runtime (
AWS_*,GOOGLE_*,AZURE_*,FLAPI_CONFIG_SERVICE_TOKEN).the implementation. No "sneak it in" merges.
Out of scope
be added later if needed).
design).
FLAPI_PORT/FLAPI_HOSTand a full credential env-var audit.Sub-issues (build order)
archive_ioRAII wrapperselfpath+bundle_locator(cross-platform EOCD scan)EmbeddedArchiveFileProvider+FileProviderFactorydispatchembed://DuckDB FileSystem (withGlob+SeekPosition)flapi pack/info/unpacksubcommandsFLAPI_CONFIG+FLAPI_LOG_LEVELenv-var precedenceDependency notes:
Acceptance criteria
make test,make integration-test).flapi pack --in examples/ --out flapi-prodproduces a binarythat, run from any cwd with no source tree present, serves the same
endpoints (including DuckDB
embed://reads).flapi packcalls don't growthe binary.
mode without crashing.
codesign --verifypasses on reserved-segment output;notarisation pipeline is unblocked.
*.env/*.pem/*.key/secrets/*causepackto refuse with a clear error.Verification
References
src/include/vfs_adapter.hpp:27-73src/vfs_adapter.cpp:403-408src/main.cpp:269-304(argparse — currently no subcommands)