Documentation
Reference
Dynamic compose

Dynamic Compose

In Runtipi version v3.2.0 we added a new feature called dynamic compose, this is a simplified and custom version of the docker-compose.yml file that allows for more control on how apps gets deployed, for example only using the reverse proxy or only using ports, it also allows extra features like multiple appstores. This feature is still quite new and we are trying to migrate the entire appstore to the new format deprecating the docker-compose.yml file.

Here is an example docker-compose.json file for the app Nginx:

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

And that's it, with just 10 lines of json you can deploy an nginx app.

Creating the dynamic compose file

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

Creating the file and enabling dynamic compose

The first thing we need to do is create the docker-compose.json next to the docker-compose.yml file in the app folder. We also need to tell Runtipi to use the file so we need to edit the app's config.json and add the following configuration line at the end:

"dynamic_config": true

Defining the services

Now we need to add our services array which defines all our services.

{
  "services": []
}

The services key is the same as in the docker-compose.yml, inside this array we can add multiple apps.

Adding a service

Let's add our myapp service.

{
  "services": [
    {
      "name": "myapp"
    }
  ]
}

As you can see I created an object and added the name key, that defines the service name and container name.

Adding an image

Let's add an image now, for this example our image is myapp:latest.

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

Exposing the UI

In this example this service also exposes a web UI, so let's add that too.

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

So we defined our app port using the internalPort key, the isMain key indicates where the traefik labels should go, traefik labels should always be on the part of the app that exposes the UI.

Adding volumes

In this example we also need to store some app data, we want to bind the ${APP_DATA_DIR}/data/myapp host folder to the /data container folder with read-write permissions, this can be done like this:

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

As you can see the volumes key is an array where you can define multiple volumes in the form of the object we showcased above.

Adding environment variables

Another important thing in a docker compose file are the environment variables, environment variables can be added very easily using this simple format:

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

You can add as many environment variables as you like and you can also use the values from form fields as you normally would.

Adding multiple ports

A lot of apps require more that one port, either an API or a setup UI, you can add multiple ports like this:

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

Again we create an addPorts array and define our container and host port, you can specify if the port is UDP or TCP, if you don't define anything TCP will be assumed. If you need to add a ports that does both TCP and UDP you can do it like so:

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

Adding a healthcheck

If you like you can add a healthcheck to your app, this is exactly the same as in the docker-compose.yml file but in JSON format. Here is an example:

{
  "services": [
    {
      "name": "myapp",
      "image": "myapp:latest",
      "internalPort": 80,
      "isMain": true,
      "volumes": [
        {
          "hostPath": "${APP_DATA_DIR}/data/myapp",
          "containerPath": "data",
          "readOnly": false
        }
      ],
      "environment": {
        "FOO": "bar",
        "PASSWORD": "${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"
      }
    }
  ]
}

Depeneds on

A lot of services depend on other services (like a database) to be ready when they start. You can add the dependsOn key like this:

{
  "services": [
    {
      "name": "myapp",
      "image": "myapp:latest",
      "internalPort": 80,
      "isMain": true,
      "volumes": [
        {
          "hostPath": "${APP_DATA_DIR}/data/myapp",
          "containerPath": "data",
          "readOnly": false
        }
      ],
      "environment": {
        "FOO": "bar",
        "PASSWORD": "${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"
        }
      }
    }
  ]
}

If you don't need to specify the condition you can add a simpler format like this:

"dependsOn": "service1"

Miscellaneous

Apart from the most common docker compose items, you can also add some other not so often ones.

For example a custom command:

"command": "/my/app"

A custom network mode:

"networkMode": "host"

Ulimits:

"ulimits": {
  "nproc": {
    "soft": 10,
    "hard": 20
  },
  "nofile": {
    "soft": 20,
    "hard": 30
  }
}

Extra hosts:

"extraHosts": [
  "somehost:someotherhost"
]