Skip to content

Relationships ​

A relationship wires an application to a backing service — a database or cache. Declaring a relationship does two things: it injects the service's connection details as environment variables into the application, and it makes the application wait for the service to be healthy before starting.

<relationships> ​

<relationships> contains one or more <relationship> elements.

xml
<relationships>
    <relationship name="database" target="db"/>
    <relationship name="cache" target="redis"/>
</relationships>

<relationship> ​

AttributeRequiredDefaultDescription
nameYes—Logical name of the relationship. Used to build the env var prefix.
targetYes—Name of the service to connect to. Must match a <service name>.

Target must exist

target must match the name of a service declared under <services>. A relationship that points at a non-existent service is invalid.

Environment variable injection ​

For each relationship, Tragwerk injects connection variables into the application container (and its workers and cron sidecar). The variable prefix is derived from the relationship name:

TRAGWERK_<NAME>_

where <NAME> is the relationship name upper-cased with spaces and hyphens replaced by underscores. So name="database" produces the prefix TRAGWERK_DATABASE_, and name="read-replica" produces TRAGWERK_READ_REPLICA_.

Keys for SQL services (postgresql, mysql, mariadb) ​

VariableValue (defaults)
TRAGWERK_<NAME>_HOSTservice hostname (slug)
TRAGWERK_<NAME>_PORT5432 (pg) / 3306 (mysql, mariadb)
TRAGWERK_<NAME>_DATABASEapp
TRAGWERK_<NAME>_USERapp
TRAGWERK_<NAME>_PASSWORDsecret

Keys for cache services (redis, valkey) ​

VariableValue (defaults)
TRAGWERK_<NAME>_HOSTservice hostname (slug)
TRAGWERK_<NAME>_PORT6379

TIP

There is no single *_URL variable — connection details are exposed as discrete HOST / PORT / DATABASE / USER / PASSWORD parts. Build any DSN your framework needs from these.

Worked example ​

xml
<application name="app" type="php:8.5" root="/">
    <web>
        <location path="/" root="public" index="index.php" passthru="/index.php"/>
    </web>
    <relationships>
        <relationship name="database" target="db"/>
        <relationship name="cache" target="redis"/>
    </relationships>
</application>
<!-- ... -->
<services>
    <service name="db" type="postgresql:18"/>
    <service name="redis" type="redis:8"/>
</services>

This injects the following environment variables into the app container:

bash
TRAGWERK_DATABASE_HOST=db
TRAGWERK_DATABASE_PORT=5432
TRAGWERK_DATABASE_DATABASE=app
TRAGWERK_DATABASE_USER=app
TRAGWERK_DATABASE_PASSWORD=secret

TRAGWERK_CACHE_HOST=redis
TRAGWERK_CACHE_PORT=6379

Values are always strings

These arrive through the container environment, so every one of them is a string — TRAGWERK_DATABASE_PORT included. Cast it before handing it to a driver that expects an integer.

Command-line clients ​

Every application image ships a CLI client for each service type Tragwerk supports, plus jq, so hooks, crons and maintenance scripts can talk to a service without installing anything first:

CommandPackageCovers
mariadb, mariadb-dump, mysql, mysqldumpmariadb-clientmariadb:*, mysql:*
psql, pg_dumppostgresql-clientpostgresql:*
redis-cliredis-toolsredis:*, valkey:*
jqjq—

postgresql-client is Debian's default, currently 17. psql talks to a PostgreSQL 18 server fine, but pg_dump refuses a server newer than itself — install the matching client from the PGDG repository in a build hook if you need to dump an 18 server from inside the application container.

Health-gated startup ​

Each relationship also adds a depends_on entry on the target service with condition service_healthy. The application container does not start until the database or cache reports healthy (via the service's healthcheck), so your app never boots against a service that is not ready yet.

Tragwerk — self-hosted PHP application hosting.