Tools: Breaking: How I Automated Multi-Platform Docker Image Builds for Embedded SoCs (RK3588, RV1126, RK3568)

Tools: Breaking: How I Automated Multi-Platform Docker Image Builds for Embedded SoCs (RK3588, RV1126, RK3568)

How I Automated Multi-Platform Docker Image Builds for Embedded SoCs

The Core Problems

Problem 1: Multiple Dockerfiles That Drift Apart

Problem 2: Port Collisions

Problem 3: Supporting Three Ubuntu Versions Without Breaking Any

The Three-Layer Config System

One Command to Build, One to Run

What I Wish I'd Done Earlier If you work with embedded Linux boards — RK3588, RV1126, RK3568, or similar ARM SoCs — you've probably hit these problems: I spent months dealing with this and eventually built a tool called HarborPilot to solve it. Here's what I learned. When you start, one Dockerfile per platform seems fine. After 6 months, the RK3588 file has fixes that never made it to the RK3568 file, and Ubuntu 24.04 support requires changes that would break 20.04. Solution: One monolithic 5-stage Dockerfile. Platform-specific behaviour is injected via ARG/ENV at build time. When you run Docker containers for RK3588 and RK3568 simultaneously, they'll both try to use port 2109 for SSH. You either document this manually (error-prone) or automate it. Solution: PORT_SLOT — a single integer that derives all port mappings: Set PORT_SLOT=0 for RK3588, PORT_SLOT=2 for RK3568. Done. Zero conflicts, no documentation needed. Ubuntu 24.04 has three breaking changes compared to 20.04/22.04: All three are handled with OS_VERSION conditionals in the relevant scripts. The key insight: instead of platform-specific Dockerfiles, use a config inheritance system. A real platform file for RK3568 on Ubuntu 20.04: That's the complete file. Everything else inherits from Layer 1 defaults. Adding a new platform takes 15-20 lines. The wizard handles PORT_SLOT collision detection automatically. Build a platform image: Start dev container on any Ubuntu host: Create a new platform non-interactively (CI-friendly): The envsubst switch was overdue. I originally used sed for template rendering in the Dockerfile stages — brittle, hard to debug. Replacing it with envsubst (from gettext-base) eliminated a whole class of escaping bugs. The modular ubuntu_only_entrance.sh was also worth the refactor. The original was a 400-line monolith. Splitting it into 6 numbered modules made debugging 10x faster. Feedback welcome — especially if you're using a different Rockchip or Allwinner SoC. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

Stage 1: base OS (apt sources, packages, user creation) Stage 2: dev tools (cmake, gdb, CUDA, OpenCV, Node.js) Stage 3: SDK init (git, symlinks, helper scripts) Stage 4: env config (proxy, profile variables) Stage 5: workspace + entrypoint + smoke tests Stage 1: base OS (apt sources, packages, user creation) Stage 2: dev tools (cmake, gdb, CUDA, OpenCV, Node.js) Stage 3: SDK init (git, symlinks, helper scripts) Stage 4: env config (proxy, profile variables) Stage 5: workspace + entrypoint + smoke tests Stage 1: base OS (apt sources, packages, user creation) Stage 2: dev tools (cmake, gdb, CUDA, OpenCV, Node.js) Stage 3: SDK init (git, symlinks, helper scripts) Stage 4: env config (proxy, profile variables) Stage 5: workspace + entrypoint + smoke tests CLIENT_SSH_PORT = 2109 + PORT_SLOT × 10 GDB_PORT = 2345 + PORT_SLOT × 10 CLIENT_SSH_PORT = 2109 + PORT_SLOT × 10 GDB_PORT = 2345 + PORT_SLOT × 10 CLIENT_SSH_PORT = 2109 + PORT_SLOT × 10 GDB_PORT = 2345 + PORT_SLOT × 10 PORT_SLOT=0 PORT_SLOT=2 /etc/apt/sources.list pip install --break-system-packages Layer 1: configs/defaults/*.env ← global defaults (10 domain-scoped files) Layer 2: configs/common.env ← project version & constants Layer 3: configs/platforms/*.env ← per-platform overrides (≤20 lines) Layer 1: configs/defaults/*.env ← global defaults (10 domain-scoped files) Layer 2: configs/common.env ← project version & constants Layer 3: configs/platforms/*.env ← per-platform overrides (≤20 lines) Layer 1: configs/defaults/*.env ← global defaults (10 domain-scoped files) Layer 2: configs/common.env ← project version & constants Layer 3: configs/platforms/*.env ← per-platform overrides (≤20 lines) # configs/platforms/rk3568-rk3568_ubuntu-20.04.env PRODUCT_NAME="rk3568-rk3568_ubuntu-20-04" CHIP_FAMILY="rk3568" OS_VERSION="20.04" OS_VERSION_ID="20-04" PORT_SLOT=2 HOST_VOLUME_DIR="/mnt/disk/volumes/rk3568" HAS_PROXY=true HTTP_PROXY_IP="192.168.3.152" # configs/platforms/rk3568-rk3568_ubuntu-20.04.env PRODUCT_NAME="rk3568-rk3568_ubuntu-20-04" CHIP_FAMILY="rk3568" OS_VERSION="20.04" OS_VERSION_ID="20-04" PORT_SLOT=2 HOST_VOLUME_DIR="/mnt/disk/volumes/rk3568" HAS_PROXY=true HTTP_PROXY_IP="192.168.3.152" # configs/platforms/rk3568-rk3568_ubuntu-20.04.env PRODUCT_NAME="rk3568-rk3568_ubuntu-20-04" CHIP_FAMILY="rk3568" OS_VERSION="20.04" OS_VERSION_ID="20-04" PORT_SLOT=2 HOST_VOLUME_DIR="/mnt/disk/volumes/rk3568" HAS_PROXY=true HTTP_PROXY_IP="192.168.3.152" ./harbor # Interactive: pick platform → build → tag → push to Harbor → verify manifest digest ./harbor # Interactive: pick platform → build → tag → push to Harbor → verify manifest digest ./harbor # Interactive: pick platform → build → tag → push to Harbor → verify manifest digest ./ubuntu_only_entrance.sh start ./ubuntu_only_entrance.sh start ./ubuntu_only_entrance.sh start ./scripts/create_platform.sh --non-interactive \ --name rk3566-debian12 --os debian --os-version 12 \ --harbor-ip 192.168.3.68 --port-slot 6 ./scripts/create_platform.sh --non-interactive \ --name rk3566-debian12 --os debian --os-version 12 \ --harbor-ip 192.168.3.68 --port-slot 6 ./scripts/create_platform.sh --non-interactive \ --name rk3566-debian12 --os debian --os-version 12 \ --harbor-ip 192.168.3.68 --port-slot 6 gettext-base ubuntu_only_entrance.sh - Five different Dockerfiles, diverging more with every Ubuntu release - Port collisions when running containers for multiple platforms at the same time - Ubuntu 24.04 broke your apt mirror setup, pip installs, and UID assignments all at once - Image push to Harbor requires glue scripts nobody maintains - DEB822 apt format — /etc/apt/sources.list is deprecated - UID 1000 pre-occupied — the default ubuntu user already has it - PEP 668 — pip install now refuses without --break-system-packages - GitHub: potterwhite/HarborPilot (MIT) - AI agent docs — codebase map and working rules for LLM coding agents - Current supported platforms: RK3588/RK3588S, RV1126/RV1126bp, RK3568 on Ubuntu 20.04/22.04/24.04