A framework you install, not a repo you fork. The PHP core, the React component library and the whole quality gate arrive through Composer and npm; your project keeps its own namespace and its own git history, and uBixVault holds its secrets.
$ composer create-project ubixsys/ubixcore-skeleton acme $ cd acme && APP_NAME=HelloApi php -S 127.0.0.1:8080 -t public $ curl 127.0.0.1:8080/health # {"status":"ok","app":"HelloApi"} $ bin/ubix code:review # phpcs · phpstan (level max) · phpunit
uBixCore is two things published together from one tree: a framework (ubixsys/ubixcore on Composer, @ubixsys/ubixcore on npm) and a skeleton (ubixsys/ubixcore-skeleton) that composer create-project turns into a working host project. The framework lives in vendor/ and is never committed to your repository; your committed composer.lock is what pins the version every image is built from.
The point of the split is that the tooling ships with the framework, not just the library: the bin/ubix CLI, the Ubix phpcs standard with its custom sniffs, a PHPStan baseline at level max, PHPUnit base classes that enforce the house rules on every class, the migration runner, and the Docker and GitLab CI templates. A new project starts with the whole workflow.
Ubix\ is the framework's namespace; you never write code there. Your project has its own PSR-4 root and the framework finds your controllers, commands and tests through Composer's map. Secrets are read from uBixVault at startup; non-secret configuration is environment on the Deployment. The image ships no .env, on purpose.
// php/Acme/Controller/HelloController.php namespace Acme\Controller; use Ubix\Controller\AbstractController as Controller; final class HelloController extends Controller { public function hello(Request $request, Response $response): Response { return $this->renderJson($response, ['hello' => 'world']); } } // app/AcmeApi/src/Routes.php $app->map(['GET'], '/hello', HelloController::class . ':hello');
$ bin/ubix code:review # same three tools CI runs, same rules from vendor/ $ vendor/bin/phpcs # <rule ref="Ubix"/> — 230+ sniffs $ vendor/bin/phpstan analyse # includes the framework's level-max baseline
$ bin/ubix migrate:diff # live schema vs checked-in reference $ bin/ubix migrate:up # apply in filename order, destructive-change guard $ bin/ubix migrate:status
$ composer update ubixsys/ubixcore $ git add composer.lock && git commit -m "chore: ubixcore 0.2.0" # one-line diff, one MR
vendor/. Nothing of it is committed; the lock file records which version built each image.Ubix\ is reserved for the framework. Controllers, models, data types and commands live under your own root — Acme\, Kitg\SowingMe\ — and the house rules apply to them by family, not by vendor prefix.docs/ci-setup.md lists every variable and secret a first green pipeline needs.bin/ubix finds commands through Composer's PSR-4 map; add your namespace to one array and your commands appear beside the framework's.Ubix phpcs standard with custom sniffs, PHPStan at level max, strict PHPUnit, and a test base that checks every class against the house rules — and proves each concrete class has a test.diff against a reference dump, and a destructive-statement guard with a backup before it applies..env in an image, ever.v* tag publishes the PHP framework, the React library and the skeleton with matching version numbers.@ubixsys/ubixcore: the shared component library, in TypeScript, for React Router apps built on the framework.A developer needs a running app; an operator needs a green pipeline. They are different jobs, so they get different walkthroughs.
Requirements: PHP 8.4+, Composer 2, git. Node 22+ only if you add a React app.
$ composer create-project ubixsys/ubixcore-skeleton acme $ cd acme # thin bin/ubix + public/index.php, a HelloApi app, php/App/ (your PSR-4 root), # phpcs/phpstan/phpunit configs pointing at vendor/, Dockerfile, pipeline, .env.example
$ git init -b dev && git add -A && git commit -m "chore: bootstrap from ubixsys/ubixcore-skeleton" # composer.lock is committed on purpose; vendor/, node_modules/ and .env* are not
$ php bin/ubix list # the framework's commands, plus yours $ APP_NAME=HelloApi php -S 127.0.0.1:8080 -t public $ curl 127.0.0.1:8080/health
# rename the namespace: composer.json autoload App\ -> Acme\, php/App -> php/Acme, # then the five files that say App\ (composer dump-autoload afterwards) $ bin/ubix code:review # green before your first commit
Assumed stack: GitLab with its container and package registries, a shell runner with Docker buildx and kubeconfigs, k3s namespaces per tier (ws-<env> for APIs, live-<env> for web), one uBixVault per tier with Kubernetes auth. Substitute your hosts; the shape is the same. Every step is spelled out in the skeleton's docs/ci-setup.md.
secret/<project>/composer, or add your project to the framework's job-token allowlist. The auth enters the build as a BuildKit secret, never a layer.UBIXVAULT_CI_TOKEN_DEV, UBIXVAULT_CI_TOKEN_PROD, GITLAB_PROMOTE_TOKEN.bin/vault-ci-setup.sh <env> <project>, admin token in the environment. It creates the read-only policy, writes test-db, composer and discord, and prints the CI token. Pod credentials live at secret/<app>/<env>/db, read through VAULT_ADDR + VAULT_K8S_ROLE.regcred, the wildcard-TLS label, and the non-secret runtime config (MEMCACHE_SERVERS, database host/port/name, log and template paths) as env on each Deployment.dev and read the first failure against the list at the end of docs/ci-setup.md; each maps to a step above.Lesson learned moving a real product onto this: a baked .env hides every setting an image depends on. Put secrets in uBixVault and the rest on the Deployment, and the image can be promoted between tiers unchanged.
What you get, where it goes, and what you type.
| Package | Registry | Notes |
|---|---|---|
| ubixsys/ubixcore | Composer | The PHP framework: Ubix\ namespace, CLI, standard, test bases, migrations. Source on GitHub. |
| ubixsys/ubixcore-skeleton | Composer | The create-project template. Source on GitHub. |
| @ubixsys/ubixcore | npm | The React 19 + TypeScript component library for React Router apps. |
| ghcr.io/ubixsys/ubixcore-php | container | The PHP runtime image: nginx + PHP-FPM + memcache(d) on Alpine, multi-arch, tags 8.5 and 8.4. Source on GitHub. |
| Path | Owner | Notes |
|---|---|---|
| composer.json / .lock | you | Requires ubixsys/ubixcore; the lock pins the framework your images are built from. |
| bin/ubix · public/index.php | you | Thin entry points that call Ubix\Bootstrap; the only values in them are yours (command namespaces, app name). |
| app/<App>/src/ | you | Dependencies.php (PHP-DI), Middleware.php, Routes.php, plus the app's Kubernetes manifests. One folder per deployable app; APP_NAME selects it per image. |
| php/<Vendor>/ | you | Your PSR-4 root. Families (Controller, Model, Repository, DataType…) get the house rules regardless of the prefix. |
| phpcs.xml · phpstan.neon · phpunit.xml | you | Point at the rules that ship in vendor/ubixsys/ubixcore; add your own overrides below them. |
| vendor/ubixsys/ubixcore | framework | Never committed. Upgrade with composer update ubixsys/ubixcore. |
| Command | Group | Notes |
|---|---|---|
| code:review | Code | phpcs, phpstan and phpunit as one gate, the same locally, in the pre-push hook and in CI. Also code:commit, code:merge, code:loc. |
| migrate:up · status · diff · reconcile | Migrate | Apply pending migrations, list state, diff the live schema against the reference dump, or record one as applied without running it. |
| app:run · app:build · app:deploy | App | Run an app locally, build its image, apply its manifests. |
| database:* | Database | Schema reset and bootstrap helpers for local and test databases. |
| cron:* | Cron | Scheduled jobs, loaded only when a cron command is invoked. |
| <yours> | any | Extend Ubix\Console\Command\AbstractCommand under your namespace; bin/ubix discovers it. |
Nothing in vendor/ knows where your project is or what it is called. The bootstrap tells it, once.
Your bin/ubix and public/index.php are a dozen lines each: they call Ubix\Bootstrap\environment(), then console() or http(). The bootstrap exports the project root, loads secrets from uBixVault, and builds the Symfony Console or Slim app from your app/<App>/src files.
Inside the framework, one service resolves every path from that root, so nothing computes locations from its own file. Commands are discovered through Composer's PSR-4 map, which is why a host namespace works with a one-line change.
The coding standard, PHPStan baseline and PHPUnit base classes are part of the package. A host's phpcs.xml is <rule ref="Ubix"/> plus its file list; the test base decides which rules apply to a class from its family segment, so a Kitg\SowingMe\Controller\Api\X is held to the same controller rules as anything in Ubix\Controller.