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.
By default, RobustOS Pro does not include a Docker environment and requires manual installation.
Ensure your device is connected to the internet.
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
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.
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
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.
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.
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)
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"]
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 .
After the image is built, we can use it to run containers.
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.
The --device parameter can map host device files to inside the container.
Format: --device=<host_path>:<container_path>
Example: --device=/dev/ttyCOM1:/dev/ttyCOM1
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)
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.
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.
docker ps
# Use container name to view real-time logs
docker logs -f serial-app-container
docker stop serial-app-container # Stop the container
docker start serial-app-container # Start the container
docker restart serial-app-container # Restart the container
# 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
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 \
...
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
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:
Check host permissions: On the host, run ls -l /dev/ttyCOM1 to check its user group (usually dialout or tty).
Use privileged mode (not recommended): Adding the --privileged flag to the docker run command can solve this, but it introduces serious security risks.
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.
Problem: docker run fails with an error message containing "port is already allocated".
Reason: Host port 3000 is already occupied by another service.
Solution:
Stop the service occupying the port.
Change to a different host port in the docker run command, for example, -p 3001:5000.
# 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.
| Command | Description |
|---|---|
docker build -t <image_name> . | Build an image from the Dockerfile in the current directory |
docker images | List all local images |
docker run [options] <image_name> | Create and run a container from an image |
docker ps | List all running containers |
docker ps -a | List 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 |
Official Docker Documentation: https://docs.docker.com/
Dockerfile Best Practices: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
Docker Compose Documentation: https://docs.docker.com/compose/