Post

From Embedded App to Container

From Embedded App to Container

Docker whale

So you have an application running on a Raspberry Pi, BeagleBone, or similar embedded Linux platform. Maybe it’s a temperature monitor, a data logger, or a custom control system. You’ve heard about Infix and want to try running your application as a container. This guide shows you how.

Previous posts in this series covered running existing containers, advanced networking, and basic container setups. This time we focus on something different: how to containerize your own application.

We’ll walk through the complete process: preparing your application, creating a container image, and running it on Infix. By the end, you’ll have a containerized application that’s easier to deploy, update, and maintain.

This guide assumes basic familiarity with Linux and command-line tools. You should have Infix installed and be able to log in via SSH or console. For detailed container documentation, see the container guide.

What You’ll Need

Before starting, gather:

  1. Your application - source code or compiled binaries
  2. Dependencies - libraries, configuration files, data files
  3. Infix system - running v24.11.0 or later
  4. Container tools - Docker (or Podman) on your development machine (optional)
  5. Network access - to download base images and push your container

Infix uses Podman internally for container management, but provides a docker command alias for familiarity. All examples use docker, which works identically whether you’re using Docker or Podman.

Example Application

Let’s use a simple but realistic example: a Python script that monitors system temperature and logs data to a file. This represents a typical embedded IoT application.

temp-monitor.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python3
import time
import os
import configparser
from datetime import datetime

def read_temperature():
    """Read temperature from system (mock for demo)"""
    # On real hardware: read from /sys/class/thermal/thermal_zone0/temp
    # For demo, return a simulated value
    return 42.5

def load_config(config_file='/etc/temp-monitor.conf'):
    """Load configuration from file, fall back to environment/defaults"""
    config = configparser.ConfigParser()

    # Set defaults
    defaults = {
        'log_file': os.getenv('LOG_FILE', '/data/temperature.log'),
        'interval': os.getenv('INTERVAL', '60'),
        'format': 'csv'
    }

    # Try to read config file if it exists
    if os.path.exists(config_file):
        config.read(config_file)
        if config.has_section('monitor'):
            return {
                'log_file': config.get('monitor', 'log_file', fallback=defaults['log_file']),
                'interval': config.getint('monitor', 'interval', fallback=int(defaults['interval'])),
                'format': config.get('monitor', 'format', fallback=defaults['format'])
            }

    # Use defaults if no config file
    return {
        'log_file': defaults['log_file'],
        'interval': int(defaults['interval']),
        'format': defaults['format']
    }

def main():
    cfg = load_config()

    print(f"Temperature monitor starting...")
    print(f"Logging to: {cfg['log_file']}")
    print(f"Interval: {cfg['interval']}s")
    print(f"Format: {cfg['format']}")

    while True:
        temp = read_temperature()
        timestamp = datetime.now().isoformat()

        if cfg['format'] == 'csv':
            log_entry = f"{timestamp},{temp}\n"
        else:
            log_entry = f"[{timestamp}] Temperature: {temp}°C\n"

        with open(cfg['log_file'], 'a') as f:
            f.write(log_entry)

        print(f"{timestamp}: {temp}°C")
        time.sleep(cfg['interval'])

if __name__ == '__main__':
    main()

On your current embedded system, you probably:

  • Install Python: apt install python3
  • Copy the script somewhere: /usr/local/bin/temp-monitor.py
  • Add a systemd service or cron job
  • Hope everything still works after the next system update

Let’s containerize this instead.

Infix includes mg, a lightweight Emacs-compatible editor, a great alternative for editing files directly on the device. Just use mg Containerfile instead of cat > (below) for a better editing experience. {: .prompt-tip }

Creating a Container Image

You have two options for creating container images:

  1. Build elsewhere, run on Infix - recommended for most users
  2. Build directly on Infix - useful for prototyping

Choosing a Base Image

Before creating your container, consider which base image to use:

Alpine Linux (used in our examples):

  • Minimal size (~5 MB base image)
  • Uses musl libc instead of glibc
  • Fast package installation with apk
  • Limitation: Not 100% compatible with glibc - some applications built for standard Linux distributions may not work
  • Best for: New applications, Python/Go/Rust apps, size-critical deployments

Debian/Ubuntu:

  • Uses standard glibc (better compatibility)
  • Larger base image (~50-100 MB)
  • Familiar apt package manager
  • Best for: Existing applications, binary compatibility requirements, complex dependencies

Example alternatives:

1
2
3
FROM debian:bookworm-slim     # Debian 12 (minimal)
FROM ubuntu:22.04             # Ubuntu LTS
FROM python:3.11              # Python on Debian (larger but more compatible)

If you’re migrating an existing application from Raspberry Pi OS, Debian, or Ubuntu, starting with a Debian-based image often saves troubleshooting time.

Cross-Architecture Considerations

Many developers build on x86_64 (amd64) workstations but deploy to ARM devices. Here’s what you need to know:

Architecture matching: Your Infix device might run:

  • aarch64 (ARM 64-bit) - Raspberry Pi 4/5, most modern ARM devices
  • armv7l (ARM 32-bit) - Older Raspberry Pi models
  • x86_64 (amd64) - PC-based systems

Build strategies:

  1. Multi-architecture builds (recommended for distribution):
    1
    2
    
    $ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 \
        -t myapp/temp-monitor:v1.0 --push .
    
  2. Target-specific builds (simplest for single deployment):
    1
    
    $ docker build --platform linux/arm64 -t myapp/temp-monitor:v1.0 .
    
  3. Native builds (build directly on target architecture)

Important notes:

  • Pure interpreted languages (Python, Ruby) work across architectures
  • Compiled code must match the target architecture
  • Pre-built binaries in your container must be for the target architecture
  • Cross-compilation may require QEMU emulation (slower builds)

Option 1: Build on Your Development Machine

Create a Containerfile (or Dockerfile) in your project directory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM python:3.11-alpine

# Install any system dependencies
# RUN apk add --no-cache some-package

# Copy your application
COPY temp-monitor.py /usr/local/bin/temp-monitor.py
RUN chmod +x /usr/local/bin/temp-monitor.py

# Set default environment variables
ENV LOG_FILE=/data/temperature.log
ENV INTERVAL=60

# Create directory for data (will be a volume)
RUN mkdir -p /data

# Run the application
CMD ["/usr/local/bin/temp-monitor.py"]

Build the image:

1
$ docker build -t myapp/temp-monitor:v1.0 .

Export as OCI archive:

1
2
$ docker save -o temp-monitor-v1.0.tar myapp/temp-monitor:v1.0
$ gzip temp-monitor-v1.0.tar

Transfer to your Infix device:

1
$ scp temp-monitor-v1.0.tar.gz admin@infix-device:/var/tmp/

Option 2: Build on Infix (Prototyping)

For quick experiments, you can build directly on Infix. Exit the CLI to your login shell and prepare your application files in /var/tmp/myapp/:

1
2
3
4
5
6
7
8
9
10
admin@infix:~$ mkdir -p /var/tmp/myapp
admin@infix:~$ cd /var/tmp/myapp
admin@infix:/var/tmp/myapp$ cat > temp-monitor.py
#!/usr/bin/env python3
... paste your script here ...
^D
admin@infix:/var/tmp/myapp$ cat > Containerfile
FROM python:3.11-alpine
... paste containerfile here ...
^D

Build the image:

1
admin@infix:/var/tmp/myapp$ sudo docker build -t temp-monitor:latest .

Building on the device consumes storage in /var and requires CPU resources. For production, build on a development machine instead.

Running Your Container on Infix

Now that you have a container image, let’s configure Infix to run it.

Basic Configuration

First, load the image (if you transferred an archive):

1
admin@infix:/> container load /var/tmp/temp-monitor-v1.0.tar.gz name temp-monitor:v1.0

Or reference the OCI archive directly in your configuration:

1
2
3
4
5
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set image oci-archive:/var/tmp/temp-monitor-v1.0.tar.gz
admin@infix:/config/container/temp-monitor/> set hostname temp-mon
admin@infix:/config/container/temp-monitor/> leave

Check the status:

1
2
3
admin@infix:/> show container
CONTAINER ID  IMAGE                               COMMAND     CREATED        STATUS        PORTS  NAMES
a1b2c3d4e5f6  localhost/temp-monitor:v1.0                     5 seconds ago  Up 4 seconds         temp-monitor

View the logs:

1
2
3
4
5
admin@infix:/> container log temp-monitor
Temperature monitor starting...
Logging to: /data/temperature.log
Interval: 60s
2025-11-20T10:15:00: 42.5°C

Adding Persistent Storage

The container is running, but logs are lost when it restarts. Add a volume:

1
2
3
4
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set volume logs target /data
admin@infix:/config/container/temp-monitor/> leave

Now /data in the container is persistent and survives container upgrades.

Customizing Environment Variables

Override the default logging interval:

1
2
3
4
5
6
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> edit env INTERVAL
admin@infix:/config/container/temp-monitor/env/INTERVAL/> set value 30
admin@infix:/config/container/temp-monitor/env/INTERVAL/> end
admin@infix:/config/container/temp-monitor/> leave

Container Networking

Your application may need network access. Infix provides flexible options.

Host Networking (Simplest)

For applications that need full network access:

1
2
3
4
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set network host true
admin@infix:/config/container/temp-monitor/> leave

The container shares the host’s network namespace - all ports, interfaces, and routing are identical to the host.

Bridge Network (Isolated)

For better isolation, use a container bridge:

1
2
3
4
5
6
7
admin@infix:/> configure
admin@infix:/config/> edit interface docker0
admin@infix:/config/interface/docker0/> set container-network
admin@infix:/config/interface/docker0/> end
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set network interface docker0
admin@infix:/config/container/temp-monitor/> leave

If your application exposes an HTTP API on port 8080:

1
admin@infix:/config/container/temp-monitor/> set network publish 8080:8080

Direct Interface Access

Infix can give containers direct access to physical ports or VETH pairs:

1
2
3
4
5
6
7
8
9
10
11
12
admin@infix:/> configure
admin@infix:/config/> edit interface veth0
admin@infix:/config/interface/veth0/> set veth peer veth0c
admin@infix:/config/interface/veth0/> set ipv4 address 192.168.10.1 prefix-length 24
admin@infix:/config/interface/veth0/> end
admin@infix:/config/> edit interface veth0c
admin@infix:/config/interface/veth0c/> set ipv4 address 192.168.10.2 prefix-length 24
admin@infix:/config/interface/veth0c/> set container-network
admin@infix:/config/interface/veth0c/> end
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set network interface veth0c
admin@infix:/config/container/temp-monitor/> leave

This gives precise control over container networking and enables advanced setups like dedicated monitoring networks.

Migrating from systemd

On traditional embedded Linux, you probably use systemd to manage your application. Here’s how container concepts map to systemd:

systemdInfix ContainerNotes
systemctl startContainer runs automaticallySet manual false (default)
systemctl enablePersistent configSave with copy running-config startup-config
Restart=alwaysrestart-policy alwaysDefault behavior
Environment fileenv settingsSet key-value pairs in config
ExecStartPre=Not neededHandle in Containerfile
Logs in journaldContainer logsUse container log NAME

To make your container start automatically at boot (default):

1
2
admin@infix:/config/container/temp-monitor/> show manual
manual false;

To require manual start:

1
admin@infix:/config/container/temp-monitor/> set manual true

Configuration Files

Many embedded applications need configuration files. Infix provides two approaches:

Approach 1: Content Mounts (Small Files)

For small config files, store content directly in Infix configuration:

1
2
3
4
5
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> edit mount settings
admin@infix:/config/container/temp-monitor/mount/settings/> set target /etc/temp-monitor.conf
admin@infix:/config/container/temp-monitor/mount/settings/> text-editor content

An editor opens where you can paste your configuration:

1
2
3
4
[monitor]
interval = 15
log_file = /data/temperature.log
format = text

On exit, it’s automatically base64 encoded and stored in startup-config. The content is available to your container at /etc/temp-monitor.conf, and your Python application will now use these settings instead of the defaults.

Approach 2: Volume Mounts (Large Files)

For larger files or collections, use a volume:

1
2
3
4
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set volume config target /etc/app
admin@infix:/config/container/temp-monitor/> leave

Then populate files from your login shell:

1
2
3
admin@infix:~$ sudo -i
root@infix:~# cd /var/lib/containers/storage/volumes/temp-monitor-config/_data
root@infix:/var/lib/containers/storage/volumes/temp-monitor-config/_data# vi config.yaml

Upgrading Your Application

When you release a new version of your application:

Build and export the new version:

1
2
$ docker build -t myapp/temp-monitor:v1.1 .
$ docker save -o temp-monitor-v1.1.tar.gz myapp/temp-monitor:v1.1

Transfer and update configuration:

1
2
3
4
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set image oci-archive:/var/tmp/temp-monitor-v1.1.tar.gz
admin@infix:/config/container/temp-monitor/> leave

Infix automatically stops the old container and starts the new one. Your volumes remain intact.

Using Mutable Tags (Development)

For development with :latest tags:

1
admin@infix:/> container upgrade temp-monitor

This pulls the newest image and recreates the container.

Publishing to a Registry

For easier deployment across multiple devices, publish to a container registry:

GitHub Container Registry

1
2
3
$ docker login ghcr.io
$ docker tag temp-monitor:v1.0 ghcr.io/username/temp-monitor:v1.0
$ docker push ghcr.io/username/temp-monitor:v1.0

On Infix:

1
admin@infix:/config/container/temp-monitor/> set image ghcr.io/username/temp-monitor:v1.0

Docker Hub

1
2
3
$ docker login docker.io
$ docker tag temp-monitor:v1.0 username/temp-monitor:v1.0
$ docker push username/temp-monitor:v1.0

On Infix:

1
admin@infix:/config/container/temp-monitor/> set image username/temp-monitor:v1.0

Now deploying to new devices is as simple as adding the container configuration - no need to transfer archive files manually.

Advanced Topics

Hardware Access

If your application needs GPIO, I2C, or other hardware:

1
2
3
4
5
6
7
admin@infix:/config/container/temp-monitor/> edit mount gpio
admin@infix:/config/container/temp-monitor/mount/gpio/> set source /sys/class/gpio
admin@infix:/config/container/temp-monitor/mount/gpio/> set target /sys/class/gpio
admin@infix:/config/container/temp-monitor/mount/gpio/> set read-only false
admin@infix:/config/container/temp-monitor/mount/gpio/> end
admin@infix:/config/container/temp-monitor/> edit capabilities
admin@infix:/config/container/temp-monitor/capabilities/> set add sys_admin

For device access (e.g., /dev/i2c-1):

1
2
3
4
admin@infix:/config/container/temp-monitor/> edit mount i2c
admin@infix:/config/container/temp-monitor/mount/i2c/> set source /dev/i2c-1
admin@infix:/config/container/temp-monitor/mount/i2c/> set target /dev/i2c-1
admin@infix:/config/container/temp-monitor/mount/i2c/> set read-only false

Running Multiple Instances

Need to run multiple copies with different configurations?

1
2
3
4
5
6
7
8
9
10
11
admin@infix:/> configure
admin@infix:/config/> edit container temp-monitor-1
admin@infix:/config/container/temp-monitor-1/> set image temp-monitor:v1.0
admin@infix:/config/container/temp-monitor-1/> edit env SENSOR
admin@infix:/config/container/temp-monitor-1/env/SENSOR/> set value sensor1
admin@infix:/config/container/temp-monitor-1/env/SENSOR/> end
admin@infix:/config/container/temp-monitor-1/> end
admin@infix:/config/> edit container temp-monitor-2
admin@infix:/config/container/temp-monitor-2/> set image temp-monitor:v1.0
admin@infix:/config/container/temp-monitor-2/> edit env SENSOR
admin@infix:/config/container/temp-monitor-2/env/SENSOR/> set value sensor2

Complete Example Configuration

Here’s a complete working configuration for reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
admin@infix:/> configure
admin@infix:/config/> edit interface docker0
admin@infix:/config/interface/docker0/> set container-network
admin@infix:/config/interface/docker0/> end
admin@infix:/config/> edit container temp-monitor
admin@infix:/config/container/temp-monitor/> set image oci-archive:/var/tmp/temp-monitor-v1.0.tar.gz
admin@infix:/config/container/temp-monitor/> set hostname temp-mon
admin@infix:/config/container/temp-monitor/> set network interface docker0
admin@infix:/config/container/temp-monitor/> edit env INTERVAL
admin@infix:/config/container/temp-monitor/env/INTERVAL/> set value 30
admin@infix:/config/container/temp-monitor/env/INTERVAL/> end
admin@infix:/config/container/temp-monitor/> set volume logs target /data
admin@infix:/config/container/temp-monitor/> leave
admin@infix:/> copy running-config startup-config

Troubleshooting

Container won’t start:

1
2
admin@infix:/> show log
admin@infix:/> container log temp-monitor

Check container status:

1
admin@infix:/> show container

Inspect running container:

Exit the CLI and use the docker command to exec into the container:

1
2
admin@infix:~$ sudo docker exec -it temp-monitor sh
/ #

Remove and recreate:

1
2
3
admin@infix:/> configure
admin@infix:/config/> delete container temp-monitor
admin@infix:/config/> leave

Then recreate with the configuration above.

Conclusion

You’ve successfully containerized an embedded application and deployed it on Infix. Your application is now:

  • Isolated - dependencies don’t conflict with the system
  • Portable - runs on any Infix device
  • Upgradeable - new versions deploy without affecting data
  • Maintainable - configuration is versioned and reproducible

The same approach works for any embedded application: data loggers, control systems, protocol gateways, monitoring agents, and more.

For more advanced networking scenarios with your containers, check out the advanced networking post. If you need to understand the basics of running containers on Infix first, start with the Docker containers introduction.

For complete technical details, see the container documentation or explore the YANG model for all available configuration options.

Remember to save your configuration:

1
admin@infix:/> copy running-config startup-config

Take care!

This post is licensed under CC BY 4.0 by the author.