create-flowdular 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +16 -10
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +2 -2
  4. package/agent-template/.agents/skills/bug-hunt/SKILL.md +1 -1
  5. package/agent-template/.agents/skills/database-adapter/SKILL.md +5 -5
  6. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  7. package/agent-template/.agents/skills/deploy-operate/SKILL.md +1 -1
  8. package/agent-template/.agents/skills/migration-authoring/SKILL.md +4 -4
  9. package/agent-template/.agents/skills/module-new/SKILL.md +1 -1
  10. package/agent-template/.agents/skills/spec-interview/SKILL.md +20 -20
  11. package/agent-template/.agents/skills/test-hardening/SKILL.md +2 -2
  12. package/agent-template/.agents/skills/ux-design/SKILL.md +1 -1
  13. package/agent-template/.agents/skills/workflow-development/SKILL.md +95 -9
  14. package/agent-template/.ai/README.md +5 -3
  15. package/agent-template/.ai/agents/README.md +1 -1
  16. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +1 -0
  17. package/agent-template/.ai/agents/sandbox/backend-engineer.md +1 -0
  18. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +1 -0
  19. package/agent-template/.ai/blueprints/add-migration/README.md +1 -1
  20. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +1 -1
  21. package/agent-template/.ai/blueprints/new-module/required-files.yaml +1 -1
  22. package/agent-template/.ai/examples/bad/client-imports-server/README.md +1 -1
  23. package/agent-template/.ai/examples/bad/missing-acl/README.md +1 -1
  24. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +1 -1
  25. package/agent-template/.ai/guides/application-development.md +7 -5
  26. package/agent-template/.ai/platform-capabilities.md +9 -5
  27. package/agent-template/.ai/policies/capabilities.yaml +28 -12
  28. package/agent-template/.ai/policies/task-budgets.yaml +1 -1
  29. package/agent-template/.ai/rules/flowdular.md +3 -2
  30. package/agent-template/.ai/skills/README.md +1 -1
  31. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -1
  32. package/agent-template/.ai/skills/auth-security-review/SKILL.md +2 -2
  33. package/agent-template/.ai/skills/bug-hunt/SKILL.md +1 -1
  34. package/agent-template/.ai/skills/database-adapter/SKILL.md +5 -5
  35. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  36. package/agent-template/.ai/skills/deploy-operate/SKILL.md +1 -1
  37. package/agent-template/.ai/skills/migration-authoring/SKILL.md +4 -4
  38. package/agent-template/.ai/skills/module-new/SKILL.md +1 -1
  39. package/agent-template/.ai/skills/spec-interview/SKILL.md +20 -20
  40. package/agent-template/.ai/skills/test-hardening/SKILL.md +2 -2
  41. package/agent-template/.ai/skills/ux-design/SKILL.md +1 -1
  42. package/agent-template/.ai/skills/workflow-development/SKILL.md +96 -10
  43. package/agent-template/.ai/subagents/module-executor.md +25 -0
  44. package/agent-template/.ai/subagents/reviewer.md +23 -0
  45. package/agent-template/.ai/subagents/spec-author.md +23 -0
  46. package/agent-template/.claude/agents/module-executor.md +22 -0
  47. package/agent-template/.claude/agents/reviewer.md +24 -0
  48. package/agent-template/.claude/agents/spec-author.md +20 -0
  49. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
  50. package/agent-template/.claude/skills/auth-security-review/SKILL.md +2 -2
  51. package/agent-template/.claude/skills/bug-hunt/SKILL.md +1 -1
  52. package/agent-template/.claude/skills/database-adapter/SKILL.md +5 -5
  53. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  54. package/agent-template/.claude/skills/deploy-operate/SKILL.md +1 -1
  55. package/agent-template/.claude/skills/migration-authoring/SKILL.md +4 -4
  56. package/agent-template/.claude/skills/module-new/SKILL.md +1 -1
  57. package/agent-template/.claude/skills/spec-interview/SKILL.md +20 -20
  58. package/agent-template/.claude/skills/test-hardening/SKILL.md +2 -2
  59. package/agent-template/.claude/skills/ux-design/SKILL.md +1 -1
  60. package/agent-template/.claude/skills/workflow-development/SKILL.md +95 -9
  61. package/agent-template/.codex/agents/module-executor.toml +17 -0
  62. package/agent-template/.codex/agents/reviewer.toml +14 -0
  63. package/agent-template/.codex/agents/spec-author.toml +15 -0
  64. package/agent-template/AGENTS.md +3 -2
  65. package/agent-template/CLAUDE.md +3 -2
  66. package/agent-template/docs/adr/0007-module-owned-agents.md +35 -1
  67. package/agent-template/docs/agent-contract.md +2 -2
  68. package/agent-template/docs/cli.md +24 -3
  69. package/agent-template/docs/configuration.md +59 -5
  70. package/agent-template/docs/database-adapters.md +20 -20
  71. package/agent-template/docs/design-system.md +3 -3
  72. package/agent-template/docs/getting-started.md +25 -32
  73. package/agent-template/docs/module-distribution.md +79 -86
  74. package/agent-template/docs/module-web-surfaces.md +9 -7
  75. package/agent-template/docs/modules.md +9 -1
  76. package/agent-template/docs/sandbox.md +117 -6
  77. package/agent-template/platform/scripts/build.mjs +7 -0
  78. package/agent-template/rulesync.jsonc +1 -1
  79. package/dist/bin.js +12 -6
  80. package/package.json +2 -2
  81. package/template/default/.env.example +10 -3
  82. package/template/default/.prettierignore +2 -0
  83. package/template/default/.vercelignore +8 -0
  84. package/template/default/README.md +26 -15
  85. package/template/default/_gitignore +3 -2
  86. package/template/default/infra/README.md +86 -65
  87. package/template/default/infra/docker/.env.example +66 -0
  88. package/template/default/infra/docker/Dockerfile +24 -10
  89. package/template/default/infra/docker/app-entrypoint.mjs +5 -0
  90. package/template/default/infra/docker/compose.yaml +105 -58
  91. package/template/default/infra/docker/database-urls.mjs +28 -0
  92. package/template/default/infra/docker/pitr.sh +177 -0
  93. package/template/default/infra/docker/postgres/10-roles.sh +16 -12
  94. package/template/default/infra/docker/start.mjs +402 -0
  95. package/template/default/infra/kubernetes/database-secret.example.yaml +3 -3
  96. package/template/default/infra/vercel/README.md +262 -0
  97. package/template/default/infra/vercel/build.mjs +214 -0
  98. package/template/default/infra/vercel/handler.mjs +100 -0
  99. package/template/default/modules/example/migrations/0001_example_core.up.sql +2 -2
  100. package/template/default/modules/example/module.json +1 -1
  101. package/template/default/modules/example/package.json +3 -3
  102. package/template/default/modules/example/spec/module.yaml +1 -1
  103. package/template/default/modules/example/src/services/migration.ts +2 -2
  104. package/template/default/modules/example/tests/module.test.ts +1 -1
  105. package/template/default/package.json +3 -2
  106. package/template/default/platform/index.html +7 -19
  107. package/template/default/platform/octane.config.ts +252 -156
  108. package/template/default/platform/package.json +5 -5
  109. package/template/default/platform/public/favicon.svg +1 -1
  110. package/template/default/platform/scripts/build.mjs +56 -0
  111. package/template/default/platform/scripts/dev.mjs +38 -0
  112. package/template/default/platform/src/App.tsrx +25 -1
  113. package/template/default/platform/src/generated/modules.server.ts +3 -0
  114. package/template/default/platform/src/server/database.ts +24 -0
  115. package/template/default/platform/src/server/runtime-role.ts +33 -0
  116. package/template/default/platform/src/server/setup/access.ts +160 -0
  117. package/template/default/platform/src/server/setup/adapters.ts +554 -0
  118. package/template/default/platform/src/server/setup/environment.ts +154 -0
  119. package/template/default/platform/src/server/setup/gate.ts +84 -0
  120. package/template/default/platform/src/server/setup/index.ts +181 -0
  121. package/template/default/platform/src/server/setup/modules.ts +123 -0
  122. package/template/default/platform/src/server/setup/page.ts +497 -0
  123. package/template/default/platform/src/server/setup/routes.ts +787 -0
  124. package/template/default/platform/src/server/setup/sanitize.ts +111 -0
  125. package/template/default/platform/src/server/setup/seed.ts +145 -0
  126. package/template/default/platform/src/server/setup/token.ts +79 -0
  127. package/template/default/platform/src/server/worker-tick.ts +193 -0
  128. package/template/default/platform/src/server/workspace-root.ts +16 -0
  129. package/template/default/render.yaml +70 -0
  130. package/template/default/vercel.json +5 -0
@@ -7,13 +7,15 @@ example module, and nothing else you have to configure.
7
7
 
8
8
  ```bash
9
9
  pnpm install
10
- pnpm flowdular setup
11
10
  pnpm dev
12
11
  ```
13
12
 
14
- Open <http://localhost:4310> and sign in as `admin@example.com`. Quick setup is
15
- a destructive local reset: stop `pnpm dev` first, and never point it at a
16
- deployed database.
13
+ On the first run, the server starts the setup page and `pnpm dev` opens it in
14
+ your browser. Enter the one-time token printed in the terminal, then choose
15
+ your workspace and owner account. The embedded database is already selected.
16
+ If a browser cannot open automatically, visit <http://localhost:4310/setup>.
17
+ Restart `pnpm dev` after setup, then sign in with the account you chose. Use
18
+ `pnpm dev -- --no-open` to leave the browser alone.
17
19
 
18
20
  Local development runs on embedded PostgreSQL, so there is no database server to
19
21
  install. Data lives under `.flowdular/data`.
@@ -25,7 +27,7 @@ install. Data lives under `.flowdular/data`.
25
27
  | `platform` | The deployable composition root and the application shell |
26
28
  | `modules/example` | A tenant-scoped record module: API, migration, screen, tests |
27
29
  | `specs` | The platform spec of this application |
28
- | `infra` | Dockerfile, compose stack and Kubernetes base |
30
+ | `infra` | Docker, Kubernetes and Vercel deployment assets |
29
31
  | `flowdular.json` | Enabled modules and locales, owned by the CLI |
30
32
  | `.env` | The keys generated for this app. Never commit it |
31
33
  | `.env.example` | Every key a production deployment reads |
@@ -33,11 +35,13 @@ install. Data lives under `.flowdular/data`.
33
35
  ## Work with coding agents
34
36
 
35
37
  `AGENTS.md` and `CLAUDE.md` introduce the application contract. `.ai` contains the
36
- editable rules, skills, role prompts, policies, blueprints and reference module.
37
- Codex and Claude Code discover generated skills in `.agents/skills` and
38
- `.claude/skills`. Supporting guides are in `docs`.
38
+ editable rules, skills, role prompts, subagents, policies, blueprints and
39
+ reference module. Codex and Claude Code discover generated skills in
40
+ `.agents/skills` and `.claude/skills`, and the spec author, module executor and
41
+ reviewer as subagents in `.codex/agents` and `.claude/agents`. Supporting guides
42
+ are in `docs`.
39
43
 
40
- After editing `.ai/rules` or `.ai/skills`, run `pnpm rules:generate`.
44
+ After editing `.ai/rules`, `.ai/skills` or `.ai/subagents`, run `pnpm rules:generate`.
41
45
  `pnpm rules:check` detects drift and also runs as part of `pnpm verify`.
42
46
  Extend local modules using the published `@flowdular/sdk` imports. Installed
43
47
  SDK source is reference material and must not be edited in `node_modules`.
@@ -77,19 +81,26 @@ account access, then paste the token and the application address into the
77
81
  connect screen.
78
82
 
79
83
  ```bash
80
- pnpm flowdular sandbox grant --email admin@example.com --tenant operations-demo --apply
84
+ pnpm flowdular sandbox grant --email <your-owner-email> --tenant <your-workspace-slug> --apply
81
85
  ```
82
86
 
83
87
  ## Deploy
84
88
 
85
89
  ```bash
86
- cp .env.example infra/docker/.env # then fill in every value
87
- docker compose -f infra/docker/compose.yaml up --build
90
+ node infra/docker/start.mjs
88
91
  ```
89
92
 
90
- `.env.example` lists every key the server reads in production. `infra/README.md`
91
- covers the container image, the PostgreSQL roles and TLS, migrations on rollout,
92
- and the Kubernetes base.
93
+ The launcher creates `infra/docker/.env` with private database passwords and
94
+ keys, starts PostgreSQL and MinIO, then opens the setup page. Enter the token
95
+ shown in the terminal. The database step is already complete, so you start with
96
+ the workspace owner. `infra/README.md` covers the stack and deployments.
97
+
98
+ To deploy on Vercel, run `vercel login` and then
99
+ `pnpm flowdular deploy start vercel --apply`. It provisions Neon PostgreSQL, a
100
+ private Blob store and the stable keys, deploys, and prints a one-time token for
101
+ creating the first workspace at `/setup`; `infra/vercel/README.md` walks through
102
+ it and the manual path. Module workers run in a worker Function that Vercel Cron
103
+ and state-changing requests tick.
93
104
 
94
105
  ## Commands
95
106
 
@@ -1,8 +1,9 @@
1
1
  node_modules/
2
2
  dist/
3
3
  .flowdular/
4
- .env
5
- .env.*
4
+ # vercel link appends these two lines unless they are already here verbatim.
5
+ .vercel
6
+ .env*
6
7
  !.env.example
7
8
  *.log
8
9
  .DS_Store
@@ -1,5 +1,49 @@
1
1
  # Deployment
2
2
 
3
+ ## Deployment targets
4
+
5
+ Run `pnpm flowdular deploy targets` to inspect the target contracts and
6
+ `pnpm flowdular deploy plan docker` before starting the local stack.
7
+ `pnpm flowdular deploy start docker --apply` starts Docker Compose and opens
8
+ first-run setup. It prints the setup token, so it requires a private
9
+ interactive terminal and refuses `--json` or redirected output.
10
+
11
+ The repository-root `render.yaml` is a Render Blueprint for an always-on
12
+ container. It needs external PostgreSQL with separate runtime, background and
13
+ migrator roles, verified TLS and an existing S3 bucket. The Blueprint prompts
14
+ for the database and storage values, derives its public HTTPS origin from the
15
+ Render service and generates encryption keys. Back up those keys separately.
16
+ If you add a custom domain, set its HTTPS origin in `render.yaml` so the next
17
+ Blueprint sync keeps it. It uses
18
+ `/api/health` during initial setup; verify
19
+ `/api/ready` after setup. See the main Flowdular deployment guide for details.
20
+ Render Blueprints can create a database, but `fromDatabase` gives its internal
21
+ URL, whose self-signed TLS certificate cannot satisfy Flowdular's production
22
+ `verify-full` requirement. The Blueprint also cannot declare the three
23
+ separate roles. Render can host MinIO, but this Blueprint does not initialize
24
+ its bucket or wire its credentials. For a store other than AWS S3, set
25
+ `FD_STORAGE_S3_ENDPOINT` and, if needed, `FD_STORAGE_S3_FORCE_PATH_STYLE` on
26
+ the Render service. See the [Render Postgres connection guide](https://render.com/docs/postgresql-creating-connecting)
27
+ and [MinIO guide](https://render.com/docs/deploy-minio).
28
+ After committing
29
+ `render.yaml` and `infra/docker/Dockerfile` and pushing the branch to a
30
+ credential-free Git origin, `deploy plan render --json` returns a Deploy to
31
+ Render URL when both files pass structural checks and match the pushed branch. The URL
32
+ selects that branch for Render, but the external PostgreSQL, storage and secret
33
+ prerequisites still require operator setup. Local Git tracking refs are not
34
+ live provider validation, so confirm that Render can access the branch.
35
+
36
+ The [Vercel artifact](vercel/README.md) packages the Octane handler and client
37
+ assets as a web Function and a worker Function. [Vercel Functions scale down to zero](https://vercel.com/docs/functions),
38
+ so module workers run only inside ticks from Vercel Cron and from
39
+ state-changing requests. `deploy plan vercel --json` checks its source and
40
+ returns an import URL for a pushed branch. `deploy start vercel --apply`
41
+ provisions Neon PostgreSQL, a private Blob store and the stable keys through the
42
+ Vercel CLI, deploys to Production and prints a one-time token for creating the
43
+ first workspace at `/setup`.
44
+ Cloudflare's Durable Object Container API can keep a process alive, but
45
+ Flowdular has no verified adapter for its restart, secret and rollout lifecycle.
46
+
3
47
  The production artifact is the server built from `platform`. It runs as a
4
48
  non-root user and serves two public probes from
5
49
  `platform/src/server/health.ts`: `GET /api/health` answers as soon as the
@@ -11,73 +55,50 @@ background roles hold neither `SUPERUSER` nor `BYPASSRLS`, and answers 503 with
11
55
  ## Local container
12
56
 
13
57
  ```bash
14
- cp .env.example infra/docker/.env # then fill in every value
15
- docker compose -f infra/docker/compose.yaml up --build
58
+ node infra/docker/start.mjs
16
59
  ```
17
60
 
18
- Compose refuses to start while a key or a database password is empty, and the
19
- owning module would refuse at boot anyway. The app is published on
20
- `http://localhost:3000`; set `FD_PORT` to change the host port.
21
-
22
- The build stage runs `pnpm install --frozen-lockfile`, so commit
23
- `pnpm-lock.yaml` before building.
24
-
25
- Compose also starts PostgreSQL. A one-shot `postgres-tls` service generates a
26
- self-signed server certificate for `CN=postgres` on first run, Postgres serves
27
- TLS with it, and the app verifies it through `FD_DATABASE_TLS_CA_FILE` under
28
- `verify-full`. First cluster initialization creates three roles:
29
- `coreloom_migrator` owns the schema, `coreloom_runtime` holds neither
30
- `SUPERUSER` nor `BYPASSRLS`, so the row-level security tenant tables force
31
- actually binds the application, and `coreloom_background` serves the
32
- cross-tenant scheduler poll with no default table grant at all.
33
-
34
- Public sign-up is disabled. The session cookie is Secure (`__Host-` prefix)
35
- because the container expects TLS in front of it. For a plain-HTTP run on a
36
- workstation set `FD_AUTH_SECURE_COOKIE=false` in `infra/docker/.env`; do not do
37
- this for anything reachable from a network.
38
-
39
- The runtime image contains only `platform/dist` and `platform/package.json`. The
40
- server bundle imports node built-ins exclusively, so no `node_modules` directory
41
- ships with it.
61
+ The launcher creates `infra/docker/.env` with owner-only permissions, generates
62
+ PostgreSQL passwords and platform keys once, and keeps them on later runs. It
63
+ starts PostgreSQL, a private MinIO bucket and the app. When the database is
64
+ empty, it opens the first-run setup in the workstation browser and prints the
65
+ one-time setup token in the terminal. Use `--no-open` on a headless host. The
66
+ normal address is `http://localhost:3000`; change `FD_PORT` in the Docker env
67
+ file to use another port. The container binds to `127.0.0.1` by default. A
68
+ generated Compose project name keeps this app's volumes separate from other
69
+ Flowdular projects on the same machine.
70
+
71
+ The database and object store are connected before the setup page appears, so
72
+ the wizard starts with workspace and owner details. After confirmation, Compose
73
+ restarts the app into the sign-in screen. AI models are optional and can be
74
+ connected in Providers after signing in. Mail remains off until the operator
75
+ sets `FD_AUTH_MAIL_TRANSPORT=smtp`, `FD_AUTH_SMTP_URL` and `FD_AUTH_MAIL_FROM`.
76
+
77
+ To run Compose directly, copy `infra/docker/.env.example` to
78
+ `infra/docker/.env`, fill every required secret, set
79
+ `FD_AUTH_SECURE_COOKIE=false` for local HTTP, and run
80
+ `docker compose --env-file infra/docker/.env -f infra/docker/compose.yaml up --build`.
81
+ The setup token is in the `app` container logs. Keep that token out of URLs and
82
+ shared logs. For remote access, configure a TLS proxy, `FD_AUTH_PUBLIC_ORIGIN`,
83
+ `FD_BIND_ADDRESS` and secure cookies.
84
+
85
+ Compose gives the PostgreSQL runtime and background roles neither `SUPERUSER`
86
+ nor `BYPASSRLS`, and the migrator owns the schema. Its TLS certificate is
87
+ created once and verified by the app. The image ships the bundled server and a
88
+ small entrypoint that URL-encodes database passwords before starting it.
89
+
90
+ Back up the PostgreSQL data and WAL volumes, the MinIO object volume, and
91
+ `infra/docker/.env` together. The env file holds the keys needed to decrypt
92
+ records and objects after restore. `infra/docker/pitr.sh` can create a base
93
+ backup and replay archived PostgreSQL WAL segments; run its `--help` before a
94
+ restore, which requires explicit confirmation and stops the app when requested.
42
95
 
43
96
  ## Migrations on rollout
44
97
 
45
- Every module owns its migrations and applies them itself, inside a migration
46
- lease that connects as `FD_DATABASE_MIGRATOR_URL`, the first time its runtime is
47
- used. A rollout therefore needs no apply step: start the new image and the
48
- schema catches up under the schema-owning role, while requests keep running as
49
- the runtime role.
50
-
51
- What a rollout should do is fail before the new image serves traffic when an
52
- already applied migration no longer matches the code being deployed. That is the
53
- one-shot `migration-check` service in `infra/docker/compose.yaml`: it runs
54
- `flowdular migration status --json`, reports what is pending, and exits non-zero
55
- on a checksum mismatch. `app` starts only after it completes successfully.
56
-
57
- ```bash
58
- docker compose -f infra/docker/compose.yaml run --rm migration-check
59
- ```
60
-
61
- Outside compose, run the same check from a checkout with the deployment's
62
- `FD_DATABASE_*` values in the environment. On Kubernetes it belongs in a
63
- pre-upgrade Job or in the pipeline step ahead of `kubectl apply`. After the
64
- rollout, `flowdular migration verify` should come back clean.
65
-
66
- `flowdular migration apply` exists, but it is not part of a rollout. It takes one
67
- module at a time (`--module <id> --apply`; there is no `--all` flag) and the
68
- capability is local-only: the runner refuses it unless `FD_ENV` or `NODE_ENV` is
69
- `development` or `test`. Use it on a workstation against a local database:
70
-
71
- ```bash
72
- pnpm flowdular migration status # pending work, per module
73
- pnpm flowdular migration apply --module example.core # dry run
74
- pnpm flowdular migration apply --module example.core --apply
75
- ```
76
-
77
- Repeat the last two lines for each module `migration status` reports.
78
-
79
- Applied migrations are immutable. To change schema, add a numbered migration;
80
- never edit one the ledger already recorded.
98
+ Module migrations run through the migration role on first use. Applied
99
+ migrations are immutable: add a numbered migration rather than editing one
100
+ that is already in the ledger. Check migration status with the workspace CLI
101
+ before a production rollout, and verify it afterwards.
81
102
 
82
103
  ## Published image
83
104
 
@@ -93,9 +114,9 @@ kubectl create secret generic flowdular-secrets \
93
114
  --from-literal=workflowsPayloadKey="$(openssl rand -base64 32)" \
94
115
  --from-literal=workflowsCursorKey="$(openssl rand -base64 32)"
95
116
  kubectl create secret generic flowdular-database \
96
- --from-literal=migratorUrl='postgresql://coreloom_migrator:...@postgres:5432/flowdular' \
97
- --from-literal=runtimeUrl='postgresql://coreloom_runtime:...@postgres:5432/flowdular' \
98
- --from-literal=backgroundUrl='postgresql://coreloom_background:...@postgres:5432/flowdular'
117
+ --from-literal=migratorUrl='postgresql://flowdular_migrator:...@postgres:5432/flowdular' \
118
+ --from-literal=runtimeUrl='postgresql://flowdular_runtime:...@postgres:5432/flowdular' \
119
+ --from-literal=backgroundUrl='postgresql://flowdular_background:...@postgres:5432/flowdular'
99
120
  kubectl apply -k infra/kubernetes
100
121
  ```
101
122
 
@@ -0,0 +1,66 @@
1
+ # node infra/docker/start.mjs creates infra/docker/.env automatically.
2
+ # To use docker compose directly, copy this file and fill every required secret.
3
+ # Never commit the copy. Generate encryption keys with: openssl rand -base64 32
4
+ # Set a different project name for each app so their named volumes cannot mix.
5
+ COMPOSE_PROJECT_NAME=
6
+ FD_AGENT_CREDENTIAL_KEY=
7
+ FD_AGENT_CREDENTIAL_KEY_PREVIOUS=
8
+ FD_AGENT_RUN_GRANT_KEY=
9
+ FD_AUTH_MFA_KEY=
10
+ FD_AUTH_MFA_KEY_PREVIOUS=
11
+ FD_APPROVAL_GRANT_KEY=
12
+ FD_APPROVAL_GRANT_KEY_PREVIOUS=
13
+ FD_AUTOMATIONS_CREDENTIAL_KEY=
14
+ FD_AUTOMATIONS_CREDENTIAL_KEY_PREVIOUS=
15
+ FD_NOTIFICATIONS_SECRET_KEY=
16
+ FD_NOTIFICATIONS_SECRET_KEY_PREVIOUS=
17
+ FD_WORKFLOWS_PAYLOAD_KEY=
18
+ FD_WORKFLOWS_PAYLOAD_KEY_PREVIOUS=
19
+ FD_WORKFLOWS_CURSOR_KEY=
20
+ FD_WORKFLOWS_CURSOR_KEY_PREVIOUS=
21
+ FD_STORAGE_ENCRYPTION_KEY=
22
+ FD_STORAGE_ENCRYPTION_KEY_PREVIOUS=
23
+ FD_CONNECTORS_SECRET_KEY=
24
+ FD_CONNECTORS_SECRET_KEY_PREVIOUS=
25
+ FD_AUDIT_ANCHOR_KEY=
26
+ FD_AUDIT_ANCHOR_KEY_PREVIOUS=
27
+
28
+ # The default S3-compatible store is the MinIO service in compose.yaml, with a
29
+ # private flowdular bucket. Override these values for an external S3 service.
30
+ # Back up the MinIO object volume with PostgreSQL and this .env file.
31
+ FD_MINIO_ROOT_PASSWORD=
32
+ FD_STORAGE_S3_BUCKET=
33
+ FD_STORAGE_S3_REGION=
34
+ FD_STORAGE_S3_ENDPOINT=
35
+ FD_STORAGE_S3_ACCESS_KEY_ID=
36
+ FD_STORAGE_S3_SECRET_ACCESS_KEY=
37
+ FD_STORAGE_S3_FORCE_PATH_STYLE=true
38
+
39
+ # PostgreSQL credentials. The superuser only bootstraps the cluster; the app
40
+ # connects as flowdular_runtime and migrates as flowdular_migrator. Newly created
41
+ # passwords are URL-safe hex. Existing passwords are kept as-is.
42
+ FD_POSTGRES_SUPERUSER_PASSWORD=
43
+ FD_DATABASE_MIGRATOR_PASSWORD=
44
+ FD_DATABASE_RUNTIME_PASSWORD=
45
+ FD_DATABASE_BACKGROUND_PASSWORD=
46
+
47
+ # Outgoing mail. With none, workspace invitations are refused and password reset
48
+ # messages are never delivered. Set smtp and both values below to send them; the
49
+ # URL carries the relay password, so treat this file as a secret.
50
+ FD_AUTH_MAIL_TRANSPORT=none
51
+ FD_AUTH_SMTP_URL=
52
+ FD_AUTH_MAIL_FROM=
53
+
54
+ # Host port for the published container port 3000.
55
+ FD_PORT=3000
56
+ FD_BIND_ADDRESS=127.0.0.1
57
+ FD_APPLICATION_PATH=/app
58
+ FD_AUTH_PUBLIC_ORIGIN=
59
+
60
+ # The local launcher writes false for HTTP on a workstation. For a deployment
61
+ # behind TLS, set true and set FD_AUTH_PUBLIC_ORIGIN to its HTTPS origin.
62
+ FD_AUTH_SECURE_COOKIE=
63
+
64
+ # Prometheus exposition on GET /api/metrics. Leave FD_METRICS unset to keep it off.
65
+ FD_METRICS=false
66
+ FD_METRICS_TOKEN=
@@ -7,9 +7,17 @@ WORKDIR /workspace
7
7
 
8
8
  RUN corepack enable && corepack prepare pnpm@11.17.0 --activate
9
9
 
10
+ # The image only builds: tests and checks run in CI before a tag is cut. The
11
+ # package store is fetched from the lockfile alone, so this layer is reused
12
+ # until the lockfile changes.
13
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
14
+ RUN pnpm fetch
10
15
  COPY . .
11
- RUN pnpm install --frozen-lockfile
12
- RUN pnpm verify && pnpm build
16
+ RUN pnpm install --frozen-lockfile --offline
17
+ RUN pnpm build
18
+ RUN mkdir -p module-plans \
19
+ && if [ ! -f flowdular.modules.lock.json ]; then printf '{"schemaVersion":1,"modules":[]}\n' > flowdular.modules.lock.json; fi \
20
+ && if [ ! -f flowdular.module-sources.json ]; then printf '{"schemaVersion":1,"sources":[]}\n' > flowdular.module-sources.json; fi
13
21
 
14
22
  FROM node:24-bookworm-slim AS runtime
15
23
 
@@ -17,21 +25,27 @@ ENV NODE_ENV=production
17
25
  ENV PORT=3000
18
26
  WORKDIR /app
19
27
 
20
- RUN groupadd --system --gid 1001 flowdular \
21
- && useradd --system --uid 1001 --gid flowdular flowdular \
28
+ RUN groupadd --system --gid 1001 octane \
29
+ && useradd --system --uid 1001 --gid octane octane \
22
30
  && mkdir -p /data \
23
- && chown flowdular:flowdular /data
31
+ && chown octane:octane /data
24
32
 
25
33
  # The server bundle is self-contained: platform/dist/server/entry.js imports
26
34
  # only node:* built-ins, so no node_modules (and no devDependencies) ship.
27
- COPY --from=builder --chown=flowdular:flowdular /workspace/platform/dist ./platform/dist
28
- COPY --from=builder --chown=flowdular:flowdular /workspace/platform/package.json ./platform/package.json
29
-
30
- USER flowdular
35
+ COPY --from=builder --chown=octane:octane /workspace/platform/dist ./platform/dist
36
+ COPY --from=builder --chown=octane:octane /workspace/platform/package.json ./platform/package.json
37
+ COPY --chown=octane:octane infra/docker/app-entrypoint.mjs infra/docker/database-urls.mjs ./infra/docker/
38
+ COPY --from=builder --chown=octane:octane /workspace/flowdular.json ./flowdular.json
39
+ COPY --from=builder --chown=octane:octane /workspace/flowdular.modules.lock.json ./flowdular.modules.lock.json
40
+ COPY --from=builder --chown=octane:octane /workspace/flowdular.module-sources.json ./flowdular.module-sources.json
41
+ COPY --from=builder --chown=octane:octane /workspace/module-plans ./module-plans
42
+ COPY --from=builder --chown=octane:octane /workspace/modules ./modules
43
+
44
+ USER octane
31
45
  EXPOSE 3000
32
46
  VOLUME ["/data"]
33
47
 
34
48
  HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
35
49
  CMD node -e "fetch('http://127.0.0.1:3000/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"
36
50
 
37
- CMD ["node", "platform/dist/server/entry.js"]
51
+ CMD ["node", "--import", "./infra/docker/app-entrypoint.mjs", "platform/dist/server/entry.js"]
@@ -0,0 +1,5 @@
1
+ import { installDatabaseUrls } from './database-urls.mjs';
2
+
3
+ if (process.env.FD_DATABASE_ADAPTER === 'postgresql') {
4
+ installDatabaseUrls(process.env);
5
+ }
@@ -5,6 +5,7 @@ services:
5
5
  volumes:
6
6
  - ./postgres/tls-init.sh:/tls-init.sh:ro
7
7
  - flowdular-postgres-tls:/tls
8
+ - flowdular-postgres-wal:/var/lib/postgresql/wal-archive
8
9
  restart: 'no'
9
10
 
10
11
  postgres:
@@ -14,10 +15,10 @@ services:
14
15
  condition: service_completed_successfully
15
16
  environment:
16
17
  POSTGRES_DB: flowdular
17
- POSTGRES_PASSWORD: ${FD_POSTGRES_SUPERUSER_PASSWORD:?Set FD_POSTGRES_SUPERUSER_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
18
- FD_DATABASE_MIGRATOR_PASSWORD: ${FD_DATABASE_MIGRATOR_PASSWORD:?Set FD_DATABASE_MIGRATOR_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
19
- FD_DATABASE_RUNTIME_PASSWORD: ${FD_DATABASE_RUNTIME_PASSWORD:?Set FD_DATABASE_RUNTIME_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
20
- FD_DATABASE_BACKGROUND_PASSWORD: ${FD_DATABASE_BACKGROUND_PASSWORD:?Set FD_DATABASE_BACKGROUND_PASSWORD in infra/docker/.env (openssl rand -base64 32)}
18
+ POSTGRES_PASSWORD: ${FD_POSTGRES_SUPERUSER_PASSWORD:?Run node infra/docker/start.mjs or set FD_POSTGRES_SUPERUSER_PASSWORD}
19
+ FD_DATABASE_MIGRATOR_PASSWORD: ${FD_DATABASE_MIGRATOR_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_MIGRATOR_PASSWORD}
20
+ FD_DATABASE_RUNTIME_PASSWORD: ${FD_DATABASE_RUNTIME_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_RUNTIME_PASSWORD}
21
+ FD_DATABASE_BACKGROUND_PASSWORD: ${FD_DATABASE_BACKGROUND_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_BACKGROUND_PASSWORD}
21
22
  command:
22
23
  - postgres
23
24
  - -c
@@ -26,8 +27,21 @@ services:
26
27
  - ssl_cert_file=/tls/server.crt
27
28
  - -c
28
29
  - ssl_key_file=/tls/server.key
30
+ # Point-in-time recovery: every completed WAL segment is copied into the
31
+ # archive volume, and a quiet database still ships one every five
32
+ # minutes. See infra/README.md, "Backups and PITR", for the limits of a
33
+ # plain cp archive and for the base backup this needs.
34
+ - -c
35
+ - wal_level=replica
36
+ - -c
37
+ - archive_mode=on
38
+ - -c
39
+ - archive_timeout=300
40
+ - -c
41
+ - 'archive_command=test ! -f /var/lib/postgresql/wal-archive/%f && cp %p /var/lib/postgresql/wal-archive/%f'
29
42
  volumes:
30
43
  - flowdular-postgres:/var/lib/postgresql/data
44
+ - flowdular-postgres-wal:/var/lib/postgresql/wal-archive
31
45
  - flowdular-postgres-tls:/tls:ro
32
46
  - ./postgres/10-roles.sh:/docker-entrypoint-initdb.d/10-roles.sh:ro
33
47
  healthcheck:
@@ -40,29 +54,40 @@ services:
40
54
  - no-new-privileges:true
41
55
  restart: unless-stopped
42
56
 
43
- # Rollout gate. Each module applies its own migrations on first use through a
44
- # migration lease, so this one-shot run does not apply anything: it reports
45
- # what is pending and fails the rollout when an applied migration no longer
46
- # matches the checksum of the code being deployed. It needs the workspace and
47
- # the CLI, so it runs the builder stage rather than the runtime image.
48
- migration-check:
49
- build:
50
- context: ../..
51
- dockerfile: infra/docker/Dockerfile
52
- target: builder
53
- image: flowdular-app-builder:local
54
- depends_on:
55
- postgres:
56
- condition: service_healthy
57
- command: ['pnpm', 'flowdular', 'migration', 'status', '--json']
57
+ minio:
58
+ image: minio/minio:RELEASE.2025-09-07T16-13-09Z
59
+ command: server /data --console-address :9001
58
60
  environment:
59
- FD_DATABASE_ADAPTER: postgresql
60
- FD_DATABASE_URL: postgresql://coreloom_runtime:${FD_DATABASE_RUNTIME_PASSWORD}@postgres:5432/flowdular
61
- FD_DATABASE_MIGRATOR_URL: postgresql://coreloom_migrator:${FD_DATABASE_MIGRATOR_PASSWORD}@postgres:5432/flowdular
62
- FD_DATABASE_TLS: verify-full
63
- FD_DATABASE_TLS_CA_FILE: /tls/server.crt
61
+ MINIO_ROOT_USER: ${FD_MINIO_ROOT_USER:-flowdularlocal}
62
+ MINIO_ROOT_PASSWORD: ${FD_MINIO_ROOT_PASSWORD:?Run node infra/docker/start.mjs or set FD_MINIO_ROOT_PASSWORD}
64
63
  volumes:
65
- - flowdular-postgres-tls:/tls:ro
64
+ - flowdular-objects:/data
65
+ security_opt:
66
+ - no-new-privileges:true
67
+ restart: unless-stopped
68
+
69
+ minio-init:
70
+ image: minio/mc:RELEASE.2025-08-13T08-35-41Z
71
+ entrypoint: /bin/sh
72
+ command:
73
+ - -ec
74
+ - |
75
+ for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do
76
+ if mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD" >/dev/null 2>&1; then
77
+ mc mb --ignore-existing "local/$$FD_STORAGE_S3_BUCKET"
78
+ exit 0
79
+ fi
80
+ sleep 2
81
+ done
82
+ echo 'MinIO did not become ready within 60 seconds.' >&2
83
+ exit 1
84
+ depends_on:
85
+ minio:
86
+ condition: service_started
87
+ environment:
88
+ MINIO_ROOT_USER: ${FD_MINIO_ROOT_USER:-flowdularlocal}
89
+ MINIO_ROOT_PASSWORD: ${FD_MINIO_ROOT_PASSWORD:?Run node infra/docker/start.mjs or set FD_MINIO_ROOT_PASSWORD}
90
+ FD_STORAGE_S3_BUCKET: ${FD_STORAGE_S3_BUCKET:-flowdular}
66
91
  security_opt:
67
92
  - no-new-privileges:true
68
93
  restart: 'no'
@@ -71,11 +96,11 @@ services:
71
96
  build:
72
97
  context: ../..
73
98
  dockerfile: infra/docker/Dockerfile
74
- image: flowdular-app:local
99
+ image: flowdular:local
75
100
  depends_on:
76
101
  postgres:
77
102
  condition: service_healthy
78
- migration-check:
103
+ minio-init:
79
104
  condition: service_completed_successfully
80
105
  environment:
81
106
  NODE_ENV: production
@@ -83,29 +108,56 @@ services:
83
108
  # Every module reads and writes through the platform provider; the
84
109
  # deployment has one database and no per-module files.
85
110
  FD_DATABASE_ADAPTER: postgresql
86
- FD_DATABASE_URL: postgresql://coreloom_runtime:${FD_DATABASE_RUNTIME_PASSWORD}@postgres:5432/flowdular
87
- FD_DATABASE_MIGRATOR_URL: postgresql://coreloom_migrator:${FD_DATABASE_MIGRATOR_PASSWORD}@postgres:5432/flowdular
111
+ # The image entrypoint encodes these passwords before making PostgreSQL
112
+ # URLs. Older deployments may have generated base64 values containing /.
113
+ FD_DATABASE_RUNTIME_PASSWORD: ${FD_DATABASE_RUNTIME_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_RUNTIME_PASSWORD}
114
+ FD_DATABASE_MIGRATOR_PASSWORD: ${FD_DATABASE_MIGRATOR_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_MIGRATOR_PASSWORD}
88
115
  # Cross-tenant reads (the scheduler poll) run as this role. It carries no
89
116
  # blanket grant: a migration hands it named columns under its own policy.
90
- FD_DATABASE_BACKGROUND_URL: postgresql://coreloom_background:${FD_DATABASE_BACKGROUND_PASSWORD}@postgres:5432/flowdular
117
+ FD_DATABASE_BACKGROUND_PASSWORD: ${FD_DATABASE_BACKGROUND_PASSWORD:?Run node infra/docker/start.mjs or set FD_DATABASE_BACKGROUND_PASSWORD}
118
+ FD_DATABASE_URL: ${FD_DATABASE_URL:-}
119
+ FD_DATABASE_MIGRATOR_URL: ${FD_DATABASE_MIGRATOR_URL:-}
120
+ FD_DATABASE_BACKGROUND_URL: ${FD_DATABASE_BACKGROUND_URL:-}
91
121
  FD_DATABASE_TLS: verify-full
92
122
  FD_DATABASE_TLS_CA_FILE: /tls/server.crt
123
+ FD_AUTH_PUBLIC_ORIGIN: ${FD_AUTH_PUBLIC_ORIGIN:-http://localhost:${FD_PORT:-3000}}
124
+ FD_APPLICATION_PATH: ${FD_APPLICATION_PATH:-/app}
125
+ FD_SETUP_AUTO_RESTART: 'true'
93
126
  FD_AUTH_ALLOW_SIGN_UP: 'false'
94
127
  # TLS terminates in front of this container. Only a plain-HTTP run on a
95
128
  # workstation may set FD_AUTH_SECURE_COOKIE=false in .env.
96
129
  FD_AUTH_SECURE_COOKIE: ${FD_AUTH_SECURE_COOKIE:-true}
97
- # Every key comes from infra/docker/.env (copy the root .env.example).
98
- # Compose refuses to start without them; the owning module would refuse at
99
- # boot anyway.
100
- FD_AGENT_CREDENTIAL_KEY: ${FD_AGENT_CREDENTIAL_KEY:?Set FD_AGENT_CREDENTIAL_KEY in infra/docker/.env (openssl rand -base64 32)}
101
- FD_AGENT_RUN_GRANT_KEY: ${FD_AGENT_RUN_GRANT_KEY:?Set FD_AGENT_RUN_GRANT_KEY in infra/docker/.env (openssl rand -base64 32)}
102
- FD_AUTOMATIONS_CREDENTIAL_KEY: ${FD_AUTOMATIONS_CREDENTIAL_KEY:?Set FD_AUTOMATIONS_CREDENTIAL_KEY in infra/docker/.env (openssl rand -base64 32)}
103
- FD_NOTIFICATIONS_SECRET_KEY: ${FD_NOTIFICATIONS_SECRET_KEY:?Set FD_NOTIFICATIONS_SECRET_KEY in infra/docker/.env (openssl rand -base64 32)}
104
- FD_WORKFLOWS_PAYLOAD_KEY: ${FD_WORKFLOWS_PAYLOAD_KEY:?Set FD_WORKFLOWS_PAYLOAD_KEY in infra/docker/.env (openssl rand -base64 32)}
105
- FD_WORKFLOWS_CURSOR_KEY: ${FD_WORKFLOWS_CURSOR_KEY:?Set FD_WORKFLOWS_CURSOR_KEY in infra/docker/.env (openssl rand -base64 32)}
106
- FD_STORAGE_ENCRYPTION_KEY: ${FD_STORAGE_ENCRYPTION_KEY:?Set FD_STORAGE_ENCRYPTION_KEY in infra/docker/.env (openssl rand -base64 32)}
107
- FD_CONNECTORS_SECRET_KEY: ${FD_CONNECTORS_SECRET_KEY:?Set FD_CONNECTORS_SECRET_KEY in infra/docker/.env (openssl rand -base64 32)}
108
- FD_AUDIT_ANCHOR_KEY: ${FD_AUDIT_ANCHOR_KEY:?Set FD_AUDIT_ANCHOR_KEY in infra/docker/.env (openssl rand -base64 32)}
130
+ FD_AUTH_MFA_KEY: ${FD_AUTH_MFA_KEY:?Run node infra/docker/start.mjs or set FD_AUTH_MFA_KEY}
131
+ FD_AUTH_MFA_KEY_PREVIOUS: ${FD_AUTH_MFA_KEY_PREVIOUS:-}
132
+ # Outgoing mail. none refuses invitations and delivers no password reset
133
+ # message; smtp requires the URL and the sender below.
134
+ FD_AUTH_MAIL_TRANSPORT: ${FD_AUTH_MAIL_TRANSPORT:-none}
135
+ FD_TRUST_PROXY: ${FD_TRUST_PROXY:-false}
136
+ FD_METRICS: ${FD_METRICS:-false}
137
+ FD_METRICS_TOKEN: ${FD_METRICS_TOKEN:-}
138
+ FD_AUTH_SMTP_URL: ${FD_AUTH_SMTP_URL:-}
139
+ FD_AUTH_MAIL_FROM: ${FD_AUTH_MAIL_FROM:-}
140
+ # start.mjs creates these once in infra/docker/.env. Existing values win.
141
+ FD_AGENT_CREDENTIAL_KEY: ${FD_AGENT_CREDENTIAL_KEY:?Run node infra/docker/start.mjs or set FD_AGENT_CREDENTIAL_KEY}
142
+ FD_AGENT_RUN_GRANT_KEY: ${FD_AGENT_RUN_GRANT_KEY:?Run node infra/docker/start.mjs or set FD_AGENT_RUN_GRANT_KEY}
143
+ FD_APPROVAL_GRANT_KEY: ${FD_APPROVAL_GRANT_KEY:?Run node infra/docker/start.mjs or set FD_APPROVAL_GRANT_KEY}
144
+ FD_AUTOMATIONS_CREDENTIAL_KEY: ${FD_AUTOMATIONS_CREDENTIAL_KEY:?Run node infra/docker/start.mjs or set FD_AUTOMATIONS_CREDENTIAL_KEY}
145
+ FD_NOTIFICATIONS_SECRET_KEY: ${FD_NOTIFICATIONS_SECRET_KEY:?Run node infra/docker/start.mjs or set FD_NOTIFICATIONS_SECRET_KEY}
146
+ FD_WORKFLOWS_PAYLOAD_KEY: ${FD_WORKFLOWS_PAYLOAD_KEY:?Run node infra/docker/start.mjs or set FD_WORKFLOWS_PAYLOAD_KEY}
147
+ FD_WORKFLOWS_CURSOR_KEY: ${FD_WORKFLOWS_CURSOR_KEY:?Run node infra/docker/start.mjs or set FD_WORKFLOWS_CURSOR_KEY}
148
+ FD_STORAGE_ENCRYPTION_KEY: ${FD_STORAGE_ENCRYPTION_KEY:?Run node infra/docker/start.mjs or set FD_STORAGE_ENCRYPTION_KEY}
149
+ FD_CONNECTORS_SECRET_KEY: ${FD_CONNECTORS_SECRET_KEY:?Run node infra/docker/start.mjs or set FD_CONNECTORS_SECRET_KEY}
150
+ FD_AUDIT_ANCHOR_KEY: ${FD_AUDIT_ANCHOR_KEY:?Run node infra/docker/start.mjs or set FD_AUDIT_ANCHOR_KEY}
151
+ # Previous keys are operator-managed during rotation and remain empty on a new install.
152
+ FD_AGENT_CREDENTIAL_KEY_PREVIOUS: ${FD_AGENT_CREDENTIAL_KEY_PREVIOUS:-}
153
+ FD_APPROVAL_GRANT_KEY_PREVIOUS: ${FD_APPROVAL_GRANT_KEY_PREVIOUS:-}
154
+ FD_AUTOMATIONS_CREDENTIAL_KEY_PREVIOUS: ${FD_AUTOMATIONS_CREDENTIAL_KEY_PREVIOUS:-}
155
+ FD_NOTIFICATIONS_SECRET_KEY_PREVIOUS: ${FD_NOTIFICATIONS_SECRET_KEY_PREVIOUS:-}
156
+ FD_WORKFLOWS_PAYLOAD_KEY_PREVIOUS: ${FD_WORKFLOWS_PAYLOAD_KEY_PREVIOUS:-}
157
+ FD_WORKFLOWS_CURSOR_KEY_PREVIOUS: ${FD_WORKFLOWS_CURSOR_KEY_PREVIOUS:-}
158
+ FD_STORAGE_ENCRYPTION_KEY_PREVIOUS: ${FD_STORAGE_ENCRYPTION_KEY_PREVIOUS:-}
159
+ FD_CONNECTORS_SECRET_KEY_PREVIOUS: ${FD_CONNECTORS_SECRET_KEY_PREVIOUS:-}
160
+ FD_AUDIT_ANCHOR_KEY_PREVIOUS: ${FD_AUDIT_ANCHOR_KEY_PREVIOUS:-}
109
161
  # Object storage. Production refuses the local adapter, so the app writes
110
162
  # to an S3-compatible bucket and encrypts every object under
111
163
  # FD_STORAGE_ENCRYPTION_KEY before it leaves the process. The named volume
@@ -113,23 +165,14 @@ services:
113
165
  # run outside production for a demonstration.
114
166
  FD_STORAGE_ADAPTER: ${FD_STORAGE_ADAPTER:-s3}
115
167
  FD_STORAGE_LOCAL_DIRECTORY: /var/lib/flowdular/storage
116
- FD_STORAGE_S3_BUCKET: ${FD_STORAGE_S3_BUCKET:-}
117
- FD_STORAGE_S3_REGION: ${FD_STORAGE_S3_REGION:-}
118
- FD_STORAGE_S3_ENDPOINT: ${FD_STORAGE_S3_ENDPOINT:-}
119
- FD_STORAGE_S3_ACCESS_KEY_ID: ${FD_STORAGE_S3_ACCESS_KEY_ID:-}
120
- FD_STORAGE_S3_SECRET_ACCESS_KEY: ${FD_STORAGE_S3_SECRET_ACCESS_KEY:-}
121
- FD_STORAGE_S3_FORCE_PATH_STYLE: ${FD_STORAGE_S3_FORCE_PATH_STYLE:-false}
122
- # Optional: without it the app runs and only MFA enrollment is unavailable.
123
- FD_AUTH_MFA_KEY: ${FD_AUTH_MFA_KEY:-}
124
- # Outgoing mail. With none, invitations and password resets are refused.
125
- FD_AUTH_MAIL_TRANSPORT: ${FD_AUTH_MAIL_TRANSPORT:-none}
126
- FD_TRUST_PROXY: ${FD_TRUST_PROXY:-false}
127
- FD_METRICS: ${FD_METRICS:-false}
128
- FD_METRICS_TOKEN: ${FD_METRICS_TOKEN:-}
129
- FD_AUTH_SMTP_URL: ${FD_AUTH_SMTP_URL:-}
130
- FD_AUTH_MAIL_FROM: ${FD_AUTH_MAIL_FROM:-}
168
+ FD_STORAGE_S3_BUCKET: ${FD_STORAGE_S3_BUCKET:-flowdular}
169
+ FD_STORAGE_S3_REGION: ${FD_STORAGE_S3_REGION:-us-east-1}
170
+ FD_STORAGE_S3_ENDPOINT: ${FD_STORAGE_S3_ENDPOINT-}
171
+ FD_STORAGE_S3_ACCESS_KEY_ID: ${FD_STORAGE_S3_ACCESS_KEY_ID:-${FD_MINIO_ROOT_USER:-flowdularlocal}}
172
+ FD_STORAGE_S3_SECRET_ACCESS_KEY: ${FD_STORAGE_S3_SECRET_ACCESS_KEY:-${FD_MINIO_ROOT_PASSWORD:?Run node infra/docker/start.mjs or set FD_MINIO_ROOT_PASSWORD}}
173
+ FD_STORAGE_S3_FORCE_PATH_STYLE: ${FD_STORAGE_S3_FORCE_PATH_STYLE:-true}
131
174
  ports:
132
- - '${FD_PORT:-3000}:3000'
175
+ - '${FD_BIND_ADDRESS:-127.0.0.1}:${FD_PORT:-3000}:3000'
133
176
  healthcheck:
134
177
  test:
135
178
  - CMD
@@ -153,6 +196,10 @@ services:
153
196
 
154
197
  volumes:
155
198
  flowdular-postgres:
199
+ # WAL segments and base backups for point-in-time recovery (infra/docker/pitr.sh).
200
+ flowdular-postgres-wal:
156
201
  flowdular-postgres-tls:
157
202
  # Only the local adapter writes here; a production run uses the S3 bucket.
158
203
  flowdular-storage:
204
+ # Local S3-compatible storage; back up this volume alongside PostgreSQL.
205
+ flowdular-objects: