Infrastructure Generators

bext ships four CLI generators that produce infrastructure config tailored to your project. They read bext.config.toml, detect the framework from ambient file markers (like astro.config.mjs, Gemfile, manage.py), and emit a sensible default that you can commit and edit.

All four generators default to stdout so you can pipe them directly to a file, or pass --out <path> to write the file yourself.

bext dockerfile gen

Emits a multi-stage Dockerfile tailored to the detected framework.

bext dockerfile gen > Dockerfile
# or
bext dockerfile gen --out Dockerfile

The generated Dockerfile has three stages:

1. bext-base — the official bext runtime image (ghcr.io/bextdev/bext:latest by default; override with --bext-image) 2. framework-build — the framework's toolchain: oven/bun:1 for Astro / SvelteKit / SolidStart / Qwik / PRISM, ruby:3.3-slim for Rails, python:3.12-slim for Django, composer:2 + php:8.3-fpm-alpine for Laravel, elixir:1.16-alpine for Phoenix 3. runtime — final minimal image that copies the build output, sets a HEALTHCHECK against bext health, and runs bext serve

For projects where no framework is detected, the generator skips the framework-build stage and emits a minimal runtime that just copies the project tree and runs bext serve.

Flags

Flag Default Purpose
--dir <path> . Project root to read
--out <path> stdout Write Dockerfile to this path
--bext-image <tag> ghcr.io/bextdev/bext:latest Override the base image

bext ci gen

Emits a CI workflow for a supported platform. Wave 1 ships GitHub Actions; GitLab, CircleCI, Cloudflare, Fly, and Render return a TODO(E13 wave 2) error so the CLI surface is stable for later additions.

bext ci gen --platform github > .github/workflows/bext.yml
# with a deploy target
bext ci gen --platform github --deploy-target self-hosted --out .github/workflows/bext.yml
# no deploy step, just build + test
bext ci gen --platform github --deploy-target none

The default GitHub workflow has:

- A build job that checks out the repo, installs the Rust toolchain, installs bext via the curl-installer, and runs bext check, bext build, and bext test

- A deploy job that runs only on pushes to main/master, SSHes into DEPLOY_HOST using secrets, and runs bext upgrade + systemctl reload bext

Flags

Flag Default Purpose
--dir <path> . Project root to read
--platform <name> github CI platform (wave 1: github)
--deploy-target <kind> self-hosted self-hosted or none
--out <path> stdout Write workflow to this path

bext systemd gen

Emits a bext.service unit file (and optionally a companion bext.socket unit when --socket-activation is passed).

bext systemd gen > bext.service
# with socket activation
bext systemd gen --socket-activation > units.txt
# custom user + working directory
bext systemd gen --user www-data --working-dir /srv/bext

The output uses a sentinel ### FILE: bext.service header to separate each unit in the stream. Split on that header if you piped both units to a single file.

The default unit uses Type=notify, sets hardening flags (NoNewPrivileges, ProtectSystem=strict, PrivateTmp, PrivateDevices), and restarts on failure with a 2-second back-off.

With --socket-activation, the service gets Requires=bext.socket + Sockets=bext.socket and the socket unit listens on 0.0.0.0:80 + 0.0.0.0:443 with ReusePort=true. This is the shape the zero-downtime binary upgrade path expects — see Zero-Downtime Upgrades.

Flags

Flag Default Purpose
--dir <path> . Project root to read
--socket-activation off Also emit bext.socket and wire it into the service
--user <user> bext Unix user the service runs as
--working-dir <path> /opt/bext WorkingDirectory for the service
--out <path> stdout Write units to this path

bext export docker-compose

The reverse of bext import: walk your bext.config.toml and declared plugins, then emit a docker-compose.yaml suitable for local development.

bext export docker-compose > docker-compose.yaml

The bext service is always present. Backend services are auto-added from plugin naming conventions:

Plugin name contains Service added Env var wired
-pg, -postgres, -pgsql postgres:16-alpine DATABASE_URL
-redis redis:7-alpine REDIS_URL
-meili, meilisearch getmeili/meilisearch:v1.7 MEILI_URL

So a project that declares @bext/session-redis and @bext/locking-pg gets both redis and postgres services with the matching env vars wired into the bext service. Volumes and a shared bext-net network are emitted for every backend.

A richer version that reads requires_capabilities from plugin manifests is a wave-2 item; for v1 the naming convention is authoritative.

Flags

Flag Default Purpose
--dir <path> . Project root to read
--out <path> stdout Write compose to this path
--bext-image <tag> ghcr.io/bextdev/bext:latest Override the image used by the bext service

Regeneration

All four generators are pure functions of the project state — running them twice produces identical output. Re-run them after bumping bext or adding plugins and commit the diff; that's the intended review loop.

They also have no side effects beyond writing the target file. They never modify bext.config.toml, never fetch anything from the network, and never touch the plugin cache. This makes them safe to run inside CI pipelines, pre-commit hooks, or scripts.

Framework detection

The generators recognise:

- Astroastro.config.*

- SvelteKitsvelte.config.*

- SolidStartsolid.config.*

- Qwikqwik.config.*

- RailsGemfile with a gem 'rails' line

- Djangomanage.py

- Laravelcomposer.json with "laravel/framework"

- Phoenixmix.exs with :phoenix

- PRISM (React)package.json with a "react" dep

Detection is a thin file-marker scan, not a full project parse. If you hit a project the generators misidentify, please open an issue.