Skip to content

Bundle Postgres, Valkey and NATS into the kit image - #252

Merged
07prajwal2000 merged 5 commits into
Fluxify-rest:mainfrom
07prajwal2000:feat/kit-embedded-services
Aug 14, 2026
Merged

Bundle Postgres, Valkey and NATS into the kit image#252
07prajwal2000 merged 5 commits into
Fluxify-rest:mainfrom
07prajwal2000:feat/kit-embedded-services

Conversation

@07prajwal2000

Copy link
Copy Markdown
Collaborator

Why

The kit is the trial build, but getting it running still meant copying an environment file, editing it, and bringing up a four-container compose stack. Caddy was already inside the image, so the backing services were the only reason a compose file was needed at all.

They are now bundled. docker run -p 8080:8080 is a complete Fluxify instance with nothing else installed.

What

Bundled backing services. PostgreSQL, Valkey and NATS ship in the image and start automatically. Each one is skipped when its connection variable is already set (PG_URL / REDIS_HOST / NATS_URL), so docker/kit/docker-compose.yml drives the external topology from the same image with no changes. One image, both worlds, no second Dockerfile.

State lives in a /data volume: the Postgres cluster, the JetStream store, and the encryption/session keys generated on first boot when not supplied. Image is 338MB.

Admin credentials are enforced. The seeder skips silently when SEED_USER_EMAIL / SEED_USER_PASSWORD are missing or malformed, which leaves a running instance nobody can log into. A fresh database now refuses to boot instead, before initdb, with a message saying why.

The admin UI is apps/portal. The kit still built the legacy Next.js app, which no longer typechecks against the current server schemas (it imports test-suites/run/dto, renamed to start-run). Per AGENT.md that app is legacy and must not be edited, so the kit moved to the shipping UI. It is a static Vite bundle served straight by Caddy, so the UI process is gone entirely.

Bugs found on the way

The kit could not boot without these. All predate this branch:

  • entrypoint.sh did not supervise. A bare wait blocks until every child exits, so one dead service left the container "up" and quietly broken — that is how the AI gateway had been crash-looping unnoticed. It now exits non-zero on the first death and lets the restart policy handle it. Fixing this is what exposed the three below.
  • DOCS_INDEX_PATH used path.join on an absolute env value, producing /app/ai-gateway/app/ai-gateway/docs-index.bin. path.resolve honours absolute paths and still resolves relative ones against the module directory.
  • The AI gateway has no schema-wait and died on a missing app_config. Its startup is now gated on the admin server answering.
  • HEALTHCHECK pointed at /_/admin/api/healthchecks/startup, which does not exist — the container could never report healthy. Uses public-settings.

Also in here

  • Portal: the editor page is gone; routes open on the canvas. Two flow editors existed for the same job; the canvas is the real one (block picker, playground, route switcher, settings modal) while the editor was a thinner duplicate with "coming soon" tabs. All four entry points moved: the routes-list Open button, the post-create redirect, the AI resource chip, and the applied-artifact link. That last one had a target prop to choose between the two — dead flexibility now, so removed rather than pinned to a constant. components/editor/ went with it; nothing else imported it.
  • Portal: sidebar and AI-home logos were broken. Both hardcoded /_/admin/ui/public/icons/logo.svg; files under public/ serve from the bundle root, so the public/ segment matched nothing. Because the SPA fallback answers unknown paths with index.html, the request returned 200 with an HTML body rather than 404 — the browser got markup where an image was expected. Now uses BASE_URL, and webp.
  • .gitattributes pins shell scripts to LF. A CRLF shebang makes the kernel look for /bin/sh\r; the container dies with exec /app/entrypoint.sh failed: No such file or directory.
  • Root dev script launched @fluxify/web; points at @fluxify/portal now. Vite binds 0.0.0.0 so a containerised Caddy can reach it via host.docker.internal, and the bare /_/admin/ui path redirects to the trailing-slash form Vite's base actually serves.

Testing

Verified against a running container throughout, not just at the end:

  • Bundled mode boots clean to healthy on a fresh volume — migrations, seed user, compiler, worker, AI gateway.
  • Restart reuses the cluster and the generated secrets; no re-init, no regenerated keys.
  • UI, admin API, static assets and SPA deep links all serve; /_/admin/ui → 308 → 200.
  • A user route (GET /users) returns 200 through the proxy once WORKER_PROJECT_ID is set.
  • External mode (PG_URL / REDIS_HOST / NATS_URL set) starts zero bundled services, so compose is unaffected.
  • apps/portal typechecks and builds; the route tree regenerates with no editor entries.
  • Full suite: 740 passing, 2 skipped.

Not in scope

docker/production/ and docker/admin/ are untouched by request. They still build apps/web, which is worth a follow-up.

Docs

docs/deployments/kit.md leads with the one-command run and keeps bring-your-own-services as the second option. Covers the parts that bite: mount /data or lose everything, back up the generated keys, admin credentials are mandatory, and a bundled major database upgrade is not applied in place. Also documents WORKER_PROJECT_ID=* as a way to skip the create-project-then-restart step, with the path-collision caveat stated plainly.

Fixes two dead anchors in the deployments index and folds the backing services into the kit diagram.


Related: #251 tracks project-scoped route resolution, which is what would let the kit default to serving every project without the collision caveat.

🤖 Generated with Claude Code

07prajwal2000 and others added 5 commits August 14, 2026 23:45
The root dev script still launched @fluxify/web; the portal is the admin UI
now. Also binds vite to 0.0.0.0 so Caddy running in a container can reach the
dev server through host.docker.internal, and redirects the bare /_/admin/ui
path — vite's base only serves the trailing-slash form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The kit is the trial/demo build, so "copy env.example, edit it, run compose"
was the wrong first step. All three backing services now ship in the image and
start automatically, making `docker run -p 8080:8080` a complete stack.

Each bundled service is skipped when its connection variable is already set
(PG_URL / REDIS_HOST / NATS_URL), so docker-compose.yml drives the external
topology from the same image with no changes.

Also in here, because the kit could not boot without them:

- The admin UI is apps/portal now, not the legacy Next.js app. It is a static
  Vite bundle served directly by Caddy, so the UI process is gone entirely and
  the build no longer depends on @fluxify/web (which does not typecheck against
  the current server schemas).
- entrypoint.sh supervises properly. A bare `wait` blocks until every child
  exits, so one dead service left the container "up" and quietly broken — that
  is how the AI gateway had been crash-looping unnoticed. It now exits non-zero
  on the first death and lets the restart policy handle it.
- DOCS_INDEX_PATH used path.join against an absolute env value, producing
  /app/ai-gateway/app/ai-gateway/docs-index.bin. path.resolve honours absolute
  paths and still resolves relative ones against the module directory.
- The AI gateway has no schema-wait of its own and died on a missing app_config,
  so startup is now gated on the admin server answering.
- HEALTHCHECK pointed at /_/admin/api/healthchecks/startup, which does not
  exist; the container never became healthy. Uses public-settings instead.
- SEED_USER_EMAIL/PASSWORD are validated before initdb: a fresh database
  refuses to boot without them rather than coming up with no way to log in.
- .gitattributes pins shell scripts to LF. A CRLF shebang makes the kernel look
  for "/bin/sh\r" and the container dies with "No such file or directory".

Verified both paths: bundled boots clean to healthy on a fresh volume, reuses
the cluster and generated secrets on restart, and serves the UI, admin API and
worker routes; external mode skips all three bundled services. Image is 338MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The kit ships its own database, cache and event bus now, so the guide no longer
starts at "copy an env file and run compose". It leads with the one-command run
and keeps the bring-your-own-services path as the second option, since the image
picks between them on its own.

Covers the parts that bite: /data must be mounted or everything is discarded,
the generated keys in it need backing up, admin credentials are mandatory on a
fresh instance, and a bundled major database upgrade is not applied in place.
Also documents WORKER_PROJECT_ID=* as a way to skip the create-project-then-
restart step, with the path-collision caveat stated plainly.

Fixes two dead anchors in the deployments index that pointed at removed
sections, and folds the backing services into the kit diagram now that they are
inside the container.

Production docs deliberately untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two flow editors existed for the same job. The canvas is the real one — block
picker, playground, route switcher, settings modal — while the editor page was
a thinner duplicate whose non-editor tabs were still "coming soon".

Every entry point now goes to the canvas: the Open button on the routes list,
the redirect after creating a route, the AI resource chip, and the "Go to route"
link on an applied artifact.

That last one had a `target` prop to pick between the two destinations; with one
destination left it is dead flexibility, so it is gone rather than pinned to a
constant. components/editor went with the page — nothing else imported it, the
canvas has its own block catalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both hardcoded /_/admin/ui/public/icons/logo.svg. Files under public/ are served
from the bundle root, so the public/ segment resolves to nothing — and because
the SPA fallback answers unknown paths with index.html, the request returned 200
with an HTML body instead of 404. The browser got markup where an image was
expected and rendered nothing, which is why it looked like a routing fault.

Uses BASE_URL like the login and projects pages already do, so the path stays
correct if the base ever moves, and switches to webp (18KB).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@07prajwal2000
07prajwal2000 added this pull request to the merge queue Aug 14, 2026
Merged via the queue into Fluxify-rest:main with commit 180eb14 Aug 14, 2026
12 checks passed
@07prajwal2000
07prajwal2000 deleted the feat/kit-embedded-services branch August 16, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant