# 容器部署和使用指南 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- 本文档说明在 Quectel Pi M1 开发板(Debian 13 trixie)上部署和使用 Docker 容器的方法。容器将应用及其运行依赖打包在一起,实现环境隔离、快速分发与一键部署,适合在开发板上运行 Web 服务、数据库、Node-RED 等各类应用。本文档已在 M1 开发板上实测验证。 ## 简介 Docker 是一种轻量级的容器化技术,具有以下特点: - 易管理:使用 Docker 命令管理容器和镜像,依赖隔离,简化部署流程; - 可移植性:通过 Dockerfile 和镜像实现环境一致性,便于在不同机器上快速迁移和部署; - 高效资源利用:基于容器化技术共享主机内核,资源占用低、启动速度快,适合微服务架构; - 生态丰富:Docker Hub 等镜像仓库提供大量现成镜像,开箱即用。 使用容器的主要场景:运行 Web 服务(Nginx)、数据库(PostgreSQL、MySQL)、边缘网关(Node-RED)、AI 应用(DeepSeek、YOLO 等)等。 M1 采用 Android GKI 内核,缺少标准 Docker 依赖的内核特性(devices cgroup 控制器、iptables addrtype 匹配、PID 命名空间),标准安装方式无法直接使用。因此本文档采用 **rootless Docker + crun** 适配方案,可正常运行容器。 ## 准备工作 ### 系统要求 | **项目** | **要求** | | --- | --- | | 开发板 | Quectel Pi M1 | | 操作系统 | Debian GNU/Linux 13 (trixie) | | 架构 | ARM64(aarch64) | | 网络 | 可访问互联网(用于下载软件包与拉取镜像) | | 磁盘空间 | 建议至少 2 GB 可用空间 | | 权限 | root 权限或 sudo 权限 | ### 环境确认 在终端中执行以下命令确认系统环境: ```bash cat /etc/os-release uname -m df -h / ``` ### 内核特性说明 M1 的 GKI 内核裁剪了以下标准 Docker 依赖的特性,因此需使用 rootless + crun 适配方案: | **缺失特性** | **影响** | | --- | --- | | devices cgroup 控制器 | 标准 dockerd 启动报 "Devices cgroup isn't mounted" | | iptables addrtype 匹配 | iptables 报 "Extension addrtype revision 0 not supported" | | PID 命名空间 | 无法创建容器独立的进程命名空间 | 可执行以下命令核对内核配置: ```bash zcat /proc/config.gz | grep -E "CGROUP_DEVICE|NETFILTER_XT_MATCH_ADDRTYPE|PID_NS" ``` ## 安装步骤(GKI 内核适配:rootless Docker + crun) 原理:rootless 模式不依赖 devices cgroup,配合 crun 运行时(支持禁用 cgroup 管理)与适配脚本,在当前内核上可正常运行容器。以下步骤在 M1(内核 5.15.180-gki-consolidate)上实测通过。 ### 安装依赖软件 ```bash sudo apt update sudo apt install -y docker.io slirp4netns fuse-overlayfs uidmap rootlesskit crun docker-compose sudo sysctl -w net.ipv4.ip_unprivileged_port_start=0 ``` ### 创建 rootless 用户 rootless Docker 必须以普通用户运行(不能是 root),创建一个专用用户并配置用户映射: ```bash sudo useradd -m -s /bin/bash dockeruser echo "dockeruser:100000:65536" | sudo tee -a /etc/subuid echo "dockeruser:100000:65536" | sudo tee -a /etc/subgid ``` ### 创建 crun 适配脚本 创建 `/usr/local/bin/crun-wrapper`,用于适配当前内核的限制(移除 PID/IPC 命名空间、/proc 改为 bind 挂载、移除 mqueue 挂载): ```bash sudo tee /usr/local/bin/crun-wrapper > /dev/null <<'EOF' #!/bin/sh # crun wrapper for GKI kernel (Quectel Pi M1) bundle=""; prev="" for arg in "$@"; do [ "$prev" = "-b" -o "$prev" = "--bundle" ] && { bundle="$arg"; prev=""; continue; } case "$arg" in --bundle=*) bundle="${arg#--bundle=}";; -b|--bundle) prev="$arg";; esac done if [ -n "$bundle" ] && [ -f "$bundle/config.json" ]; then python3 - "$bundle/config.json" <<'PY' import json,sys c=json.load(open(sys.argv[1])) c['linux']['namespaces']=[n for n in c.get('linux',{}).get('namespaces',[]) if n.get('type') not in ('pid','ipc')] for m in c.get('mounts',[]): if m.get('destination')=='/proc' and m.get('type')=='proc': m['type']='bind'; m['source']='/proc'; m['options']=['rbind','rprivate'] c['mounts']=[m for m in c.get('mounts',[]) if 'mqueue' not in m.get('destination','')] if 'resources' in c.get('linux',{}): c['linux']['resources']={'devices':[]} json.dump(c,open(sys.argv[1],'w')) PY fi exec /usr/bin/crun --cgroup-manager=disabled "$@" EOF sudo chmod +x /usr/local/bin/crun-wrapper ``` ### 配置 Docker 守护进程 创建 `daemon.json`,将默认运行时切换为 crun 并禁用 iptables 初始化: ```bash sudo mkdir -p /home/dockeruser/.config/docker sudo tee /home/dockeruser/.config/docker/daemon.json > /dev/null <<'EOF' { "iptables": false, "ip6tables": false, "features": { "containerd-snapshotter": false }, "default-runtime": "crun", "runtimes": { "crun": { "runtimeType": "io.containerd.runc.v2", "options": { "BinaryName": "/usr/local/bin/crun-wrapper" } } } } EOF sudo chown -R dockeruser:dockeruser /home/dockeruser/.config ``` ### 安装并启动 rootless Docker ```bash sudo -u dockeruser dockerd-rootless-setuptool.sh install sudo loginctl enable-linger dockeruser # 开机自启 ``` ### 验证安装 ```bash export DOCKER_HOST=unix:///run/user/$(id -u dockeruser)/docker.sock docker version docker run --rm hello-world ``` 看到 "Hello from Docker!" 输出即表示安装成功。 ### 内网代理环境配置(可选) 开发板无法直连外网时,可通过宿主机代理拉取镜像。需关闭 host-loopback 限制并将代理指向 slirp 网络中的宿主机地址 `10.0.2.2`: ```bash sudo tee /home/dockeruser/.config/systemd/user/docker.service.d/override.conf > /dev/null <<'EOF' [Service] Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=false" Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_DISABLE_HOST_LOOPBACK=false" Environment="HTTP_PROXY=http://10.0.2.2:3128" Environment="HTTPS_PROXY=http://10.0.2.2:3128" EOF sudo chown -R dockeruser:dockeruser /home/dockeruser/.config sudo -u dockeruser systemctl --user daemon-reload sudo -u dockeruser systemctl --user restart docker ``` ## 功能使用 ### 镜像管理 ```bash docker pull nginx:latest # 拉取镜像 docker images # 查看本地镜像 docker rmi nginx:latest # 删除镜像 docker system prune -a # 清理未使用的镜像和容器 ``` ### 运行容器与端口映射 以 Nginx 为例,后台运行并映射端口: ```bash docker run -d --name web -p 8080:80 nginx:latest docker ps # 查看运行中的容器 docker logs web # 查看容器日志 docker exec -it web bash # 进入容器交互终端 ``` 启动后,在浏览器访问 `http://<开发板IP>:8080` 即可看到 Nginx 默认页面。 ### 数据持久化(数据卷) 容器被删除后,其中产生的数据会一并丢失。需要持久化的数据应通过卷(volume)挂载到宿主机目录: ```bash docker volume create myvolume docker run -d -v myvolume:/data myapp # 或挂载宿主机目录 mkdir -p ~/nginx-html docker run -d --name web -p 8080:80 -v ~/nginx-html:/usr/share/nginx/html nginx:latest ``` ### 构建自定义镜像 在项目目录创建 Dockerfile: ```dockerfile FROM python:3.13-slim WORKDIR /app COPY . . RUN pip install --no-cache-dir -r requirements.txt CMD ["python", "app.py"] ``` ```bash docker build -t myapp:v1 . docker run -d --name myapp -p 5000:5000 myapp:v1 ``` ### 使用 Docker Compose 编排多容器 创建 compose.yaml 文件: ```yaml services: web: image: nginx:latest ports: - "8080:80" volumes: - ~/nginx-html:/usr/share/nginx/html db: image: postgres:16 environment: POSTGRES_PASSWORD: example ``` ```bash docker compose up -d # 启动全部服务 docker compose ps # 查看服务状态 docker compose logs -f # 跟踪查看日志 docker compose down # 停止并删除服务 ``` ### 环境变量与资源限制 ```bash # 传递环境变量 docker run -d -e MYSQL_ROOT_PASSWORD=password mysql:5.7 # 限制内存与 CPU docker run -d --memory 256m --cpus 0.5 --name limited nginx:latest # 容器退出后自动重启(开机自启) docker run -d --restart unless-stopped --name web -p 8080:80 nginx:latest ``` ### 容器网络 连接到同一自定义网络的容器可通过容器名称相互通信: ```bash docker network create mynetwork docker network ls docker network inspect mynetwork ``` ### 其他常用命令 ```bash docker stop # 停止容器 docker rm # 删除容器 docker stats # 查看资源使用情况 docker inspect # 查看详细信息 docker cp 主机文件 :/容器路径 # 复制文件到容器 docker cp :/容器路径 主机路径 # 从容器复制文件 ``` ## 常见问题 ### dockerd 启动报 "Devices cgroup isn't mounted" 这是 GKI 内核缺少 devices cgroup 控制器的正常表现。本文档的 rootless 模式已绕过该限制,按第 3 章安装即可。 ### iptables 报 "Extension addrtype revision 0 not supported" 内核缺少 addrtype 匹配模块。本方案已在 daemon.json 中禁用 iptables 初始化规避;容器需出网时按 5.3 节在网络命名空间手动添加 NAT 规则。 ### 容器无法访问外网 本方案禁用了 iptables NAT,需在 dockerd 所在网络命名空间手动添加 MASQUERADE 规则: ```bash DPID=$(pgrep -u dockeruser -x dockerd) sudo nsenter -t $DPID -n iptables-legacy -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE ``` ### 执行 docker 命令提示权限不足 rootless 模式下需先设置 DOCKER_HOST 环境变量: ```bash export DOCKER_HOST=unix:///run/user/$(id -u dockeruser)/docker.sock ``` 可将其加入 `~/.bashrc` 避免每次手动设置。 ### 拉取镜像超时或失败 网络无法访问 Docker Hub 时,配置镜像加速或使用代理。内网环境参考 3.7 节;也可编辑 `daemon.json` 添加 `registry-mirrors`: ```json { "registry-mirrors": ["https://docker.m.daocloud.io"] } ``` ### 运行镜像报 exec format error 镜像架构与开发板不匹配。开发板为 ARM64,应选择 arm64 架构的镜像: ```bash docker pull --platform linux/arm64 镜像名 ``` ### 容器无法停止或删除 GKI 内核适配方案下,容器 stop/rm 偶发失效(无 PID 命名空间导致)。可先手动结束容器进程再删除: ```bash ps aux | grep -E "nginx|容器进程" | grep -v grep sudo kill -9 <进程PID> docker rm -f ``` ### 容器重启后数据丢失 容器本身是无状态的,数据应通过卷挂载到宿主机持久化(见 4.3 节)。 ### 已知限制 - 无 PID 命名空间:容器内与宿主共享进程空间,容器内 `ps` 会看到宿主进程; - 容器 stop/rm 偶发失效,需手动 kill(见 5.7 节); - 部分依赖 cgroup 资源限制的功能(如 --memory 严格隔离)不生效。