Databases¶
Every database service implements DatabaseServiceInterface, so it can be
handed to an application with ->withDatabaseService(). The application then
gets a DATABASE_URL environment variable and waits for the database to be
healthy before starting:
$postgres = new PostgresService();
$event->addService($postgres);
$event->addService(
(new SymfonyService('app'))->withDirectory(__DIR__)->withDatabaseService($postgres)
);
Each of them also exposes a {name}:expose task to reach the server from the
host with a native client — see tasks.
PostgresService¶
(new PostgresService())
->withVersion('16') // PostgreSQL version (default: 16)
- Task:
castor postgres:client— a psql session - Containers:
postgres, named volumepostgres_data - Database URL:
postgresql://app:app@postgres:5432/app?serverVersion=16&charset=utf8
MySQLService¶
(new MySQLService())
->withVersion('8') // MySQL version (default: 8)
->withRootPassword('root') // Root password (default: root)
->withDatabase('app') // Database name (default: app)
- Configuration:
withSetting()and friends - Task:
castor mysql:client— a mysql session - Containers:
mysql, named volumemysql-data - Database URL:
mysql://root:root@mysql:3306/app
MariaDBService¶
(new MariaDBService())
->withVersion('12.1') // MariaDB version (default: 12.1)
->withRootPassword('root') // Root password (default: root)
->withDatabase('app') // Database name (default: app)
- Configuration:
withSetting()and friends - Task:
castor mariadb:client— a mariadb session - Containers:
mariadb, named volumemariadb-data - Database URL:
mysql://root:root@mariadb:3306/app?serverVersion=mariadb-12.1&charset=utf8mb4
ClickhouseService¶
(new ClickhouseService())
->withVersion('25.8') // ClickHouse version (default: 25.8)
->withDatabase('app') // Database name (default: app)
->withCredentials('app', 'app') // User and password (default: app / app)
->withBackup() // Install Altinity clickhouse-backup in the image
- Task:
castor clickhouse:client— a clickhouse-client session - Containers:
clickhouseandclickhouse-keeper, named volumeclickhouse-data - UI:
https://clickhouse.{root_domain}when the router is enabled
Note
ClickHouse is not a DatabaseServiceInterface: it is meant to sit next to your
main database rather than to back DATABASE_URL.
Configuring the MySQL and MariaDB servers¶
Both read every *.cnf of /etc/mysql/conf.d on top of their built-in
defaults, and the plugin ships one file there:
(new MySQLService())
->withSetting('max_connections', 500)
->withSetting('innodb_buffer_pool_size', '1G')
->withSetting('slow_query_log', true) // ON / OFF for a boolean
->withSetting('skip-name-resolve') // a flag that takes no value
->withSettings(['sort_buffer_size' => '4M']) // several at once
which generates:
# This file is generated by Castor. Do not edit it manually.
[mysqld]
max_connections = 500
innodb_buffer_pool_size = 1G
slow_query_log = ON
skip-name-resolve
sort_buffer_size = 4M
For what withSetting() cannot express — another section, a comment you want to
keep — append a raw block, or a file of your project:
->withConfiguration("[client]\ndefault-character-set = utf8mb4")
->withConfigurationFile(__DIR__ . '/docker/my.cnf')
All three merge into the same file, settings first, in the order you declared them.
The result is shipped as a compose config, not as a bind mount: the content
ends up in compose.generated.yaml, so there is no host directory for Docker to
create as root, and no file whose permissions the server might refuse — MySQL
ignores a world-writable .cnf.
The server reads it once, at boot, and compose does not recreate a container
when only the content of a config changed. The plugin stamps a digest of it in a
castor.config.{name}-config label, so castor docker:up picks a change up on
its own — no --force-recreate.
withConfigurationFile() reads the file when the compose file is generated, and
raises if the path does not exist. A mistyped path is reported there and then,
rather than leaving the server silently unconfigured. It also means editing that
file takes effect on the next Castor run, followed by a restart of the
container.
Several instances of the same database¶
withName() overrides the name a service gives itself, which is what lets you
register it twice:
$main = new PostgresService();
$analytics = (new PostgresService())->withName('analytics');
$event->addService($main);
$event->addService($analytics);
$event->addService((new SymfonyService('app'))->withDatabaseService($analytics));
The container, the named volume (analytics_data), the connection string
(postgresql://app:app@analytics:5432/app) and the expose task
(castor analytics:expose) all follow the name.
Every task of a service is named {service}:{task}, so the two instances get two
full task sets rather than fighting over one: castor postgres:client and
castor analytics:client, castor postgres:expose and
castor analytics:expose.