Deploying Applications with Docker on RobustOS Pro

Deploying Applications with Docker on RobustOS Pro

This document guides developers on installing and using Docker on RobustOS Pro devices (firmware version 2.4.0 or higher), and demonstrates application deployment and management in containers through a specific example: creating a Web application that interacts with hardware serial ports.

1. Installing Docker on RobustOS Pro

By default, RobustOS Pro does not include a Docker environment and requires manual installation.

1.1. System Requirements

Ensure your device is connected to the internet.

1.2. Installation Steps

SSH into your RobustOS Pro device and execute the following commands:

# 1. Update system package list
sudo apt-get update

# 2. Install Docker engine
# apt-get will automatically handle dependencies and install the latest stable version of Docker
sudo apt-get install docker-ce docker-ce-cli containerd.io

1.3. Verify Docker Installation

After installation, run the following command to check the Docker version and confirm successful installation.

docker --version

If successful, you'll see output similar to Docker version 28.2.2, build e6534b4.

1.4. Configure Docker Auto-Start and User Permissions

For convenience, it's recommended to set Docker to auto-start on boot and add the current user to the docker group to avoid typing sudo every time.

# Set Docker service to auto-start on boot
sudo systemctl enable docker

# Add current user to docker group
# Note: After running this command, you need to re-login to the SSH session for changes to take effect!
sudo usermod -aG docker $USER

2. Creating a Docker Application That Interacts with Hardware

This section guides you through creating a Python Flask application. The application can receive user input data through a Web page, send it via the device's serial port, and read and display serial port data on the page.

2.1. Functional Goals

  • Create a Web service listening on port 5000.

  • Provide an input box and "Send" button on the Web page to send data to the serial port.

  • Provide an area on the Web page to display the latest data read from the serial port.

2.2. Sample Application Code

In your development directory, create the following two files.

requirements.txt:

Flask==2.0.1
Werkzeug==2.3.8
pyserial==3.5

app.py:

import serial
from flask import Flask, request, render_template_string

# --- Configuration ---
# Modify serial port device file and baud rate according to your device
SERIAL_PORT = '/dev/ttyCOM1'
BAUDRATE = 115200
# -------------

app = Flask(__name__)
ser = None

# Initialize serial port
try:
    ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1)
    print(f"Successfully opened serial port {SERIAL_PORT}")
except Exception as e:
    print(f"Error opening serial port: {e}")

# HTML template
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
    <title>RobustOS Serial Control</title>
    <style>
        body { font-family: sans-serif; margin: 2em; }
        .container { max-width: 600px; margin: auto; padding: 1em; border: 1px solid #ccc; border-radius: 5px; }
        .form-group { margin-bottom: 1em; }
        label { display: block; margin-bottom: 0.5em; }
        input, button { width: 100%; padding: 0.5em; box-sizing: border-box; }
        button { background-color: #007bff; color: white; border: none; cursor: pointer; }
        .response { margin-top: 1em; padding: 1em; background-color: #f0f0f0; border: 1px solid #ddd; }
    </style>
</head>
<body>
    <div class="container">
        <h1>RobustOS Serial Web Console</h1>

        <div class="form-group">
            <form method="post" action="/send">
                <label for="data">Data to send:</label>
                <input type="text" id="data" name="data" required>
                <br><br>
                <button type="submit">Send to Serial Port</button>
            </form>
        </div>

        <div class="form-group">
            <form method="get" action="/read">
                <button type="submit">Read from Serial Port</button>
            </form>
        </div>

        {% if response_data %}
        <div class="response">
            <strong>Data read from serial port:</strong>
            <pre>{{ response_data }}</pre>
        </div>
        {% endif %}

        {% if error_message %}
        <div class="response" style="background-color: #ffdddd; border-color: #ffaaaa;">
            <strong>Error:</strong>
            <pre>{{ error_message }}</pre>
        </div>
        {% endif %}
    </div>
</body>
</html>
"

@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE)

@app.route('/send', methods=['POST'])
def send_data():
    error = None
    if not ser:
        error = "Serial port not initialized."
        return render_template_string(HTML_TEMPLATE, error_message=error)

    data_to_send = request.form.get('data', '')
    if data_to_send:
        try:
            ser.write(data_to_send.encode('utf-8'))
            print(f"Sent: {data_to_send}")
        except Exception as e:
            error = f"Failed to send data: {e}"
    return render_template_string(HTML_TEMPLATE, error_message=error)

@app.route('/read', methods=['GET'])
def read_data():
    error = None
    response = None
    if not ser:
        error = "Serial port not initialized."
        return render_template_string(HTML_TEMPLATE, error_message=error)

    try:
        if ser.in_waiting > 0:
            response = ser.read(ser.in_waiting).decode('utf-8')
            print(f"Read: {response}")
        else:
            response = "(No new data)"
    except Exception as e:
        error = f"Failed to read data: {e}"
    return render_template_string(HTML_TEMPLATE, response_data=response, error_message=error)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

2.3. Writing the Dockerfile

In the same directory as app.py and requirements.txt, create a file named Dockerfile with the following content:

# Stage 1: Use official Python image as base
FROM python:3.9-slim

# Stage 2: Set working directory
WORKDIR /app

# Stage 3: Copy dependency file and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 4: Copy application code
COPY app.py .

# Stage 5: Expose container port 5000
EXPOSE 5000

# Stage 6: Set command to execute when container starts
CMD ["python", "app.py"]

2.4. Building the Docker Image

In the directory containing the Dockerfile, execute the following command to build the image:

# -t parameter names the image for easy reference
docker build -t robustos-serial-app .

3. Running and Managing Docker Containers

After the image is built, we can use it to run containers.

3.1. Running a Container (docker run)

To allow the application in the container to access the host's hardware resources (serial port) and network, we need to add specific parameters when running.

3.1.1. Key Parameter: Mapping Device Serial Port

The --device parameter can map host device files to inside the container.

  • Format: --device=<host_path>:<container_path>

  • Example: --device=/dev/ttyCOM1:/dev/ttyCOM1

3.1.2. Key Parameter: Mapping Network Port

The -p parameter can map host ports to container ports, allowing external access to services in the container.

  • Format: -p <host_port>:<container_port>

  • Example: -p 3000:5000 (maps host port 3000 to container port 5000)

3.1.3. Complete Run Command Example

Execute the following command to start our application container:

docker run \
    -d \
    --name serial-app-container \
    --restart=always \
    --device=/dev/ttyCOM1:/dev/ttyCOM1 \
    -p 3000:5000 \
    robustos-serial-app
  • -d: Run container in background.

  • --name: Assign a unique name to the container.

  • --restart=always: Automatically restart the container when Docker restarts or the container exits.

3.2. Verifying Application Functionality

  • Access via browser: Open your browser and visit http://<device_ip>:3000.

  • Test functionality: You should see the Web interface. Enter text in the input box and click "Send" to send data through Serial Port 1 (/dev/ttyCOM1). Click the "Read" button to read data from Serial Port 1 and display it on the page.

3.3. Common Container Management Commands

  • View running containers:
docker ps
  • View container logs:
# Use container name to view real-time logs
docker logs -f serial-app-container
  • Manage container lifecycle:
docker stop serial-app-container   # Stop the container
docker start serial-app-container  # Start the container
docker restart serial-app-container # Restart the container
  • Delete containers and images:
# Need to stop the container before deleting
docker stop serial-app-container
docker rm serial-app-container

# Delete the image
docker rmi robustos-serial-app

4. Advanced Topics

4.1. Data Persistence: Using Data Volumes (Volumes)

If your application needs to save data (e.g., log files, configuration files), it's best to use data volumes so that data can be retained even if the container is deleted.

docker run \
    ... \
    -v /path/on/host:/path/in/container \
    ...
  • Example: -v /data/app_logs:/app/logs maps the /data/app_logs directory on the host to the /app/logs directory in the container.

4.2. Using Docker Compose

When applications become complex, the docker run command can become very long. Docker Compose allows you to define and run multi-container applications using a YAML file. Newer versions of Docker have integrated Compose functionality into the main CLI, and it is recommended to use the docker compose command.

First, ensure your Docker version is recent and includes the Compose plugin. If not, you can try installing it with the following command:

sudo apt-get install docker-compose-plugin

Then, create a docker-compose.yml file:

services:
  serial_app:
    image: robustos-serial-app
    container_name: serial-app-compose
    restart: always
    ports:
      - "3000:5000"
    devices:
      - "/dev/ttyCOM1:/dev/ttyCOM1"

Use the following commands to start and stop the application:

# Execute in the directory containing docker-compose.yml
docker compose up -d   # Start services

docker compose down  # Stop and remove services

5. Troubleshooting

5.1. Container Permission Issues Accessing Serial Port

  • Problem: Application logs show "Permission denied" when accessing /dev/ttyCOM1.

  • Reason: The permissions of the /dev/ttyCOM1 file on the host may not allow access by the user inside the Docker container.

  • Solution:

    1. Check host permissions: On the host, run ls -l /dev/ttyCOM1 to check its user group (usually dialout or tty).

    2. Use privileged mode (not recommended): Adding the --privileged flag to the docker run command can solve this, but it introduces serious security risks.

    3. Correct permission management: Ensure that the user running the container (or the user specified in the Dockerfile) has permission to access the device. For default root user containers, the --device parameter is usually sufficient.

5.2. Port Mapping Conflict Issues

  • Problemdocker run fails with an error message containing "port is already allocated".

  • Reason: Host port 3000 is already occupied by another service.

  • Solution:

    1. Stop the service occupying the port.

    2. Change to a different host port in the docker run command, for example, -p 3001:5000.

5.3. Common Diagnostic Commands

  • Enter inside a container:
# Start an interactive shell inside a running container
docker exec -it serial-app-container /bin/bash

Once inside the container, you can inspect files, networks, and processes just like in a regular Linux environment.

6. Appendix

6.1. Common Docker Command Cheat Sheet

CommandDescription
docker build -t <image_name> .Build an image from the Dockerfile in the current directory
docker imagesList all local images
docker run [options] <image_name>Create and run a container from an image
docker psList all running containers
docker ps -aList all containers (including stopped ones)
docker stop <container_name_or_id>Stop a running container
docker start <container_name_or_id>Start a stopped container
docker logs <container_name_or_id>View container logs
docker rm <container_name_or_id>Delete a container
docker rmi <image_name_or_id>Delete an image
docker exec -it <container_name_or_id> <command>Execute a command inside a running container
    • Related Articles

    • RobustOS Pro Application Development Getting Started

      Preface Welcome to RobustOS Pro! This guide provides a step-by-step roadmap for understanding, developing, and deploying applications on the platform. This document is only applicable to RobustOS Pro 2.4.x firmware versions. System Architecture ...
    • Web Service Integration Guide for RobustOS Pro

      1. Core Conventions Before starting, please follow these conventions to avoid affecting system stability. 1.1 Files and Directories Path Purpose Action /etc/nginx/sites-available/ Configuration files for standalone port services Create new files here ...
    • RobustOS Pro SDK

      Robustel's router allows 3rd party to develop their applications. We provide a Software Development Kit (SDK), which offers a simple and fast way to implement customer-specific functions and applications. This SDK is compatible with EG5100, LG5100, ...
    • How to Configure APN on RobustOS Pro Gateways (EV8100, EG5100 Series, R1520LG, LG5100, EG5200, MG460)

      Overview This guide provides step-by-step instructions for configuring a custom Access Point Name (APN) on Robustel gateways running the RobustOS Pro system, including models such as EV8100, EG51xx series, R1520LG, LG5100, EG5200, and MG460. It ...
    • RobustOS Pro Third-Party Application Development Guide

      1. Quick Overview What is RobustOS Pro? RobustOS Pro is an embedded Linux distribution based on Debian 11 (bullseye), designed specifically to meet the demanding requirements of industrial IoT gateways, providing a high degree of customization and ...