首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
树莓派 Docker 全家桶:从零到一键部署的完整指南
📅 2026/9/17 21:14:27
✍️ 爱科研究院
👁 阅读 3,247
树莓派加鱼或者说加容器这件事我前前后后写过好几篇。从最初跑个静态网页到后面把数据库、缓存、反向代理都塞进 Docker再到现在终于到了整顿“战场”的时候。如果你也是那种把树莓派当家庭服务器用的人大概率会经历同样一个阶段容器越跑越多命令全靠脑子记配置散落在各个 ssh 历史里换一张 SD 卡就想重新做人。这篇是系列的第七篇不再像之前那样单个服务手把手跑而是把所有该有的服务整合成一套 Docker 全家桶从一个纯净的树莓派系统开始到最后用一条命令全部拉起来。适合已经玩过 Docker 基础操作、想把自己的派从“能用”升级成“稳定好用”的人。先说清楚这套方案解决的核心问题第一容器之间网络互通不再靠 IP 乱猜第二所有服务的数据、配置、日志全部归到统一目录迁移和备份有迹可循第三一键部署脚本保证哪怕系统重装也能快速恢复到可用状态。后面所有内容都是围绕这三点展开的。1. 项目规划为什么要把零散容器“全家桶化”1.1 散装 Docker 的痛踩过的都懂前几篇里我们基本是出一个服务就用一个docker run把容器拉起来。单个容器没什么问题但数量上来了麻烦就跟着来。比如我当时的树莓派上跑着 Nginx、MariaDB、Redis、Portainer、一个 Python 写的定时任务脚本还有一个 Node-RED 用于在家里折腾自动化。问题主要体现在几个方面docker run的参数太长换机器、换 SD 卡的时候只能靠翻历史命令回忆少一个-v数据就丢一次。容器启动顺序要靠手工控制树莓派开机自启后数据库没起来Nginx 先起来了应用连不上数据库还得手动一个个 restart。容器间的网络默认是 bridge 模式每个容器有自己的 IP但重启后 IP 会变。应用配置里写死 IP 的做法会随着时间慢慢变成一场灾难。数据、配置、日志散落在/opt、/home、/var/lib/docker各处备份的时候根本不知道该打包哪些目录。一句话总结散装 Docker 只适合测试环境不适合当家庭服务器底座。全家桶整合本质上就是给这些容器立规矩。1.2 服务选型树莓派能跑多少东西心里要有数在做全家桶整合前我先给手上的树莓派 4B8GB 版本算了笔账。8GB 内存对于跑网页服务器来说相当宽裕但 CPU 是四核 A72性能也就那样。所以我的选型原则是优先选官方镜像优先选 Alpine 为基础的小镜像优先选 arm64 版本那些动不动就拉一个几百 MB 全家桶镜像的服务直接放弃。这一套整合里最终选择了这些服务服务镜像用途外部端口备注webnginx:stable-alpine静态站点与反向代理80/443核心入口dbmysql:8.0主数据库不直接暴露仅内网访问cacheredis:7-alpine缓存与队列不直接暴露仅内网访问managerportainer/portainer-ceDocker 可视化管理9000网页管理updatercontainrrr/watchtower自动更新基础容器不暴露排除数据库镜像apppython:3.12-alpine自研小应用8080示例业务服务这里有个很关键的取舍MySQL 和 Redis 的端口不让外部访问。如果你的树莓派只在家里内网用其实连反向代理里也只需要暴露 80/443 就够了。数据库端口一旦暴露到公网哪怕是内网都等于给扫描器送人头。应用要连数据库走的是 Docker 内部网络用服务名解析根本不需要端口映射。1.3 目录规划所有数据放在一个篮子里这套全家桶的目录布局我直接写死/srv/docker/ ├── compose/ # docker-compose.yml 文件和部署脚本 ├── web/ # nginx 配置、站点文件 ├── db/ # MySQL 数据文件 ├── cache/ # Redis 持久化数据 ├── portainer/ # Portainer 数据 ├── app/ # 自研应用代码和配置 └── backups/ # 备份脚本输出目录所有容器挂载路径都指向/srv/docker下的子目录。这么做的理由很简单备份的时候只需要打包这一个目录恢复的时候也只需要还原这一个目录。后面的部署脚本、迁移步骤都围绕这个设计来展开。2. 从纯净底座开始系统初始化与 Docker 环境准备2.1 树莓派系统的选择与最小化安装要搞全家桶系统层面我不建议装桌面版。桌面环境会占用内存还会引入不必要的服务和容器抢资源。我选的是 Raspberry Pi OS Lite64 位也就是无桌面版。系统安装这块多数人踩的坑集中在烧录工具上。树莓派官方出的 Raspberry Pi Imager 已经很好用了但很多人不知道它有几个很实用的隐藏功能在烧录前点击右下角的齿轮图标可以提前设置 SSH 开启、WiFi 连接、用户账号密码。这一点非常重要尤其是你想无屏幕安装的时候烧录完直接插电开机SSH 就能连上省掉了接显示器配键盘的麻烦。我当时的操作流程是这样的用 Raspberry Pi Imager 烧录 Raspberry Pi OS Lite64-bit到 SD 卡。在烧录设置里填好 hostname 为rpi-server开启 SSH设置好用户pi的密码。烧录完成后插回树莓派网线连接路由器开机。在路由器后台找到树莓派的 IP或者直接用arp -a扫然后用 SSH 连上去。连上之后不要急着装 Docker先把系统基础配置搞定。我通常会执行一轮更新sudo apt update sudo apt upgrade -y然后关掉不需要的服务比如蓝牙如果你的项目完全用不到减少干扰sudo systemctl disable bluetooth sudo systemctl disable hciuart顺手把时区设置成东八区否则后面容器里的日志时间会乱套sudo timedatectl set-timezone Asia/Shanghai更换 apt 软件源这种操作各人的网络环境不一样自己按需处理但注意换完之后再执行一次sudo apt update确认能够正常拉取索引。2.2 安装 Docker 与 Compose 插件的正确姿势树莓派上装 Docker最常见的方法是直接执行官方安装脚本curl -fsSL https://get.docker.com | sh这一步会把 Docker Engine 和 containerd 都装好也会顺手装上 compose 插件。装完后记得把当前用户加进 docker 组否则每条命令都要加 sudosudo usermod -aG docker $USER newgrp docker然后设置开机自启并确认状态sudo systemctl enable docker sudo systemctl start docker docker version这个环节要强调一点树莓派的 arm 架构上不存在可用的 Docker Desktop。热搜里经常有人搜“docker desktop failed to start”那基本是 Windows 或者 macOS 上的问题跟树莓派无关。树莓派上装的是纯命令行的 Docker Engine远程管理靠 Portainer 这类工具实现使用体验上完全够用。装好之后检查一下 compose 插件是否可用docker compose version如果显示Docker Compose version v2.x.x那就到位了。接下来所有编排工作都围绕docker compose命令展开。2.3 SD 卡与系统参数的提前优化树莓派用 SD 卡做存储这是整个系统最大的短板。Docker 的日志写入、数据库的随机读写对 SD 卡都是不小的损耗。为了延长 SD 卡寿命我做了两个优化。第一个给 Docker 配置日志轮转。新建或编辑/etc/docker/daemon.json{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }改完重启 Dockersudo systemctl restart docker。这样每个容器的日志最多占 30MB 左右不会出现日志把 SD 卡写满的情况。第二个调整 swap 策略。树莓派默认的 swap 文件在 SD 卡上频繁换页会导致大量写入。我在树莓派上限制了容器内存这样即使某个容器出现内存泄露也不会把 swap 打爆。限制方式可以在 compose 文件里给每个服务加上mem_limit这个后面会说。3. 核心环节用 Docker Compose 编排全家桶3.1 服务间的网络规划全家桶的核心是网络编排。Docker Compose 会默认创建一个自定义 bridge 网络所有属于同一个 compose 文件的服务都能通过服务名互相访问。这个特性直接把“容器 IP 变了导致应用连不上数据库”的问题从根上消灭了。我的 compose 文件里会显式定义一个网络networks: webnet: driver: bridge然后在每个服务下面都声明加入这个网络。比如 PHP 应用要连 MySQL只需要在数据库配置里写hostdb和port3306不需要知道 MySQL 容器实际 IP 是什么。端口规划同样重要。80 和 443 是 Nginx 的9000 给 Portainer 管理界面8080 给业务应用。其余服务一律不映射到宿主机只在内网通信时通过服务名访问。这样做的好处是你在路由器里只需要给树莓派开 80/443 两个端口做端口转发就够了。3.2 编写 docker-compose.yml一份可以直接上手的全家桶以下是我的docker-compose.yml的核心结构我做了简化处理但保留生产可用的关键配置services: web: image: nginx:stable-alpine container_name: web restart: always ports: - 80:80 - 443:443 volumes: - /srv/docker/web/conf:/etc/nginx/conf.d:ro - /srv/docker/web/html:/usr/share/nginx/html:ro - /srv/docker/web/cert:/etc/nginx/cert:ro - /srv/docker/web/log:/var/log/nginx depends_on: app: condition: service_healthy networks: - webnet db: image: mysql:8.0 container_name: db restart: always volumes: - /srv/docker/db/data:/var/lib/mysql - /srv/docker/db/conf:/etc/mysql/conf.d:ro environment: MYSQL_ROOT_PASSWORD: change_me_root_password MYSQL_DATABASE: webapp MYSQL_USER: webapp MYSQL_PASSWORD: change_me_app_password TZ: Asia/Shanghai command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5 networks: - webnet cache: image: redis:7-alpine container_name: cache restart: always volumes: - /srv/docker/cache/data:/data command: redis-server --appendonly yes healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 networks: - webnet app: image: python:3.12-alpine container_name: app restart: always ports: - 8080:8080 volumes: - /srv/docker/app:/app working_dir: /app command: python app.py depends_on: db: condition: service_healthy cache: condition: service_healthy networks: - webnet manager: image: portainer/portainer-ce container_name: manager restart: always ports: - 9000:9000 volumes: - /var/run/docker.sock:/var/run/docker.sock - /srv/docker/portainer/data:/data networks: - webnet updater: image: containrrr/watchtower container_name: updater restart: always volumes: - /var/run/docker.sock:/var/run/docker.sock command: --schedule 0 4 * * * --cleanup --label-enable networks: - webnet networks: webnet: driver: bridge这份文件里几个细节值得展开说说。depends_on配合condition: service_healthy是核心技巧。以前我们写depends_on只控制启动顺序不控制依赖状态数据库容器起来了但 MySQL 还没初始化完应用照样连不上。加上 healthcheck 之后compose 会等依赖服务“健康”了再启动下一个服务这个机制比单纯 sleep 几秒可靠得多。MySQL 的环境变量和command一起用保证数据库字符集是 utf8mb4避免中文乱码。MySQL 8.0 的镜像默认字符集不是 utf8mb4我在这上面吃过亏早点配上省心。Watchtower 加上--label-enable之后并不会自动更新所有容器只会更新打了com.centurylinklabs.watchtower.enabletrue标签的容器。我在 compose 文件里只有web和app打了这个标签数据库、Portainer 这类不能随便更新的服务选择手动管理。自动更新数据库镜像的风险太大万一遇到不兼容的版本数据坏了哭都来不及。3.3 给 Web 服务配上可用的 Nginx 反向代理配置全家桶里的 Nginx 不只是托管静态页面还要负责把请求转发给内部的服务。举个例子当访问https://your.domain/api/时Nginx 把请求转发给http://app:8080。在/srv/docker/web/conf下建一个app.confserver { listen 80; server_name your.domain; location / { proxy_pass http://app:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这里最关键的是proxy_pass http://app:8080这个app就是 compose 文件里的服务名Docker 内部 DNS 会自动解析到对应容器的 IP。换一个环境跑只要服务名不变配置永远不用改。修改完 Nginx 配置后需要重新加载docker exec web nginx -t docker exec web nginx -s reload先测试再 reload是个好习惯否则语法错误会让 Nginx 直接拒绝启动。3.4 启动与校验第一次拉起全家桶一切就绪后进入 compose 文件所在目录执行docker compose up -d-d参数表示后台运行。第一次执行会拉镜像树莓派上下载速度取决于网络环境耐心等就行。启动完成后用下面的命令查看所有容器状态docker compose ps正常情况下所有服务的STATUS列都是Up或者Up (healthy)。如果某个容器反复重启用docker compose logs 服务名看日志排查。到这里整个环境其实已经跑起来了。但手工执行docker compose up -d还谈不上“一键部署”因为系统重装之后你要重新创建目录、配置文件、环境变量这些东西散在博文各处复现成本还是太高。下一步我们把它全部自动化。4. 一键部署把整个流程封装成脚本4.1 脚本设计思路与骨架一键部署脚本的定位是不管这台树莓派是不是刚装好系统只要满足“Docker 已安装”这个前提运行一条命令就能把整个全家桶跑起来。设计上我要求脚本具备几个特性幂等性重复执行不会产生副作用已存在的容器不会重复创建。健壮性每一步都检查上一步的结果出错立即退出给出明确提示。可维护性目录结构、镜像列表、端口映射全部收敛在脚本里改了不用到处找。脚本我放在/srv/docker/compose/deploy.sh和docker-compose.yml放一起。核心骨架如下#!/usr/bin/env bash set -euo pipefail BASE_DIR/srv/docker COMPOSE_FILE${BASE_DIR}/compose/docker-compose.yml STACK_NAMEhome GREEN\033[0;32m RED\033[0;31m NC\033[0m log() { echo -e ${GREEN}[INFO]${NC} $1 } err() { echo -e ${RED}[ERROR]${NC} $1 exit 1 } precheck() { command -v docker /dev/null 21 || err Docker 未安装请先安装 Docker docker compose version /dev/null 21 || err Docker Compose 插件不可用 [ -f $COMPOSE_FILE ] || err 未找到 docker-compose.yml df -h $BASE_DIR | tail -1 | awk {print $4} | grep -qE ^[0-9.]*[MG] || err 磁盘空间不足 log 环境检查通过 } mk_dirs() { for dir in web/conf web/html web/cert web/log db/data db/conf cache/data portainer/data app backups; do mkdir -p $BASE_DIR/$dir done log 目录创建完成 } do_up() { precheck mk_dirs docker compose -f $COMPOSE_FILE up -d --remove-orphans docker compose -f $COMPOSE_FILE ps log 全家桶启动完成 } do_down() { docker compose -f $COMPOSE_FILE down log 全家桶已停止 } do_restart() { docker compose -f $COMPOSE_FILE restart log 全家桶已重启 } do_status() { docker compose -f $COMPOSE_FILE ps } do_logs() { docker compose -f $COMPOSE_FILE logs -f --tail200 ${2:-} } do_clean() { docker system prune -f docker image prune -f log 无用的容器和镜像已清理 } usage() { cat EOF 用法: $0 {up|down|restart|status|logs|clean} up 部署/启动全家桶 down 停止并移除容器 restart 重启所有容器 status 查看容器状态 logs 查看日志可指定服务名如 $0 logs db clean 清理无用 Docker 资源 EOF } case ${1:-} in up) do_up ;; down) do_down ;; restart) do_restart ;; status) do_status ;; logs) do_logs $ ;; clean) do_clean ;; *) usage exit 1 ;; esac脚本开头加了set -euo pipefail这三个参数组合起来的效果是只要任何一条命令失败脚本立即退出不继续往下跑避免带病部署。变量未定义也会直接报错这对排查问题很有帮助。precheck里做了几件事检查 Docker 命令是否存在、compose 插件是否可用、compose 文件是否存在、磁盘剩余空间是否够用。最后一行磁盘检查虽然简单但很实用——树莓派 SD 卡空间一旦写满整个数据库就挂了这个教训我确实有过。4.2 首次部署体验脚本写好后给可执行权限chmod x /srv/docker/compose/deploy.sh然后在任意目录执行sudo /srv/docker/compose/deploy.sh up正常情况下会看到日志输出提示环境检查通过、目录创建完成然后就是 compose 拉镜像、启动容器。执行完成后用一个小命令验证所有服务是否健康/srv/docker/compose/deploy.sh status看到所有服务都是Up或者Up (healthy)说明这次部署成功了。从这一刻起“一键部署”才真正成立。以后无论树莓派是系统崩溃需要重装还是换一台新硬件我只需要刷系统、装 Docker、把/srv/docker目录整体拷过去、执行一次deploy.sh up。一个晚上就能恢复到原来的状态。4.3 日常维护的命令封装deploy.sh除了部署还承担了日常维护的职责。我觉得最常用的是logs和clean。排查问题时./deploy.sh logs db可以实时盯数据库日志。./deploy.sh clean会清理掉所有 dangling 镜像和停止的容器每次用完一些临时容器后执行一次能避免 SD 卡空间被慢慢蚕食。还有一个细节我习惯把deploy.sh加一个软链接到/usr/local/bin/homectl这样平时维护只需要敲homectl status homectl logs app命令短心态也轻松。5. 树莓派 Docker 全家桶的常见问题与避坑实录5.1 镜像跑不起来平台架构不匹配树莓派上最经典的问题就是拉了一个 x86_64 架构的镜像结果容器启动直接报exec format error。原因很简单树莓派的 CPU 是 arm64而镜像只提供了 amd64 版本。碰到这个问题第一反应是去 Docker Hub 看镜像的 tags找到带arm64或者aarch64的版本。绝大多数主流软件的镜像都已经支持多架构直接拉默认 latest 一般没问题但一些冷门镜像或者老版本镜像就需要手动指定平台。还有一个验证技巧拉完镜像后用docker inspect查看架构docker inspect 镜像名 | grep Architecture看到arm64才是对的。5.2 MySQL 启动失败权限和内存是关键MySQL 8.0 在树莓派上启动失败通常有两个原因。第一是数据目录权限问题。如果你挂载的宿主机目录/srv/docker/db/data的属主不是 999MySQL 容器内的用户 UIDMySQL 会拒绝启动。解法很直接sudo mkdir -p /srv/docker/db/data sudo chown -R 999:999 /srv/docker/db/data第二是内存不足。树莓派 4B 的 2GB 版本跑 MySQL 8.0 确实捉襟见肘MySQL 8.0 默认配置的 buffer pool 开得很大。我自己的做法是在 compose 文件里加mem_limit: 512m同时给 MySQL 加配置压低内存占用。在/srv/docker/db/conf/my.cnf里写[mysqld] performance_schema OFF innodb_buffer_pool_size 128M innodb_log_buffer_size 8M当然如果你用的是树莓派 5内存普遍 8GB 起步这套限制可以适度放宽。5.3 容器内时间不准容器默认使用 UTC 时区日志时间跟宿主机差 8 个小时排查问题时非常别扭。解决方案有三个层级在 compose 文件的environment里配置TZ: Asia/Shanghai大部分官方镜像会遵守。如果基础镜像没有内置 tzdata需要进容器安装或者用挂载/etc/localtime的方式/etc/localtime:/etc/localtime:ro。最彻底的方式是在docker-compose.yml顶层加services: app: environment: TZ: Asia/Shanghai我通常在 compose 服务定义里统一加上TZ: Asia/Shanghai环境变量简单有效。5.4 HTTPS 证书更新与 Nginx 配置热加载如果挂载了域名和证书每三个月要换一次证书这是家庭服务器绕不开的运维工作。我的方案是证书签发脚本比如 acme.sh 或者 certbot跑在宿主机上生成证书到/srv/docker/web/cert然后每次续期完成后执行一次docker exec web nginx -s reload。需要注意certbot 的 webroot 方式需要 Nginx 能访问到验证目录但只要 Nginx 配置正确、挂载目录正确这个问题不难解决。5.5 忘备份导致的数据事故最后聊一个我用真实教训换来的经验。以前我总觉得树莓派上的数据没那么重要直到有一次 SD 卡突然损坏所有容器配置、数据库数据全部没救回来。从那以后我把“备份”写进了全家桶的默认动作里。备份方案很简单用tar打包/srv/docker下所有数据目录tar -czvf /srv/docker/backups/home_$(date %F).tar.gz /srv/docker数据库部分还可以用mysqldump单独导出方便单独恢复。把这些命令拼成一个backup.sh加进 crontab 每周执行一次心里就有底了。备份的目的不是为了防止树莓派坏掉而是为了防止自己哪天手贱改错配置后还能回到之前能用的状态。这套全家桶整合方案走到这里算是真正把树莓派变成了一个可维护的家庭服务器底座。从系统重装后的一键恢复到日常体检和日志排查大多数操作变成了固定动作。后面我还打算接入一些自动化的监控告警比如容器健康状态异常时直接推消息到手机但那是另一个话题了。如果你也正准备把手里的树莓派整理成这套样子建议从目录规划和 compose 文件下手先把结构立好再慢慢往里加服务。结构对了后面所有的优化都是锦上添花。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/17 21:14:27
VS Code C++插件ipch缓存清理与迁移指南
2026/9/17 21:14:27
Cursor AI编程工具实战指南:从安装配置到高效工作流
2026/9/17 21:14:27
Anomalib 中的 DSR 双 subspace 重投影模型:量化特征、三阶段训练与异常分割实现详解
2026/9/17 21:54:35
VSCode 自动更新后版本降级:多安装源与 PATH 排查修复
2026/9/17 21:54:35
D* Lite与横向避障算法在UGV路径规划中的实践
2026/9/17 21:54:35
Genkit Vertex AI 插件完全指南:Model Garden、Rerankers、Evaluation 与 Vector Search 实战
2026/9/17 21:54:35
JVM性能分析工具实战指南与调优技巧
2026/9/17 21:54:35
编译原理第四章:自上而下语法分析核心考点与LL(1)实战
2026/9/17 21:49:35
BUUCTF BabySQL:双写绕过黑名单与联合查询注入
2026/9/17 0:00:44
开学论文写作指南:核心框架梳理与高效完成技巧分享
2026/9/17 0:00:44
OpenMAIC:轻量级多Agent教学框架实战指南
2026/9/17 0:00:44
AWS无服务器应用开发指南:从Lambda到SAM的架构与实践
2026/9/16 18:36:59
拯救者Y7000黑屏故障排查与维修实战指南
2026/9/16 7:38:03
AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
2026/9/17 4:19:54
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化