Introduction
Bosch’s ctrlX OS is an industrial edge platform that uses snaps as the main app packaging format. Running Docker containers on ctrlX devices usually requires converting each container into a snap package, which can be tedious and slows down container deployment.
This article shows how to build and deploy a Portainer Edge Agent snap for ctrlX OS. Using the Edge Agent, you can remotely manage Docker containers on ctrlX EDGE devices directly from a Portainer Server, without repackaging each container into snap package. This setup allows you to remotely deploy containers, manage lifecycles, and volumes, streamline debugging, and orchestrate your entire device fleet, all while retaining the security of Snap confinement and the flexibility of Docker.
In particular in this setup we used the Edge Controller 400 ctrlx OS wago hardware and successfully managed to deploy multiple docker containers on it through our Edge Agent without the need for the snap packaging procedure.

Project File Structure
The project is organized as follows:
├── build_all.sh
├── build_content.sh
├── build_snap.sh
├── docker-compose/
│ ├── docker-compose.yml
├── configs/
│ ├── package-assets
| ├── portainer.package-manifest.json
└── snap
├──snapcraft.yaml
Explanation of Each File and Folder
- build_all.sh
The master build script. It detects the target architecture or accepts it as a parameter, then runs build_content.sh
and build_snap.sh
sequentially. This script automates the entire build process.
#!/bin/bash
TARGET_ARCH=$(dpkg --print-architecture)
if [[ -n $1 ]]; then
TARGET_ARCH=$1
fi
echo TARGET_ARCH: ${TARGET_ARCH}
echo --- build content
bash build_content.sh ${TARGET_ARCH}
echo --- build snap
bash build_snap.sh ${TARGET_ARCH}
- build_content.sh
Prepares all dynamic content and configurations before building the snap. It generates environment variable files and any other required config files that tailor the Portainer Edge Agent for your specific ctrlX device.
#!/bin/bash
TARGET_ARCH=$(dpkg --print-architecture)
if [[ -n $1 ]]; then
TARGET_ARCH=$1
fi
echo TARGET_ARCH: ${TARGET_ARCH}
IMAGE_NAME="portainer/agent"
IMAGE_TAG="2.30.1"
DOCKER_CLI="/snap/bin/docker"
echo --- create ./docker-compose/docker-compose.env
rm -v -f ./docker-compose/docker-compose.env
echo IMAGE_NAME=${IMAGE_NAME} >> ./docker-compose/docker-compose.env
echo IMAGE_TAG=${IMAGE_TAG} >> ./docker-compose/docker-compose.env
echo EDGE_KEY= <insert here your server EDGE_KEY> >> ./docker-compose/docker-compose.env
echo EDGE_ID=<insert edge id of your choice> >> ./docker-compose/docker-compose.env
echo --- create docker image with platform ${TARGET_ARCH}
rm -f -v ./docker-compose/*.tar
${DOCKER_CLI} pull ${IMAGE_NAME}:${IMAGE_TAG} --platform ${TARGET_ARCH}
${DOCKER_CLI} save ${IMAGE_NAME}:${IMAGE_TAG} | gzip > ./docker-compose/image.tar.gz
${DOCKER_CLI} rmi ${IMAGE_NAME}:${IMAGE_TAG}
- build_snap.sh
Executes Snapcraft commands to build the snap package. It handles cleaning previous build artifacts and runs Snapcraft in --destructive-mode
for a flexible and reproducible build.
#!/bin/bash
TARGET_ARCH=$(dpkg --print-architecture)
if [[ -n $1 ]]; then
TARGET_ARCH=$1
fi
echo TARGET_ARCH: ${TARGET_ARCH}
echo --- clean snap
snapcraft clean --destructive-mode
echo --- build snap with architecture ${TARGET_ARCH}
snapcraft --destructive-mode --enable-experimental-target-arch --target-arch=${TARGET_ARCH}
- configs/
Contains static configuration files used by the snap at runtime. Keeping these separate helps maintain clarity between build logic, configuration, and deployment artifacts.
Here there is a subdirectory package-assets containing a file portainer.package-manifest.json that has the following structure (optional)
{
"$schema": "https://json-schema.boschrexroth.com/ctrlx-automation/ctrlx-core/apps/package-manifest/package-manifest.v1.1.schema.json",
"version": "1.0.0",
"id": "portainer-agent",
"menus": {
"sidebar": [
{
"id": "portainer.agent",
"title": "Portainer Agent",
"icon": "bosch-ic-construction",
"target": "_blank",
"link": "https://${hostname}:9001",
"permissions": []
}
],
"overview": [
{
"id": "portainer.agent",
"title": "Portainer Agent",
"icon": "bosch-ic-construction",
"target": "_blank",
"link": "https://${hostname}:9001",
"permissions": []
}
]
}
}
- docker-compose/
Holds the Docker Compose manifest (docker-compose.yml
) defining how the Portainer Edge Agent container is run.
version: "3.7"
services:
portainer-agent:
image: ${IMAGE_NAME}:${IMAGE_TAG}
container_name: portainer_edge_agent
environment:
- EDGE=1
- EDGE_ID=${EDGE_ID}
- EDGE_KEY=${EDGE_KEY}
- EDGE_INSECURE_POLL=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/snap/ctrlx-docker/common/var-lib-docker/volumes:/var/lib/docker/volumes
- /:/host
- portainer_agent_data:/data
restart: always
volumes:
portainer_agent_data:
- snapcraft.yaml
The Snapcraft build manifest defining how to package the Portainer Edge Agent snap. It declares the application, required interfaces (plugs), and includes the Docker Compose files and configs inside the snap.
name: portainer-agent
version: '2.30.1'
base: core20
summary: Portainer Edge Agent
description: |
This snap contains the Portainer Edge Agent docker image.
The agent will automatically connect to Portainer Edge Management.
grade: stable
confinement: strict
parts:
docker-compose:
plugin: dump
source: ./docker-compose
organize:
'*': docker-compose/${SNAPCRAFT_PROJECT_NAME}/
configs:
source: ./configs
plugin: dump
organize:
'package-assets/*': package-assets/${SNAPCRAFT_PROJECT_NAME}/
slots:
docker-compose:
interface: content
content: docker-compose
source:
read:
- $SNAP/docker-compose/${SNAPCRAFT_PROJECT_NAME}
docker-volumes:
interface: content
content: docker-volumes
source:
write:
- $SNAP_DATA/docker-volumes/${SNAPCRAFT_PROJECT_NAME}
package-assets:
interface: content
content: package-assets
source:
read:
- $SNAP/package-assets/${SNAPCRAFT_PROJECT_NAME}
package-run:
interface: content
content: package-run
source:
write:
- $SNAP_DATA/package-run/${SNAPCRAFT_PROJECT_NAME}
Building and Installing the Snap
Run the master build script to build your snap:
./build_all.sh [architecture]
This generates a snap package for your target architecture (it’s optional).
Install the snap on your ctrlX OS device via command line or device dashboard.
Managing ctrlX EDGE Devices via Portainer
Once the Portainer Edge Agent snap is running on your device:
- Use Portainer Server’s Edge Stacks feature to remotely deploy any Docker container or stack on your ctrlX devices.
- Manage container lifecycles, logs, and state without converting containers into snaps.
- Browse Docker volumes directly through Portainer’s intuitive interface, allowing you to inspect and manage persistent container data easily, this greatly simplifies debugging and data management.
- Scale deployments and orchestrate your entire fleet from one centralized Portainer dashboard.
- Benefit from both Snap’s confinement security and Docker’s flexibility.
Example: Deploying edgeConnector
to ctrlX Devices via Portainer
First of all, ensure you’ve built and installed the Edge Agent snap package by following the steps detailed in the previous section. The snap is built using the provided file structure (build_all.sh
, docker-compose.yml
, etc.) and running the command:
./build_all.sh [architecture]
and can be installed via the ctrlX Device Dashboard.
.webp)
Once the Edge Agent snap package is installed the device should appear on the waiting room of your server.
Once you associate the device and can see that it’s running and connected to your Portainer Server, you can deploy an app like edgeConnector
as shown below.
Step 1: Create an Edge Group
Before you can deploy an Edge Stack, you must assign your ctrlX device(s) to an Edge Group.
- In Portainer, go to Edge Group.
- Click create a new Edge Group.
- Name the group (e.g.,
ctrlx-devices
) and select the Static option. - Save the group.
💡 Edge Groups let you deploy stacks to one or multiple devices at once.
Step 2: Associate your device
- In Portainer, go to Waiting room.
- Select you ctrlx device and click on Associate and assignment
- From the Edge Group dropdown select the previously created Edge Group.
- Now your device should appear in your home as a running device with a heartbeat
Step 3: Create the Edge Stack
- In Portainer, go to Edge Stacks.
- Click + Add Stack.
- Give your stack a name (e.g.,
edgeconnector-stack
). - Select the Edge group to which you assigned your device
- Select Web editor as the deployment method.
Step 2: Define the Docker Compose File
Paste the following example into the editor. In this case, we’re using the edgeConnector application as an example, but you can substitute it with any other container that fits your use case.
version: "3.8"
services:
edgeconnector-siemens:
image: softingindustrial/edgeconnector-siemens
container_name: edgeconnector-siemens
restart: unless-stopped
ports:
- "443:443"
- "4897:4897"
- Click Deploy the stack.
Portainer will now push the container definition to your ctrlX Edge Agent, which will pull and start the container locally on the device.
Step 4: Verify the Deployment
- Go to Environments > [Your ctrlX Device] > Containers to see the
edgeconnector
container running. - You can view logs, inspect resource usage, or redeploy updated configurations—all remotely from Portainer.
Conclusion
This project structure and build process make it easy to deploy Portainer Edge Agent on ctrlX OS devices as a snap. It eliminates the need for tedious snap packaging of every container and unlocks seamless container fleet management.