|
Linux Cross-Compilation in Practice: Docker Containerized Build Guide

Linux Cross-Compilation in Practice: Docker Containerized Build Guide

Embedded developers have all encountered this awkward situation: code compiles fine on the dev machine, but errors appear everywhere when switching environments. Dependency library versions are wrong, toolchains are missing, environment variables are chaotic… Today we’ll talk about how to use Docker to “package” the cross-compilation environment and take it anywhere, making the build process truly reproducible.

Why Do You Need a Containerized Cross-Compilation Environment?

Pain points of traditional cross-compilation:

  • Tedious environment configuration: you have to reconfigure the toolchain every time you reinstall the system

  • Inconsistent versions: team members using different compiler versions causes strange issues

  • Multi-architecture support is troublesome: developing for ARM, MIPS, and RISC-V simultaneously requires switching between multiple environments

  • CI/CD integration is difficult: automated build environments are hard to standardize

Docker containers perfectly solve these problems: configure once, run anywhere.

What Do You Need?

ItemModel/SpecPrice
Development HostLinux/Windows/Mac¥0 (already have)
Docker Engine20.10+¥0 (open source)
Cross-compilation Toolchaingcc-arm-linux-gnueabihf, etc.¥0 (open source)
Disk SpaceRecommended 10GB+¥0
Total¥0

Yes, you read that right, the complete solution is zero cost!

Step 1: Install Docker

Ubuntu/Debian System

# Uninstall old versions (if any)
sudo apt-get remove docker docker-engine docker.io containerd runc

# Install dependencies
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release

# Add Docker official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Set up stable repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

# Verify installation
docker --version

Add Current User to Docker Group (to Avoid sudo Every Time)

sudo usermod -aG docker $USER
# Log out and log back in for changes to take effect

Notes: ⚠️ If you’re on a corporate intranet, you may need to configure a Docker mirror accelerator. Edit /etc/docker/daemon.json:

{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://registry.cn-hangzhou.aliyuncs.com"
  ]
}

Step 2: Build ARM Cross-Compilation Environment

We’ll create a compilation environment targeting ARM Cortex-A series (like Raspberry Pi, i.MX6).

Create Dockerfile

Create Dockerfile.arm in the project root directory:

FROM ubuntu:22.04

# Avoid interactive prompts
ENV DEBIAN_FRONTEND=noninteractive

# Install basic tools
RUN apt-get update && apt-get install -y \
    build-essential \
    gcc-arm-linux-gnueabihf \
    g++-arm-linux-gnueabihf \
    binutils-arm-linux-gnueabihf \
    libc6-dev-armhf-cross \
    git \
    cmake \
    make \
    wget \
    vim \
    && rm -rf /var/lib/apt/lists/*

# Set cross-compilation environment variables
ENV CROSS_COMPILE=arm-linux-gnueabihf-
ENV ARCH=arm
ENV CC=${CROSS_COMPILE}gcc
ENV CXX=${CROSS_COMPILE}g++

# Create working directory
WORKDIR /workspace

# Default command
CMD ["/bin/bash"]

Build Image

docker build -f Dockerfile.arm -t cross-compile-arm:latest .

Use Container to Compile

# Mount project directory and enter container
docker run -it --rm \
    -v $(pwd):/workspace \
    cross-compile-arm:latest \
    /bin/bash

# Compile inside container
cd /workspace
arm-linux-gnueabihf-gcc -o hello hello.c

How it works: By using -v $(pwd):/workspace to mount the host’s current directory into the container, compilation artifacts will appear directly on the host, convenient for subsequent flashing or testing.

Step 3: Support Multi-Architecture Compilation

In actual projects, we may need to support multiple architectures simultaneously. Create a multi-architecture Docker image:

Create Multi-Architecture Dockerfile

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# Install multi-architecture cross-compilation toolchains
RUN apt-get update && apt-get install -y \
    build-essential \
    # ARM 32-bit
    gcc-arm-linux-gnueabihf \
    g++-arm-linux-gnueabihf \
    # ARM 64-bit
    gcc-aarch64-linux-gnu \
    g++-aarch64-linux-gnu \
    # MIPS
    gcc-mips-linux-gnu \
    g++-mips-linux-gnu \
    # RISC-V
    gcc-riscv64-linux-gnu \
    g++-riscv64-linux-gnu \
    # Common tools
    binutils-multiarch \
    git \
    cmake \
    make \
    && rm -rf /var/lib/apt/lists/*

# Create compilation script
RUN echo '#!/bin/bash' > /usr/local/bin/cross-compile.sh && \
    echo 'ARCH=$1' >> /usr/local/bin/cross-compile.sh && \
    echo 'shift' >> /usr/local/bin/cross-compile.sh && \
    echo 'case $ARCH in' >> /usr/local/bin/cross-compile.sh && \
    echo '  arm) export CROSS_COMPILE=arm-linux-gnueabihf- ;;' >> /usr/local/bin/cross-compile.sh && \
    echo '  arm64) export CROSS_COMPILE=aarch64-linux-gnu- ;;' >> /usr/local/bin/cross-compile.sh && \
    echo '  mips) export CROSS_COMPILE=mips-linux-gnu- ;;' >> /usr/local/bin/cross-compile.sh && \
    echo '  riscv64) export CROSS_COMPILE=riscv64-linux-gnu- ;;' >> /usr/local/bin/cross-compile.sh && \
    echo '  *) echo "Unsupported arch: $ARCH"; exit 1 ;;' >> /usr/local/bin/cross-compile.sh && \
    echo 'esac' >> /usr/local/bin/cross-compile.sh && \
    echo 'exec ${CROSS_COMPILE}gcc "$@"' >> /usr/local/bin/cross-compile.sh && \
    chmod +x /usr/local/bin/cross-compile.sh

WORKDIR /workspace
CMD ["/bin/bash"]

Build and Use

# Build multi-architecture image
docker build -f Dockerfile.multiarch -t cross-compile-multi:latest .

# Compile ARM version
docker run -it --rm -v $(pwd):/workspace cross-compile-multi:latest \
    cross-compile.sh arm -o hello_arm hello.c

# Compile ARM64 version
docker run -it --rm -v $(pwd):/workspace cross-compile-multi:latest \
    cross-compile.sh arm64 -o hello_arm64 hello.c

# Compile RISC-V version
docker run -it --rm -v $(pwd):/workspace cross-compile-multi:latest \
    cross-compile.sh riscv64 -o hello_riscv64 hello.c

Step 4: Integrate with CMake Projects

For projects using CMake, you need to configure a toolchain file.

Create CMake Toolchain File toolchain-arm.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CROSS_COMPILE arm-linux-gnueabihf-)

set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc)
set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Compile CMake Project Inside Docker

docker run -it --rm -v $(pwd):/workspace cross-compile-arm:latest \
    /bin/bash -c "
    mkdir -p build && cd build && \
    cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-arm.cmake .. && \
    make -j$(nproc)
    "

Step 5: CI/CD Integration Example

Use in .gitlab-ci.yml or GitHub Actions:

GitHub Actions Example

name: Cross Compile

on: [push]

jobs:
  build-arm:
    runs-on: ubuntu-latest
    container: cross-compile-arm:latest
    steps:
      - uses: actions/checkout@v3

      - name: Build
        run: |
          arm-linux-gnueabihf-gcc -o firmware main.c

      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: firmware-arm
          path: firmware

Common Problem Troubleshooting

Problem 1: Container can’t access host network

  • Cause: Docker default network isolation

  • Solution: Add --network host parameter (development environment only):

docker run -it --rm --network host -v $(pwd):/workspace cross-compile-arm:latest

Problem 2: Compiled program won’t run

  • Cause: Cross-compiled programs need to run on the target architecture

  • Solution: Use QEMU user-mode emulation to test:

# Install QEMU
sudo apt-get install qemu-user-static

# Run ARM program
qemu-arm-static ./hello_arm

Problem 3: Chinese characters display as garbled text inside container

  • Cause: Container lacks Chinese fonts/locale

  • Solution: Add to Dockerfile:

RUN apt-get install -y locales && \
  locale-gen zh_CN.UTF-8 && \
  update-locale LANG=zh_CN.UTF-8
ENV LANG=zh_CN.UTF-8

Problem 4: Image size is too large (over 2GB)

  • Cause: Installed too many unnecessary packages

  • Solution: Use multi-stage builds, only keep compilation artifacts:

# Build stage
FROM ubuntu:22.04 AS builder
RUN apt-get install -y gcc-arm-linux-gnueabihf make

# Runtime stage (only keep necessary tools)
FROM ubuntu:22.04
COPY --from=builder /usr/bin/arm-linux-gnueabihf-gcc /usr/bin/

Advanced Tips: Using Pre-built Images

If you don’t want to build it yourself, you can use community-maintained images:

# ARM cross-compilation
docker pull multiarch/crossbuild

# Usage example
docker run --rm -v $(pwd):/workspace -w /workspace multiarch/crossbuild \
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

Summary

Core advantages of containerizing cross-compilation environments with Docker:

  1. Reproducible environment—Dockerfile is version-controlled, any machine can restore the compilation environment with one click

  2. Multi-architecture support—one Dockerfile supports ARM, ARM64, MIPS, RISC-V cross-compilation simultaneously

  3. Team consistency—all members use exactly the same toolchain versions, avoiding “it compiles on my machine” issues

  4. Seamless CI/CD integration—container images are directly used in GitHub Actions / GitLab CI, zero-configuration automated builds

It’s recommended to include the project’s Dockerfile in version control, maintained alongside the code. This way, no matter how much time passes, as long as Docker is available, the compilation environment can be restored with one click.

Hope this blog article is helpful to you!


Related resources: