Vortos
Docker

Docker

Publish production-ready Docker files for FrankenPHP or PHP-FPM+Nginx with one command — vortos:docker:publish.

Docker

vortos-docker provides Docker infrastructure files for Vortos projects. Most applications get these files through php vortos setup, which publishes compose files for the selected runtime and removes optional services that were not selected. You can also publish the raw runtime stubs manually with vortos:docker:publish.

Installation

composer require vortos/vortos-docker

Package Registration

bootstrap/app.php
use Vortos\Docker\DependencyInjection\DockerPackage;

$packages = [
    // ... other packages
    new DockerPackage(),
];

Publish Docker Files

# FrankenPHP runtime (default — recommended)
php bin/console vortos:docker:publish

# PHP-FPM + Nginx runtime
php bin/console vortos:docker:publish --runtime=phpfpm
# or
php bin/console vortos:docker:publish -r phpfpm

Files are copied from the package's stubs/ directory into your project root. Changed existing files are backed up before replacement.

Setup Publishes Capability-Aware Compose Files

When Docker files are published by php vortos setup, the compose file is filtered for the chosen stack. For example, choosing no read database removes the MongoDB service, choosing in-memory cache removes Redis, and choosing in-memory messaging removes Kafka and the worker service.

The standalone vortos:docker:publish command publishes the runtime stub directly. Use setup when you want compose files matched to selected capabilities.

Useful options:

php bin/console vortos:docker:publish --dry-run
php bin/console vortos:docker:publish --no-overwrite
php bin/console vortos:docker:publish --no-backup

FrankenPHP (Default)

FrankenPHP is a modern PHP application server built on Caddy. It supports worker mode — PHP processes stay alive across requests, eliminating bootstrap overhead on every request.

Published Files

docker-compose.yaml
docker-compose.prod.yaml
.dockerignore
docker/
├── frankenphp/
│   └── Caddyfile                 ← HTTP/HTTPS routing config
├── php/
│   ├── Dockerfile                ← PHP + extensions + app
│   └── entrypoint.sh             ← boot preflight — runs vortos:cache:warmup before the server starts
├── postgres/
│   └── init/
│       ├── 001_create_database.sql
│       └── 002_vortos_tables.sql  ← messaging outbox + dead letter tables
└── worker/
    └── supervisord.conf           ← Kafka consumer process manager

Services in docker-compose.yaml

backend    — FrankenPHP (PHP + Caddy, port 80/443)
worker     — Kafka consumer worker (supervisord)
write_db   — PostgreSQL (write side)
read_db    — MongoDB (read side)
redis      — Cache + idempotency + rate limiting
kafka      — Message broker

worker, read_db, redis, and kafka are optional in setup-generated compose files. They appear only when the selected setup capabilities need them.

Worker Mode

FrankenPHP worker mode keeps PHP workers alive between requests. Runner::cleanUp() only clears ArrayAdapter — the compiled container and all services stay alive:

Request 1 → Runner::run() → compile container → handle → cleanUp() (clear ArrayAdapter only)
Request 2 → Runner::run() → use existing container → handle → cleanUp()
Request 3 → Runner::run() → use existing container → handle → cleanUp()

Boot time only on first request — all subsequent requests skip container compilation entirely.

PHP-FPM + Nginx

Traditional PHP-FPM with Nginx as reverse proxy. Compatible with any hosting environment that supports Docker.

Published Files

docker-compose.yaml
docker-compose.prod.yaml
.dockerignore
docker/
├── nginx/
│   ├── Dockerfile
│   └── default.conf              ← Nginx virtual host config
├── php/
│   ├── Dockerfile                ← PHP-FPM + extensions
│   └── entrypoint.sh             ← boot preflight — runs vortos:cache:warmup before the server starts
├── postgres/
│   └── init/
│       ├── 001_create_database.sql
│       └── 002_vortos_tables.sql
└── worker/
    └── supervisord.conf

Services

backend    — PHP-FPM
nginx      — Nginx reverse proxy (port 80)
worker     — Kafka consumer worker
write_db   — PostgreSQL
read_db    — MongoDB
redis      — Cache
kafka      — Message broker

worker, read_db, redis, and kafka are optional in setup-generated compose files. They appear only when the selected setup capabilities need them.

Development Workflow

# 1. Configure the project and publish Docker files
php vortos setup --profile=docker

# 2. Start all services
php vortos up

# 3. Run migrations / setup
docker compose exec backend php bin/console vortos:setup:persistence
docker compose exec backend php bin/console vortos:cache:clear
docker compose exec backend php bin/console vortos:cache:warmup

# 4. Watch logs
docker compose logs -f backend
docker compose logs -f worker

The generated .env contains agnostic Vortos variables such as VORTOS_WRITE_DB_PASSWORD and VORTOS_READ_DB_USER. Compose maps those values to service-specific variables like POSTGRES_PASSWORD and MONGO_INITDB_ROOT_USERNAME.

Boot Preflight

Every published Dockerfile includes a custom entrypoint.sh that runs php bin/console vortos:cache:warmup before the server or worker process starts.

If the Symfony container fails to compile — bad DI config, a broken compiler pass, a misconfigured #[OverrideImpl] or #[AsDecorator] attribute — the error is printed directly to the terminal during docker compose up and the container exits cleanly with code 1. No docker logs required.

backend  | 2 container configuration error(s) found:
backend  |
backend  |   [1] #[AsDecorator] on "App\Resolver\LoggingResolver" (src/Resolver/LoggingResolver.php:14)
backend  |       → No constructor parameter typed to "FlagContextResolverInterface" found.
backend  |
backend  |   [2] #[OverrideImpl] on "App\Cache\MyCache" (src/Cache/MyCache.php:8)
backend  |       → Class does not implement "CacheInterface".
backend exited with code 1

What the preflight catches

Any error thrown during Symfony container compilation: missing services, invalid aliases, compiler pass failures, and Vortos DI attribute errors (#[DefaultImpl], #[OverrideImpl], #[AsDecorator]). Runtime errors — database connection failures, logic errors in request handlers, missing env vars read at request time — are not caught by this preflight.

Package Workers

Installed Vortos packages can contribute long-running worker processes. Use the worker management commands to keep docker/worker/supervisord.conf aligned with the packages in the project:

php bin/console vortos:worker:list
php bin/console vortos:worker:install --dry-run
php bin/console vortos:worker:install

Examples:

php bin/console vortos:worker:install --worker=messaging-outbox-relay
php bin/console vortos:worker:install --worker=aws-ses-outbox-relay
php bin/console vortos:worker:install --worker=object-store-outbox-relay

See Workers for supervisor management, package-discovered workers, deployment reloads, and troubleshooting.

Production

For the FrankenPHP runtime docker-compose.prod.yaml is the durable baseline of the box — it brings up infrastructure plus the edge and the deploy's Docker gateway, and it deliberately does not run the application:

  • edge — a standalone Caddy that owns ports 80/443 and reverse-proxies to the active blue/green color. The deploy repoints its upstream at cutover through the admin API.
  • docker-socket-proxy — a least-privilege Docker API (a narrow endpoint allowlist) the on-box deploy talks to. The raw /var/run/docker.sock is mounted only here, read-only, and never into the application image.
  • infrastructurewrite_db / read_db / read_pg / redis / kafka, each carrying its POSTGRES_* / MONGO_INITDB_* environment: mappings (sourced from the agnostic VORTOS_* values in .env.prod) so the images initialise correctly.

The application itself runs as internal-only blue/green colors (app-blue / app-green, project vortos-app-<color>) that the deploy creates and cuts over — they publish no host ports and are not part of this file. Publishing 80/443 from an app service here would collide with the edge and defeat zero-downtime cutover, so the app is intentionally absent. The self-contained image (baked code + composer install --no-dev from the Dockerfile's app stage, pinned by <repo>@<digest>) is pulled and run by the deploy, never built on the box.

# Bring up the durable baseline once (infra + edge + socket-proxy):
docker network create vortos-net   # external, shared with the colors + edge (first-deploy provisioning also ensures it)
docker compose -f docker-compose.prod.yaml up -d

vortos-net is external to match the deploy tooling and the color projects. The PHP-FPM+Nginx runtime carries the same DB env mappings and the socket-proxy, but keeps its nginx+backend structure — the edge-router blue-green topology is FrankenPHP-specific (PHP-FPM serves FastCGI, not HTTP, so it cannot be a direct color).

PostgreSQL Init Scripts

Two SQL scripts are included:

001_create_database.sql — creates the application database.

002_vortos_tables.sql — creates the messaging infrastructure tables:

-- Outbox table — domain events queued for Kafka
CREATE TABLE IF NOT EXISTS outbox (...);

-- Dead letter table — messages that failed after all retries
CREATE TABLE IF NOT EXISTS dead_letters (...);

These tables are required by vortos-messaging. They are created automatically when the PostgreSQL container starts.

Re-publishing Is Safe

Running vortos:docker:publish again skips unchanged files and backs up changed files before replacing them. Use --dry-run to preview and --no-overwrite to protect local customisations.

Choosing a Runtime

FrankenPHPPHP-FPM + Nginx
PerformanceExcellent — worker mode eliminates PHP bootstrapGood — standard PHP-FPM
ComplexityLower — one container (PHP + web server)Higher — two containers (PHP + Nginx)
HTTPSBuilt-in automatic via CaddyManual cert management
Worker modeYes — containers stay aliveNo — PHP process per request
EcosystemNewer, growingMature, universal
Use whenNew projects, performance-criticalHosting constraints, existing Nginx config

On this page