Dynamic Compose Reference
Dynamic compose is Companion Hub’s JSON format for deploying an app: a simplified, custom version of the docker-compose.yml file that gives more control over how an app gets deployed. For example, you can choose to only use the reverse proxy or only use ports. Every app in the store ships a docker-compose.json in this format.
For a step-by-step guide on creating dynamic compose files, see Creating a dynamic compose file.
From docker-compose.json to a running stack
A dynamic compose file is not handed to Docker as-is. Hub validates it, folds in the override that matches the host’s CPU architecture, renders a compose project with Traefik labels on the isMain service, merges the values from the install form and any overrides you keep in user-config, and only then starts the containers:
flowchart TD
A["App folder<br/>config.json: dynamic_config true<br/>docker-compose.json, schemaVersion 2"] -->|"validate (v1 auto-migrated to v2)"| B["Schema check"]
B -->|"detect amd64 or arm64"| C["Apply matching overrides<br/>deep-merged; arrays replace"]
C -->|"render, one project per app"| D["Compose project<br/>Traefik labels on isMain,<br/>internalPort routed"]
V["app.env from the install form<br/>+ user-config overrides"] --> D
D -->|"start"| R["Running containers<br/>status over Server-Sent Events"]Try it out
You can check the shape of your dynamic compose configuration using the validator below:
The validator checks your file against the general dynamic compose schema. It
does not apply the rules Hub enforces when it installs an app, which are
listed under What Hub refuses to install, and
it does not recognize volumeName volumes. A file can pass here and still be
refused at install.
Docker Compose to Dynamic Config Converter
Converting an existing docker-compose.yml file to Companion Hub’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 review the converted configuration to ensure it meets your needs. Some advanced features may not be fully supported.
Configuration Options Reference
Schema Configuration
Top-level schema options:
| Option | Type | Description | Example |
|---|---|---|---|
| schemaVersion | number | Schema version (must be 2) | "schemaVersion": 2 |
| services | array | Array of service configurations | See service options below |
| overrides | array | Architecture-specific overrides | See Architecture Overrides section |
Basic Configuration
These are the most commonly used options when configuring a service:
| Option | Type | Description | Example |
|---|---|---|---|
| name | string (required) | The name of the service and container | "name": "nginx" |
| image | string (required) | The Docker image to use | "image": "nginx:latest" |
| command | string | string[] | The command to run in the container | "command": "/my/app" or "command": ["npm", "start"] |
| environment | array | Environment variables | See example below |
Example environment configuration:
"environment": [
{"key": "FOO", "value": "bar"},
{"key": "PORT", "value": 8080},
{"key": "ENABLE_FEATURE", "value": true}
]Port Configuration
Options related to exposing ports:
| Option | Type | Description | Example |
|---|---|---|---|
| internalPort | number | The main port exposed by the container | "internalPort": 80 |
| addPorts | array | Additional ports to expose | See example below |
| restart | string | Restart policy: no, always, unless-stopped, or on-failure | "restart": "unless-stopped" |
| platform | string | Pin the service to a Docker platform | "platform": "linux/amd64" |
Example addPorts configuration:
"addPorts": [{
"containerPort": 8080,
"hostPort": 8080,
"tcp": true,
"udp": false,
"interface": "127.0.0.1"
}]Volume Configuration
Options for mounting volumes:
| Option | Type | Description | Example |
|---|---|---|---|
| volumes | array | List of volume mappings | See example below |
Example volumes configuration:
"volumes": [{
"hostPath": "/host/path",
"containerPath": "/container/path",
"readOnly": false,
"shared": false,
"private": false,
"bind": {
"propagation": "rprivate"
}
}]Each volume names exactly one source, either hostPath or volumeName:
hostPathis a bind mount from the host. Use${APP_DATA_DIR}to stay inside the app’s own data folder.volumeNameis a Docker-managed named volume. Compose scopes the name to the app, sodbcannot collide with another app’sdb. A name must start with a letter or number.
Two optional flags change how a bind mount is created:
requiresPosixPermissionsmarks a bind mount that needs real file ownership, such as a database data directory. Where the host filesystem cannot carry ownership, such as a Windows-backed path, Hub mounts a named volume instead.privatemarks a bind mount that only the app’s root-running service writes and that holds material other local accounts must not read. Hub creates it with owner-only permissions.
Volume bind propagation options: rprivate, private, rshared, shared, rslave, slave
Network Configuration
Options related to networking:
| Option | Type | Description | Example |
|---|---|---|---|
| networkMode | string | Custom network mode | "networkMode": "host" |
| addToMainNetwork | boolean | Add container to main network | "addToMainNetwork": true |
| extraHosts | string[] | Additional /etc/hosts entries | "extraHosts": ["host1:ip1"] |
| hostname | string | Container hostname | "hostname": "mycontainer" |
| dns | string | string[] | Custom DNS servers | "dns": "8.8.8.8" or "dns": ["8.8.8.8"] |
Health Check Configuration
Options for container health monitoring:
| Option | Type | Description | Example |
|---|---|---|---|
| healthCheck | object | Container health check configuration | See example below |
Example healthCheck configuration:
"healthCheck": {
"test": "curl --fail http://localhost",
"retries": 3,
"interval": "30s",
"timeout": "10s",
"startInterval": "5s",
"startPeriod": "30s"
}Resource Configuration
Options for container resource limits and deployment:
| Option | Type | Description | Example |
|---|---|---|---|
| deploy | object | Resource limits and reservations | See example below |
| ulimits | object | Resource limits | See example below |
| shmSize | string | Size of /dev/shm | "shmSize": "2gb" |
Example deploy configuration:
"deploy": {
"resources": {
"limits": {
"cpus": "0.5",
"memory": "512M",
"pids": 100
},
"reservations": {
"cpus": "0.25",
"memory": "256M",
"devices": [
{
"capabilities": ["gpu"],
"driver": "nvidia",
"count": 1,
"deviceIds": ["0"]
}
]
}
}
}Example ulimits configuration:
"ulimits": {
"nproc": {"soft": 10, "hard": 20},
"nofile": {"soft": 20, "hard": 30},
"core": 1024,
"memlock": {"soft": 512, "hard": 1024}
}Note: ulimits can be specified as a number for simple limits or as an object with soft and hard values. Supported ulimit types: nproc, nofile, core, memlock.
Security Configuration
Options related to container security:
| Option | Type | Description | Example |
|---|---|---|---|
| privileged | boolean | Run container with elevated privileges | "privileged": true |
| capAdd | string[] | Add container capabilities | "capAdd": ["NET_ADMIN"] |
| capDrop | string[] | Drop container capabilities | "capDrop": ["NET_ADMIN"] |
| securityOpt | string[] | Security options | "securityOpt": ["no-new-privileges"] |
| readOnly | boolean | Mount root filesystem as read only | "readOnly": true |
| user | string | Username or UID | "user": "1000:1000" |
Advanced Configuration
Additional options for fine-tuning:
| Option | Type | Description | Example |
|---|---|---|---|
| entrypoint | string | string[] | Container entrypoint | "entrypoint": "./entrypoint.sh" |
| workingDir | string | Working directory inside container | "workingDir": "/app" |
| tty | boolean | Allocate a pseudo-TTY | "tty": true |
| stdinOpen | boolean | Keep STDIN open | "stdinOpen": true |
| stopSignal | string | Signal to stop the container | "stopSignal": "SIGTERM" |
| stopGracePeriod | string | Time to wait to stop container | "stopGracePeriod": "10s" |
| pid | string | PID namespace | "pid": "host" |
| sysctls | object | Sysctl settings | "sysctls": {"net.core.somaxconn": 1024} |
| logging | object | Logging configuration | See example below |
| devices | string[] | Device mappings | "devices": ["/dev/ttyUSB0:/dev/ttyUSB0"] |
| extraLabels | object | Additional container labels | "extraLabels": {"com.example.key": "value"} |
Example logging configuration:
"logging": {
"driver": "json-file",
"options": {
"max-size": "10m"
}
}Note: sysctls values must be numbers, and extraLabels values can be strings or booleans.
Dependencies
Options for managing service dependencies:
| Option | Type | Description | Example |
|---|---|---|---|
| dependsOn | object | string[] | Service dependencies | See example below |
| isMain | boolean | Indicates where traefik labels should be applied | "isMain": true |
Example dependsOn configurations:
Simple array format (waits for services to start):
"dependsOn": ["db", "cache"]Object format with conditions:
"dependsOn": {
"db": {
"condition": "service_healthy"
},
"cache": {
"condition": "service_started"
}
}Available conditions: service_healthy, service_started, service_completed_successfully
Architecture Overrides
Options for architecture-specific configurations:
| Option | Type | Description | Example |
|---|---|---|---|
| overrides | array | Architecture-specific service configurations | See example below |
The overrides option allows you to specify different configurations for specific architectures at the top level of your dynamic compose configuration. This is useful when you need to use different Docker images or settings for different CPU architectures (like arm64 vs amd64).
Example overrides configuration:
"overrides": [
{
"architecture": "arm64",
"services": [
{
"name": "app",
"image": "myapp:arm64-latest",
"environment": [
{"key": "ARCHITECTURE", "value": "arm64"}
]
}
]
}
]How Architecture Overrides Work
When Companion Hub starts a container, it automatically detects the current system architecture and applies any matching overrides from the overrides array. The system looks for overrides that match the current architecture, then merges those service-specific settings with the base service configuration.
Important aspects of the override system:
- You must include the
namefield in each service in the overrides to match with the base service - Only specify the properties that need to be different for each architecture
- Properties from overrides are deep-merged with the base service configurations
- Array properties (like volumes or ports) in overrides completely replace the base arrays rather than being appended
Supported Architectures
Companion Hub supports the following architecture types for overrides:
amd64: Standard 64-bit x86 architecture (also known as x86_64)arm64: 64-bit ARM architecture (also known as aarch64)
Complete Example
Here’s a complete example showing how to use architecture overrides:
{
"schemaVersion": 2,
"services": [
{
"name": "media-server",
"image": "mediaserver:latest",
"isMain": true,
"internalPort": 8096,
"volumes": [
{
"hostPath": "${APP_DATA_DIR}/config",
"containerPath": "/config"
},
{
"hostPath": "${APP_DATA_DIR}/media",
"containerPath": "/media"
}
],
"environment": [{ "key": "TZ", "value": "UTC" }]
}
],
"overrides": [
{
"architecture": "arm64",
"services": [
{
"name": "media-server",
"image": "mediaserver:arm64-latest",
"environment": [{ "key": "ARM_SPECIFIC_VAR", "value": "enabled" }]
}
]
},
{
"architecture": "amd64",
"services": [
{
"name": "media-server",
"image": "mediaserver:amd64-latest",
"deploy": {
"resources": {
"reservations": {
"devices": [
{
"capabilities": ["gpu"]
}
]
}
}
}
}
]
}
]
}In this example:
- The base configuration defines common settings for the media-server service
- On
arm64systems, the service will use the ARM64-specific image and set an ARM-specific environment variable - On
amd64systems, the service will use the AMD64-specific image and enable GPU acceleration - The name “media-server” is included in each override to match with the base service
What Hub refuses to install
Hub checks every service against an app sandbox when it renders the compose file, including the values in overrides. An install that breaks these rules stops with an error that names the field. A few audited first-party and catalog apps are exempt from the rules they need. Apps from any other source, including an app you create yourself, are not.
- Privileged mode and host namespaces.
privileged: true,networkMode: host,networkMode: container:<name>, andpid: hostare refused. - Capabilities.
capAddmay only add Docker’s default capability set, such asCHOWN,NET_BIND_SERVICE, andSETUID. Anything else, such asNET_ADMINorSYS_ADMIN, is refused. - Confinement. A
securityOptthat switches confinement off, such asseccomp=unconfined,apparmor=unconfined, or anylabel=value, is refused. - Network mode.
networkModemay bebridge,none,default,host,service:<name>, orcontainer:<name>. Any other value would join a Docker network by name, and Hub never lets an app join one of its own networks. - Devices. Passing through a character device, such as a Zigbee dongle or
/dev/dri, is fine. The/devfolder itself, block devices, and/dev/memare refused. - Host paths. A
hostPathmay not be/or sit under, or contain,/etc,/var/run,/run,/var/lib/docker,/var/lib/containerd,/proc,/sys,/dev,/root,/boot,/usr,/bin,/sbin,/lib, or/lib64. The exceptions are/etc/localtimeand/etc/timezone. A path with..or.segments, a leading~, or a$expansion other than${APP_DATA_DIR}is refused, because Hub cannot tell where it points.
Use ${APP_DATA_DIR} or a volumeName for storage, and ask for the smallest set of capabilities the app needs.
Note: Some of these options are advanced features that should be used with caution as they can affect container security and stability. Always refer to Docker documentation when using advanced options like
privileged,capAdd, orsecurityOpt.
Tip: For most applications, you’ll only need the Basic Configuration and Port Configuration options. The other options are available for more complex use cases.