Skip to content

Services

Services are the stateful backing stores your applications use — relational databases and key-value caches. They are declared in the optional <services> element and connected to applications via relationships.

<services>

<services> contains one or more <service> elements.

xml
<services>
    <service name="db" type="postgresql:18" disk="2048"/>
    <service name="redis" type="redis:8"/>
</services>

<service>

AttributeRequiredDefaultDescription
nameYesUnique service name. Referenced by a relationship's target.
typeYesService engine and version. See the matrix below.
diskNo512Disk allocation in MB (positive integer). Applies to stateful services.
local-portNoLoopback host port (102465535). When set, the service is published on 127.0.0.1 for SSH-tunnelled local client access. On main/master the configured port is used verbatim; every other branch gets a Docker-assigned free loopback port (find it via docker ps).

Name pattern

name must match [a-zA-Z][a-zA-Z0-9 _-]* and be unique within the project. The name is slugified to become the service's hostname on the Docker network.

Type matrix

Enginetype values
PostgreSQLpostgresql:14, postgresql:15, postgresql:16, postgresql:17, postgresql:18
MySQLmysql:8
MariaDBmariadb:10.6, mariadb:10.11, mariadb:11.4, mariadb:11.8
Redisredis:6, redis:7, redis:8
Valkeyvalkey:8, valkey:9

How applications connect

Applications never reference services directly. Instead, you declare a relationship whose target matches the service name. Tragwerk then injects TRAGWERK_<NAME>_* connection variables and adds a health-gated depends_on:

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"/>
    </relationships>
</application>
<!-- ... -->
<services>
    <service name="db" type="postgresql:18" disk="2048"/>
</services>

Resulting Docker Compose effect

Each service becomes a container using the resolved official image, with:

  • a persistent named volume {slug}-data mounted at the engine's data directory (so data survives restarts);
  • default credentials passed as the engine's standard environment variables (e.g. POSTGRES_DB/POSTGRES_USER/POSTGRES_PASSWORD);
  • a healthcheck (e.g. pg_isready, mysqladmin ping, or redis-cli ping) that relationships' service_healthy conditions wait on.

Services are not exposed publicly

Service ports are never published to the host or the internet. The generated container has no port mapping — a service is reachable only over the internal Docker network, by the applications that declare a relationship to it (using its slugified name as the hostname). Only application containers are fronted by Traefik. To inspect a database during development, tunnel in over SSH rather than expecting a public port.

The one opt-in exception is the local-port attribute: it publishes the service on the host loopback only (127.0.0.1, never 0.0.0.0), so it stays private and reachable solely through an SSH tunnel. See connecting from your machine.

Choose the right PHP extension

Remember to add the matching PDO extension to your app — pdo_pgsql for PostgreSQL, pdo_mysql for MySQL/MariaDB. See Extensions.

Tragwerk — self-hosted PHP application hosting.