Skip to Content
DocumentationGuidesCreating a dynamic compose file

Creating a dynamic compose file

In Runtipi version v3.2.0 we added dynamic compose, a simplified and custom version of the docker-compose.yml file that allows for more control on how apps get deployed. For example, you can choose to only use the reverse proxy or only use ports. It also allows extra features like multiple appstores.

This guide covers Schema Version 2, the current and recommended version. If you’re upgrading from an older version, see the migration section below.

Here is an example docker-compose.json file for a simple app like Nginx:

{ "schemaVersion": 2, "services": [ { "name": "nginx", "image": "nginx", "internalPort": 80, "isMain": true } ] }

And that’s it, with just a few lines of config you can deploy an nginx app.

Migrating from Schema v1 to v2

Schema version 2 introduces important structural changes to improve consistency and type safety. Here are the key differences:

Required schemaVersion field

All dynamic compose files must now include the schemaVersion field set to 2:

{ "schemaVersion": 2, "services": [...] }

Environment variables structure change

v1 format (object):

{ "environment": { "FOO": "bar", "PASSWORD": "${MYAPP_PASSWORD}" } }

v2 format (array of key-value pairs):

{ "environment": [ { "key": "FOO", "value": "bar" }, { "key": "PASSWORD", "value": "${MYAPP_PASSWORD}" } ] }

Automatic migration

Runtipi automatically migrates v1 schemas to v2 format when loading apps. However, it’s recommended to update your docker-compose.json files manually to take advantage of the improved validation and error messages.

Creating your first dynamic compose file

Let’s see how easy it is to create the docker-compose.json file.

Creating the file and enabling dynamic compose

First create the docker-compose.json next to the docker-compose.yml file in your app folder. Then edit the app’s config.json and add this configuration line at the end:

"dynamic_config": true

Defining the services

Now add the schemaVersion field and your services array which defines all your services:

{ "schemaVersion": 2, "services": [] }

The schemaVersion field indicates which version of the dynamic compose schema you’re using (always use 2). The services key is the same as in the docker-compose.yml. Inside this array you can add multiple apps.

Adding a service

Let’s add a service called myapp:

{ "schemaVersion": 2, "services": [ { "name": "myapp" } ] }

The name key defines both the service name and container name.

Adding an image

Add your Docker image. For this example we’ll use myapp:latest:

{ "schemaVersion": 2, "services": [ { "name": "app", "image": "myapp:latest" } ] }

Exposing the UI

If your service exposes a web UI, add the port and main service configuration:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true } ] }

The internalPort specifies your app’s port, and isMain indicates where the traefik labels should go. Traefik labels should always be on the part of the app that exposes the UI.

Adding volumes

To store app data, you can bind mount directories. For example, to mount ${APP_DATA_DIR}/data/myapp to /data with read-write permissions:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data/myapp", "containerPath": "/data", "readOnly": false } ] } ] }

The volumes key is an array where you can define multiple volume mounts.

Adding environment variables

Environment variables in schema v2 use an array format with key and value pairs:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data/myapp", "containerPath": "/data", "readOnly": false } ], "environment": [ { "key": "FOO", "value": "bar" }, { "key": "PASSWORD", "value": "${MYAPP_PASSWORD}" } ] } ] }

You can add as many environment variables as needed and use values from form fields as you normally would. Note that value can be a string, number, or boolean.

Adding multiple ports

Many apps require multiple ports for features like APIs or setup UIs. Add additional ports like this:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data/myapp", "containerPath": "/data", "readOnly": false } ], "environment": [ { "key": "FOO", "value": "bar" }, { "key": "PASSWORD", "value": "${MYAPP_PASSWORD}" } ], "addPorts": [ { "containerPort": 8080, "hostPort": 8080, "tcp": true }, { "containerPort": 25565, "hostPort": 25565, "udp": true } ] } ] }

Create an addPorts array and define your container and host ports as numbers (not strings). Specify if the port is UDP or TCP - if not defined, TCP is assumed. For ports that need both TCP and UDP:

"addPorts": [ { "containerPort": 8080, "hostPort": 8080, "tcp": true }, { "containerPort": 8080, "hostPort": 8080, "udp": true } ]

Adding a healthcheck

Add container health monitoring with a healthcheck:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data/myapp", "containerPath": "/data", "readOnly": false } ], "environment": [ { "key": "FOO", "value": "bar" }, { "key": "PASSWORD", "value": "${MYAPP_PASSWORD}" } ], "addPorts": [ { "containerPort": 8080, "hostPort": 8080, "tcp": true }, { "containerPort": 25565, "hostPort": 25565, "udp": true } ], "healthCheck": { "test": "curl --fail http://localhost || exit 1", "retries": 3, "interval": "30s", "timeout": "10s" } } ] }

Adding dependencies

If your service depends on other services (like a database), add the dependsOn key:

{ "schemaVersion": 2, "services": [ { "name": "myapp", "image": "myapp:latest", "internalPort": 80, "isMain": true, "volumes": [ { "hostPath": "${APP_DATA_DIR}/data/myapp", "containerPath": "/data", "readOnly": false } ], "environment": [ { "key": "FOO", "value": "bar" }, { "key": "PASSWORD", "value": "${MYAPP_PASSWORD}" } ], "addPorts": [ { "containerPort": 8080, "hostPort": 8080, "tcp": true }, { "containerPort": 25565, "hostPort": 25565, "udp": true } ], "healthCheck": { "test": "curl --fail http://localhost || exit 1", "retries": 3, "interval": "30s", "timeout": "10s" }, "dependsOn": { "service1": { "condition": "service_healthy" } } } ] }

For simple dependencies without conditions, you can use the shorter array format:

"dependsOn": ["service1", "service2"]

Other configuration options

For additional configuration options like custom commands, network modes, resource limits, and more, see the Dynamic Compose Reference documentation.

Try it out

You can validate your dynamic compose configuration using the validator below:

For a complete reference of all available configuration options, see the Dynamic Compose Reference documentation.

Docker Compose to Dynamic Config Converter

Converting an existing docker-compose.yml file to Runtipi’s dynamic compose format can be time-consuming and error-prone. We’ve created a tool to help you automatically convert your Docker Compose files to the dynamic compose format.

Docker Compose YAML

Dynamic Compose Output

⚠️

The converter works for most cases, but please review the converted configuration to ensure it meets your needs. Some advanced features may not be fully supported.

Last updated on