Project Setup
Cross-platform first-run setup for Docker and local Vortos development.
Project Setup
Vortos includes an interactive setup command for first-run project configuration:
php vortos setupThe command works on Linux, macOS, and Windows. It uses Symfony Console prompts and PHP filesystem APIs instead of Bash scripts, so it does not depend on rm, sh, stty, tput, or Unix symlinks.
Composer First Run
New projects run setup automatically through Composer:
{
"scripts": {
"post-create-project-cmd": [
"@php vortos setup --first-run"
]
}
}The setup command can be skipped by Composer with Composer's normal --no-scripts flag, then run later:
composer create-project vortos/vortos my-app --no-scripts
cd my-app
php vortos setupProfiles
Profiles are shortcuts for common setup paths:
php vortos setup --profile=docker
php vortos setup --profile=minimal| Profile | Runtime | Cache | Messaging | Use when |
|---|---|---|---|---|
docker | FrankenPHP Docker | Redis | Kafka | You want the current full Docker stack |
minimal | Local PHP | In-memory | In-memory | You want the quickest local setup |
custom | Interactive choice | Interactive choice | Interactive choice | You want to choose each setup category |
Profiles do not mean every application must use the same infrastructure. They are shortcuts over the capabilities Vortos currently ships. Future packages can add more choices.
Interactive setup asks for a profile first, then shows a review step:
Continue
Customize
CancelChoose Customize to walk through each setup category before setup writes files:
| Category | Current choices |
|---|---|
| Runtime | FrankenPHP Docker, PHP-FPM Docker, Local PHP |
| Write database | PostgreSQL (DBAL), PostgreSQL (Doctrine ORM) |
| Read database | None, MongoDB |
| Cache | Redis, in-memory cache |
| Messaging | Kafka, in-memory messaging |
| MCP | Install Vortos MCP server, skip Vortos MCP server |
Some categories have only one supported option today. They are still part of the custom flow so future packages can add more choices without changing how setup works.
Setup choices are registered as capabilities, not hardcoded in the setup command. The command reads capabilities by category and renders whatever is available.
Automatic Package Installation
When you choose a stack option that requires a package not yet installed, setup automatically runs composer require for the missing packages before finishing.
In --dry-run mode, setup prints the composer command instead of running it — useful for verifying what would be installed without making changes.
Example: choosing "PostgreSQL (Doctrine ORM)" automatically installs vortos/vortos-persistence-orm if it is not already present in your vendor/ directory.
For Docker profiles, setup may add targeted Composer platform ignores for PHP extensions that are installed inside the generated Docker image but not necessarily installed on the host PHP CLI. For example, a Docker stack using MongoDB, Redis, and Kafka can run:
composer require --ignore-platform-req=ext-mongodb --ignore-platform-req=ext-redis --ignore-platform-req=ext-rdkafka ...This keeps first-run setup fast: setup does not start or build Docker containers just to run Composer.
If composer install fails for any reason, setup prints the manual command to run so you can complete the step yourself:
composer require vortos/vortos-persistence-ormAdding Setup Options From Modules
A module can add a new setup option by registering a SetupCapabilityInterface service tagged with vortos.setup_capability.
Use this key format:
category.optionSupported categories:
| Category | Purpose | Example keys |
|---|---|---|
runtime | PHP runtime and process model | runtime.roadrunner |
write_db | Primary write database | write_db.mysql |
read_db | Optional read/projection database | read_db.postgres |
cache | Cache driver | cache.memcached |
messaging | Message broker or in-memory driver | messaging.rabbitmq |
mcp | AI client MCP integration | mcp.enabled |
Example module registration:
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Vortos\Setup\Capability\StaticSetupCapability;
$container->register('vortos.mysql.setup_capability', StaticSetupCapability::class)
->setArgument('$key', 'write_db.mysql')
->setArgument('$label', 'MySQL')
->setArgument('$category', 'write_db')
->setArgument('$composerPackages', ['vortos/vortos-persistence-mysql'])
->addTag('vortos.setup_capability')
->setPublic(false);That makes the option appear in the matching Customize step. The module still needs to provide the actual implementation behind the choice: services, config defaults, environment mapping, Docker support if needed, migrations if needed, and tests.
For a complete step-by-step guide covering all integration points — including the BUILT_IN_CAPABILITIES constant, the SetupCapabilityRegistry, key naming rules, load order, and ResetInterface requirements — see Adding Optional Modules.
Presets
Use the interactive menu or pass a preset explicitly:
php vortos setup --preset=docker-frankenphp
php vortos setup --preset=docker-phpfpm
php vortos setup --preset=local
php vortos setup --preset=minimal| Preset | Runtime | Cache | Messaging | Best for |
|---|---|---|---|---|
docker-frankenphp | Docker + FrankenPHP | Redis | Kafka | Default full-stack development |
docker-phpfpm | Docker + PHP-FPM/Nginx | Redis | Kafka | Teams standardizing on Nginx/PHP-FPM |
local | Local PHP | In-memory | In-memory | Fast local work without Docker services |
minimal | Local PHP | In-memory | In-memory | Learning, examples, CI smoke checks |
What Setup Writes
Setup writes generated local values to .env.
Do not commit .env, .vortos-setup.json, or generated backup files such as *.bak.*. Commit .env.example as the safe template for new projects.
Setup also publishes module config stubs into config/*.php when they do not already exist. These files are application-owned override points. Setup skips existing config files unless you explicitly publish configs with a force option through the config command.
config/services.php is not generated by setup. It is part of the project skeleton and is intended for application service registration.
The generated .env is grouped by section so related values stay together.
APP_ENV and APP_DEBUG are set based on the selected preset:
| Preset | APP_ENV | APP_DEBUG | HEALTH_DETAILS |
|---|---|---|---|
docker-frankenphp | prod | false | never |
docker-phpfpm | prod | false | never |
local | dev | true | debug |
minimal | dev | true | debug |
Example output for a local preset:
# App
APP_ENV=dev
APP_DEBUG=true
APP_NAME=my_app
# Write Database
VORTOS_WRITE_DB_DRIVER=postgres
VORTOS_WRITE_DB_USER=postgres
VORTOS_WRITE_DB_PASSWORD=...
VORTOS_WRITE_DB_NAME=my_app
VORTOS_WRITE_DB_DSN=pgsql://postgres:...@write_db:5432/my_app
# Read Database
VORTOS_READ_DB_DRIVER=mongo
VORTOS_READ_DB_USER=root
VORTOS_READ_DB_PASSWORD=...
VORTOS_READ_DB_DSN=mongodb://root:...@read_db:27017
VORTOS_READ_DB_NAME=my_app
# Cache
VORTOS_CACHE_DRIVER=redis
VORTOS_CACHE_DSN=redis://redis:6379
VORTOS_CACHE_PREFIX=dev_my_app_
# Messaging
VORTOS_MESSAGING_DRIVER=kafka
VORTOS_MESSAGING_DSN=kafka://kafka:9092
# Security
JWT_SECRET=...
HEALTH_DETAILS=never
HEALTH_TOKEN=...
HEALTH_EXPOSE_ERRORS=falseIf a capability is not selected, its service-specific env vars are not written. For example, choosing read_db.none does not write VORTOS_READ_DB_USER or VORTOS_READ_DB_PASSWORD.
Framework modules read Vortos env names, not vendor-specific container env names:
| Purpose | Framework env |
|---|---|
| Write database driver | VORTOS_WRITE_DB_DRIVER |
| Write database DSN | VORTOS_WRITE_DB_DSN |
| Read database driver | VORTOS_READ_DB_DRIVER |
| Read database DSN | VORTOS_READ_DB_DSN |
| Read database name | VORTOS_READ_DB_NAME |
| Cache driver | VORTOS_CACHE_DRIVER |
| Cache DSN | VORTOS_CACHE_DSN |
| Messaging driver | VORTOS_MESSAGING_DRIVER |
| Messaging DSN | VORTOS_MESSAGING_DSN |
| MongoDB cursor signing secret | VORTOS_CURSOR_SECRET |
Docker services require vendor-specific env names (POSTGRES_PASSWORD, MONGO_INITDB_ROOT_USERNAME, etc.). Setup writes agnostic equivalents (VORTOS_WRITE_DB_PASSWORD, VORTOS_READ_DB_USER, etc.) to .env. The published docker-compose.yaml maps these to vendor-specific names via environment: blocks — vendor names never appear in your env files.
Use the files this way:
| File | Commit? | Purpose |
|---|---|---|
.env | No | Generated local values, secrets, selected drivers, service DSNs |
.env.example | Yes | Example values and notes for new projects |
.vortos-setup.json | No | Local setup state used by repeat setup runs |
Setup generates passwords and stores them in .env. It does not ask for credentials during normal first-run setup. Existing secrets are preserved on rerun unless you pass --regenerate-secrets.
For Docker presets, setup publishes Docker files for the selected runtime. The generated compose file is capability-aware: unselected optional services are removed. For example, choosing read_db.none, in-memory cache, and in-memory messaging removes MongoDB, Redis, Kafka, and the worker service from the published compose file.
For local presets, setup configures in-memory cache and messaging so the framework can boot without Redis or Kafka.
MCP Setup
The Docker and local profiles install the optional Vortos MCP server by default. The minimal profile skips it by default. You can override this explicitly:
php vortos setup --profile=docker --mcp
php vortos setup --profile=docker --no-mcpWhen MCP is selected, setup installs vortos/vortos-mcp. In interactive setup it then asks:
Configure MCP in an AI client now? (yes/no) [no]:If you choose yes, setup runs:
php bin/console vortos:mcp:installThe MCP install command auto-detects supported AI clients. If multiple clients are detected, it prompts you to choose one or all detected clients. Supported clients include Codex, Claude Code, Cursor, and Windsurf.
The AI client later starts the server command automatically:
php /path/to/project/bin/console vortos:mcp:serveDo not run vortos:mcp:serve manually during normal use; it waits for MCP protocol messages on STDIN.
Repeat Runs
php vortos setup is safe to run more than once.
It detects previous setup state in .vortos-setup.json, preserves generated secrets, reports unchanged values, and creates backups before replacing existing Docker files.
php vortos setup --profile=minimal
php vortos setup --profile=dockerChanging choices is allowed, but infrastructure-sensitive changes still require developer judgment. For example, switching runtime is safe; switching databases after migrations have been applied should be reviewed before changing production data.
Dry Run
Preview setup without writing files:
php vortos setup --profile=docker --dry-run --no-interactionUse this in CI to verify the setup path without modifying the workspace.
Environment Checks
Setup checks:
| Check | Why it matters |
|---|---|
| PHP version | Vortos requires PHP 8.2+ |
| JSON/OpenSSL extensions | Required by framework services and token generation |
| Project writability | Required to write .env, Docker files, config stubs, and setup state |
| Docker CLI | Required for Docker presets |
| Redis/Mongo/Kafka extensions | Required locally when those services are selected outside Docker |
The command reports checks as OK or Needs attention. It does not hide missing tools.
Non-Interactive Setup
For CI, templates, and automated scaffolding:
php vortos setup \
--profile=docker \
--no-interactionUseful flags:
| Flag | Meaning |
|---|---|
--profile=... | Select a setup profile without prompts |
--preset=... | Select a setup without prompts |
--dry-run | Preview changes only |
--regenerate-secrets | Generate new local secrets and service passwords |
--skip-docker-publish | Configure env/state but do not publish Docker files |
--no-docker-overwrite | Skip changed Docker files |
--no-docker-backup | Overwrite Docker files without .bak files |
--publish-migrations | Include migration publishing in the final checklist |
--run-migrations | Include migration application in the final checklist |
--mcp / --no-mcp | Install or skip the optional MCP server package |
Local Development Without Docker
Vortos can run without Docker.
Use:
php vortos setup --profile=minimal
php -S 127.0.0.1:8000 -t publicThe local preset uses in-memory cache and messaging by default. If you want local PostgreSQL, Redis, MongoDB, or Kafka, install the service and required PHP extension locally, then adjust .env.
Production
Do not use in-memory cache or messaging for production. They are for local development, tests, and simple examples.