容器部署和使用指南¶
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 权限 |
环境确认¶
在终端中执行以下命令确认系统环境:
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 命名空间 |
无法创建容器独立的进程命名空间 |
可执行以下命令核对内核配置:
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)上实测通过。
安装依赖软件¶
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),创建一个专用用户并配置用户映射:
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 挂载):
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 初始化:
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¶
sudo -u dockeruser dockerd-rootless-setuptool.sh install
sudo loginctl enable-linger dockeruser # 开机自启
验证安装¶
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:
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
功能使用¶
镜像管理¶
docker pull nginx:latest # 拉取镜像
docker images # 查看本地镜像
docker rmi nginx:latest # 删除镜像
docker system prune -a # 清理未使用的镜像和容器
运行容器与端口映射¶
以 Nginx 为例,后台运行并映射端口:
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)挂载到宿主机目录:
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:
FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]
docker build -t myapp:v1 .
docker run -d --name myapp -p 5000:5000 myapp:v1
使用 Docker Compose 编排多容器¶
创建 compose.yaml 文件:
services:
web:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ~/nginx-html:/usr/share/nginx/html
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
docker compose up -d # 启动全部服务
docker compose ps # 查看服务状态
docker compose logs -f # 跟踪查看日志
docker compose down # 停止并删除服务
环境变量与资源限制¶
# 传递环境变量
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
容器网络¶
连接到同一自定义网络的容器可通过容器名称相互通信:
docker network create mynetwork
docker network ls
docker network inspect mynetwork
其他常用命令¶
docker stop <container_id> # 停止容器
docker rm <container_id> # 删除容器
docker stats # 查看资源使用情况
docker inspect <container_id> # 查看详细信息
docker cp 主机文件 <container_id>:/容器路径 # 复制文件到容器
docker cp <container_id>:/容器路径 主机路径 # 从容器复制文件
常见问题¶
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 规则:
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 环境变量:
export DOCKER_HOST=unix:///run/user/$(id -u dockeruser)/docker.sock
可将其加入 ~/.bashrc 避免每次手动设置。
拉取镜像超时或失败¶
网络无法访问 Docker Hub 时,配置镜像加速或使用代理。内网环境参考 3.7 节;也可编辑 daemon.json 添加 registry-mirrors:
{
"registry-mirrors": ["https://docker.m.daocloud.io"]
}
运行镜像报 exec format error¶
镜像架构与开发板不匹配。开发板为 ARM64,应选择 arm64 架构的镜像:
docker pull --platform linux/arm64 镜像名
容器无法停止或删除¶
GKI 内核适配方案下,容器 stop/rm 偶发失效(无 PID 命名空间导致)。可先手动结束容器进程再删除:
ps aux | grep -E "nginx|容器进程" | grep -v grep
sudo kill -9 <进程PID>
docker rm -f <container_id>
容器重启后数据丢失¶
容器本身是无状态的,数据应通过卷挂载到宿主机持久化(见 4.3 节)。
已知限制¶
无 PID 命名空间:容器内与宿主共享进程空间,容器内
ps会看到宿主进程;容器 stop/rm 偶发失效,需手动 kill(见 5.7 节);
部分依赖 cgroup 资源限制的功能(如 --memory 严格隔离)不生效。