Porting compose files to balena
Some docker-compose files that work in Docker will require slight modifications to work similarly in balena.
Major differences from Docker
BalenaOS is a minimal OS designed for running containers on edge devices. Our OS includes balenaEngine, which is based on Docker's Moby project and made specifically for IoT devices. As such, a few changes have been made so our container engine is more suitable for a lightweight implementation:
No bind mounts
Host bind mounts (./data:/app/data) do not work on balena. The host filesystem layout is managed by balenaOS and is not directly accessible to containers. Use named volumes instead.
Declare volumes in the top-level volumes: section and reference them in services:
version: "2.1"
volumes:
app-data:
config:
services:
my-service:
build: my-service
volumes:
- app-data:/app/data
- config:/etc/myappNamed volumes persist across container updates as long as the volume name stays the same, and can be shared across multiple services which allows them to access the same data. Note that volumes are purged if a device is moved to a different fleet.
Some features of the hostOS can be modified by editing the config.txt file. This includes udev rules, NetworkManager configuration, and much more. You can learn about all of the available options and how to edit this file in our documentation.
dockerfile.templates
Balena's build system supports Dockerfile templates which are simply dockerfiles files named Dockerfile.template . Variables using the syntax %%VARIABLE%% in these files will undergo variable substitution before Docker processes them.
The template system exists because Docker images are architecture-specific, and balena fleets often span multiple device types (e.g., Raspberry Pi 3, Pi 4, Intel NUC) with different CPU architectures.
Available Variables
%%BALENA_ARCH%%
Target CPU architecture
aarch64, amd64, armv7hf
%%BALENA_MACHINE_NAME%%
Yocto machine name for the device type
raspberrypi4-64, genericx86-64-ext
%%BALENA_APP_NAME%%
Fleet name
my-fleet
%%BALENA_RELEASE_HASH%%
Release hash
abc123...
%%BALENA_SERVICE_NAME%%
Service name from docker-compose.yml
pihole
Note that %BALENA_MACHINE_NAME%% resolves to the fleet's default device type, so in mixed fleets it produces the wrong value for non-default device types. %%BALENA_ARCH%% is preferred because all devices in a fleet share the same architecture.
balena.yml
A balena.yml file is an optional configuration file for providing additional settings, defaults, and configuration for your project. It's only required if you want to publish your project to balenaHub, though it's also useful to customize a deploy with balena button.
When present, name and type are the minimum useful fields:
Additional fields and options are described in our docs.
Balena Arch vs Docker/Go Arch
Balena has its own architecture naming convention, distinct from Docker and Go:
Balena (%%BALENA_ARCH%%)
Docker Platform
Go GOARCH
aarch64
linux/arm64
arm64
amd64
linux/amd64
amd64
armv7hf
linux/arm/v7
arm (GOARM=7)
rpi
linux/arm/v6
arm (GOARM=6)
i386
linux/386
386
This matters because each balenaCloud fleet has a default device type and all devices in the fleet share the same architecture.
No .env file variables
Balena does not read .env files. Variables can be set through one of the following instead:
docker-compose.yml file using the
environmentlabel. These values will be available in the releasebalenaCloud dashboard via the "Device variables" tab, either per device or for the whole fleet
the balenaCLI
balena-specific labels and unsupported fields
Our compose-file support is currently based on version 2.4, so any fields that were introduced in version 3 are not supported. Our docs provide a list of supported and unsupported fields.
In addition, there are a list of balena-specific labels you can use in your docker-compose file to enable specific features. The full list is in our docs.
labels are applied to a specific service with the labels: setting, for instance:
Standard workflow for conversion
When converting a Docker project to balena, use the details above and follow these steps:
Check
balena.yml→ what device types does this fleet target?Map device types to architectures (see table above)
For each service: does its base image support multi-platform? If yes → plain Dockerfile. If no → Dockerfile.template with the simple
%%BALENA_ARCH%%pattern.Replace bind mounts with named volumes.
Remove
.envfiles — migrate variables to balenaCloud dashboard or CLI.Add balena-specific labels only where strictly necessary — warn the user about security implications of each (see feature labels table).
Use selective
devices:/cap_add:for hardware access. Ifprivileged: trueorcap_add: [SYS_ADMIN]is unavoidable, add a# WARNING:comment in the docker-compose.yml explaining why — require explicit user acknowledgment.Test with
balena build --deviceType <type>for each target.
Using a coding agent
Coding agents such as Claude Code and Copilot can be useful in applying the tips on this page to optimize your compose file for the balena platform. (Always inspect and test any output from an AI coding agent before placing any code in production.)
Balena's documentation includes an MCP (Model Context Protocol) server located at https://docs.balena.io/~gitbook/mcp. AI tools can use this server to read our docs directly. This works with Claude, Claude Code, Cursor, Codex, VS Code, and other MCP clients.
Skill file
You can extend a coding agent's knowledge by providing it with a "skill" file. We've developed a skill file that includes all of the information in this guide (and more!) which you can find here:
Setting up your agent
Typically you place the skill file in a folder in your project's root directory. For example, for Claude in VS Code, the location would be: .claude/skills/balenify/SKILL.md
To use the skill, simply ask a question that matches the skill description:
Convert this Docker project to balenaCloud
Or simply invoke the skill using its name:
/balenify docker-compose.yml
Example conversion
For our first example we'll convert a classic LAMP (Linux, Apache, MySQL, PHP) stack typically used for web development. Here's the original Docker-compatible docker-compose file:
Based on the advice above, we can spot the following:
Bind mounts need to be eliminated
variables from .ENV files need to be hard-coded (for example
${MYSQL_DATABASE})container_nameandhealthchecklabels are unsupporteddepends_oncondition only supportsservice_startedA compose version should be specified and adhere to v. 2.1-2.4
Our "balenify" skill goes even deeper and references a set of "best practices" that include:
Remove MySQL external port so MySQL is internal-only; no need to expose it to the host
Pin images to a version rather than "latest" as with the phpmyadmin service
add a custom internal network for inter-service communication to limit blast radius
After conversion, our balena-optimized docker-compose file looks like this:
Note that some of the environment variable lines have been commented out. These values should be set using the variable feature of the balenaCloud dashboard.
You can find the full repository here. Pushing this code to your balena device and then browsing to its local IP address should yield the following page:

Last updated
Was this helpful?