资讯动态

树莓派 Docker 全家桶:从零到一键部署容器化服务器

发布时间:2026/9/17 5:59:25 来源:尧图企业网站定制
树莓派这个系列写到第 7 篇终于可以聊点省心的事了。前几篇里我分别在树莓派上折腾过系统初始化、网页服务、数据库、反向代理每一篇都是单独部署命令都是临时查、临时敲环境越装越乱。最崩溃的一次是想把整套服务迁移到另一张 SD 卡结果发现依赖分散得到处都是光重新装环境就花了一晚上。所以这篇我就讲清楚一件事如何把树莓派从“能用”变成“好迁移、好备份、一条命令拉起”的容器化服务器。这篇文章的核心思路是“纯净底座 Docker 全家桶”。我会从一张几乎空白的 Raspberry Pi OS Lite 开始装好 Docker Engine 和 Compose再把网页服务、数据库、缓存、可视化面板全部容器化最后整合成一套install.sh一键部署脚本。适合已经玩过树莓派、但每次部署都靠手工敲命令的朋友也适合想把树莓派当成家庭服务器长期稳定跑的人。看完你不仅能复制我的方案还能理解每个选择背后的原因以后自己加服务也不会抓瞎。1. 整体设计思路为什么要把 Docker 全家桶整合成一套方案1.1 散装部署的烦恼你大概率也遇到过在决定整合之前我树莓派上的软件是这样装的Nginx 用 apt 直接装、MySQL 手动编译装、Node.js 应用用 nohup 扔在后台、Redis 又是另一套配置。表面上都能跑但问题非常明显。第一系统升级不敢乱升怕某个依赖一变某个服务就起不来了第二配置分散在 /etc、/usr/local、/home 等多个目录备份都不知道该打包哪几个文件夹第三换一张 SD 卡等于所有服务全部重来完全没有可复现性。后来我意识到这种“直接在系统里装软件”的方式在树莓派这种小机器上其实很吃亏。树莓派用的是 ARM 架构很多软件在安装时要现场编译比如 MySQL 源码包编译一次一小时起步。一旦系统环境变化还得重新编译。容器化之后就不一样镜像里已经带好了运行环境我这边只需要拉下来跑不需要关心它内部装了什么依赖。1.2 为什么选 Docker 而不是其他容器方案树莓派生态里能选的容器方案主要有 Docker、Podman、LXC。LXC 更接近轻量虚拟机适合隔离不同系统环境但配置复杂镜像生态也远不如 Docker 丰富。Podman 主打无守护进程、更安全但树莓派上的相关教程和镜像资源相对少遇到问题不太好查。Docker 虽然总被人说“不够安全”但对于家庭服务器这种单机场景它的成熟度和生态就是最大优势。更重要的是 Docker 的 Compose 组织方式。以前我在系统里装三个服务要分别记住它们的启动命令、配置文件和开机自启方式。用 Compose 之后所有服务都写在一个docker-compose.yml文件里docker compose up -d一条命令全部搞定重启、停止、查看日志也都是统一命令。这种“声明式”的管理方式正是我想要的可复现性。1.3 全家桶选型哪些组件值得放进容器组件作用选择理由Docker Engine容器运行时生态最成熟ARM 镜像丰富Docker Compose Plugin多容器编排单文件管理所有服务迁移方便Nginx网页服务器 / 反向代理轻量、稳定支持静态站和反代MySQL主存储库网页服务最常用的数据库官方 arm64 镜像完善Redis缓存 / 会话存储提升动态页面响应速度PortainerDocker 可视化管理面板新手友好能直观看到容器状态Watchtower自动更新镜像可选省去手动拉新镜像的麻烦这里我想多说一句 Portainer。有些朋友觉得“都用命令行管理了搞可视化面板是不是多余”。我的看法是树莓派服务器跑起来之后你不会每天 SSH 进去敲docker ps看状态。Portainer 能在网页上直接看到每个容器的资源占用、日志、重启状态尤其适合放在家里长期跑的场景。就算是你不在家家人打开页面帮忙看下状态也方便。2. 核心细节解析这几个关键选择决定了后面顺不顺2.1 底座系统为什么是 Raspberry Pi OS Lite树莓派官方系统主要有带桌面版和 Lite 版。Docker 全家桶方案我强烈建议用 Lite 版也就是无桌面版。原因很实际桌面环境本身要占用 500MB 左右内存树莓派 4B 虽然内存有 4GB 或 8GB但容器服务堆起来之后内存永远不够用。桌面上的输入法、浏览器、文件管理器对服务器来说全是负担。Lite 版还有一个好处是稳定性。我测试过带桌面的系统长时间开机跑 Docker偶尔会因为桌面环境的某种崩溃导致网络不稳定。无桌面系统纯粹跑服务开机占用内存只有 100MB 左右剩下的全部给容器用。另外Lite 版默认没有乱七八糟的预装软件出问题排查起来干净很多。烧录工具用树莓派官方的 Raspberry Pi Imager 就行。烧录时有两个小功能我很喜欢一个是预配置 SSH一个是预配置 WiFi。把系统烧进 SD 卡之后不用插屏幕接键盘直接开机就能通过网络连上这对无桌面系统来说特别重要。2.2 Docker Engine 的安装路线官方脚本还是 apt树莓派上装 Docker 有两条主流路线一条是运行官方安装脚本curl -fsSL https://get.docker.com | sh另一条是用 apt 直接安装docker.io包。官方脚本的优势是安装了最新版本而且会自动帮你添加 Docker 官方软件源后续升级方便。缺点是脚本路径在网络上可能访问慢如果网络环境受限下载可能超时。apt 直接装docker.io的方式胜在省事但版本往往偏旧。我实测的时候apt 仓库里的docker.io版本比较老虽然基础功能没问题但老版本对新版 Compose 的支持不够好。我比较推荐的做法是优先跑官方脚本如果网络实在不给力就采用“添加 Docker 官方 apt 源后安装”。下面是添加软件源的方式# 安装依赖 sudo apt update sudo apt install -y ca-certificates curl gnupg # 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg # 添加 apt 软件源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 安装 Docker 全家桶 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin注意download.docker.com这个地址也可能访问不畅如果卡住了也别慌可以看看你的网络情况再决定用哪个镜像源。我这里不列具体镜像地址因为不同地区、不同网络环境差异很大最好根据你实际能访问的地址来选择。2.3 docker compose 还是 docker-compose别再用老命令了Docker Compose 的发展经历了两个阶段。早期的 Compose 是独立的 Python 工具安装后命令是docker-compose用短横线连接。后来 Docker 官方把 Compose 集成进 Docker CLI 插件命令变成了docker compose中间是空格。现在新装 Docker 之后官方插件docker-compose-plugin会让docker compose命令直接可用我们没必要再单独装老版本 Python 工具。用docker compose的好处是版本和 Docker Engine 一起管理不容易出现 Compose 文件格式版本不兼容的问题。比如老版本docker-compose可能不支持一些新版语法而docker compose插件会随 Docker 一起更新基本不会遇到这种坑。我在脚本里统一使用docker compose命令。如果你之前习惯了docker-compose建议尽快切换。实测中两个命令在语法上对绝大多数 Compose 文件都是兼容的但插件模式更省心。2.4 目录、权限、时区和重启策略这些细节才是稳定运行的底气部署容器不是光把镜像 run 起来就完事目录规划非常关键。我采用的是/opt/docker作为总目录下面按服务分子目录结构如下/opt/docker/ ├── docker-compose.yml ├── .env ├── nginx/ │ ├── conf.d/ │ └── html/ ├── mysql/ │ └── data/ ├── redis/ │ └── data/ ├── web/ │ └── app/ └── portainer/ └── data/这种结构的好处很直接备份时只需要打包/opt/docker这一个目录所有配置和数据都在里面迁移到新系统只需要把这个目录复制过去再执行一次docker compose up -d即可。时区是个容易被忽略的坑。容器默认使用 UTC 时间如果你在网页应用里记录日志或显示时间会出现“慢 8 小时”的问题。我在 Compose 文件里统一加了TZAsia/Shanghai环境变量数据库容器还会额外挂载宿主机的/etc/localtime文件保证容器和宿主机时间一致。重启策略我统一使用restart: unless-stopped。这个策略的意思是容器异常退出时自动重启除非你手动 stop 它。树莓派经常因为断电或者家庭网络波动导致服务中断有了这个策略服务能自动恢复不用每次手动拉起。3. 实操过程从烧录系统到一键拉起整套网页服务3.1 准备 SD 卡和基础系统这一步我默认你已经能完成基本烧录。打开 Raspberry Pi Imager选择 Raspberry Pi OS Lite存储卡选你的 SD 卡然后在设置里提前开启 SSH、配置好 WiFi 和用户名。烧录完成后把 SD 卡插进树莓派通电等一两分钟然后 SSH 连进去。建议烧录完成、找到树莓派 IP 之后第一时间手动执行一次系统更新sudo apt update sudo apt full-upgrade -y sudo reboot这一步很值得确保系统软件源和内核都是最新版后面装 Docker 时才不会因为系统太老导致奇怪的问题。3.2 编写一键部署脚本 install.sh我的目标是“一条命令完成整个环境搭建”。脚本里包含以下流程更新系统软件源安装 Docker 依赖添加 Docker 官方软件源并安装 Docker Engine、Compose 插件创建项目目录结构和.env环境变量文件将用户加入 docker 组启动 Docker 并设置开机自启拉取镜像并启动所有容器脚本内容如下#!/bin/bash set -e echo 更新系统软件源 sudo apt update sudo apt full-upgrade -y echo 安装 Docker 依赖 sudo apt install -y ca-certificates curl gnupg echo 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo 添加 Docker 软件源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null echo 安装 Docker Engine 与 Compose 插件 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin echo 设置 Docker 开机自启 sudo systemctl enable docker sudo systemctl start docker echo 将当前用户加入 docker 组 sudo usermod -aG docker $USER echo 创建项目目录 mkdir -p /opt/docker/web/app /opt/docker/nginx/html /opt/docker/mysql/data /opt/docker/redis/data /opt/docker/portainer/data echo 生成 .env 环境变量文件 if [ ! -f /opt/docker/.env ]; then cat /opt/docker/.env EOF TZAsia/Shanghai MYSQL_ROOT_PASSWORDmy-secret-pw MYSQL_DATABASEwebdb MYSQL_USERwebuser MYSQL_PASSWORDwebpass EOF fi echo 全部完成请注销重新登录后使用 docker compose 命令set -e非常关键脚本里任何一条命令失败都会立即终止避免在错误环境中继续往下跑省得后面排错时一头雾水。3.3 docker-compose.yml 全家桶配置在/opt/docker/docker-compose.yml里写入全家桶配置。这个文件是整个方案的灵魂我贴一个可以直接用的精简版services: nginx: image: nginx:stable container_name: nginx restart: unless-stopped ports: - 80:80 - 443:443 volumes: - ./nginx/html:/usr/share/nginx/html - ./nginx/conf.d:/etc/nginx/conf.d networks: - webnet depends_on: - web web: image: node:18-alpine container_name: web restart: unless-stopped working_dir: /app volumes: - ./web/app:/app command: sh -c npm install node server.js environment: - TZAsia/Shanghai networks: - webnet depends_on: - mysql - redis mysql: image: mysql:8.0 container_name: mysql restart: unless-stopped ports: - 3306:3306 environment: - TZAsia/Shanghai - MYSQL_ROOT_PASSWORD${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE${MYSQL_DATABASE} - MYSQL_USER${MYSQL_USER} - MYSQL_PASSWORD${MYSQL_PASSWORD} volumes: - ./mysql/data:/var/lib/mysql networks: - webnet redis: image: redis:7-alpine container_name: redis restart: unless-stopped ports: - 6379:6379 volumes: - ./redis/data:/data command: redis-server --appendonly yes networks: - webnet portainer: image: portainer/portainer-ce:latest container_name: portainer restart: unless-stopped ports: - 9000:9000 volumes: - /var/run/docker.sock:/var/run/docker.sock - ./portainer/data:/data networks: - webnet networks: webnet: driver: bridge注意到几个关键细节web服务我用的是node:18-alpine镜像并且把当前目录下的web/app挂载进去。这意味着你可以把任意 Node.js 网页应用代码放进/opt/docker/web/app容器启动时自动执行npm install并启动服务。开发阶段很方便改代码不用重新构建镜像。MySQL 的初始化数据全部放在./mysql/data卷中。就算容器被删掉数据还在宿主机磁盘上重新起一个容器数据不会丢。Redis 加了--appendonly yes开启持久化避免重启后缓存数据全部丢失。Portainer 直接把宿主机的 Docker 套接字/var/run/docker.sock挂载进容器这是管理 Docker 的常规做法。要注意这么做等于给了 Portainer 宿主机 Docker 的全部操作权限所以不要把它暴露到公网局域网用足够了。3.4 启动、验证与常用管理命令所有文件准备完之后执行一键启动cd /opt/docker docker compose pull docker compose up -dpull可以提前拉取镜像防止up时边拉边启动出现超时。等所有容器状态为Up后可以验证服务docker compose ps curl http://localhost:80如果 Nginx 默认页出现说明网页服务已经跑起来了。我也习惯用docker compose logs -f实时看所有容器日志排查启动阶段的问题。容器管理命令也顺手整理一下# 查看服务状态 docker compose ps # 查看某个服务日志 docker compose logs nginx # 进入容器内部 docker compose exec web sh # 重启某个服务 docker compose restart web # 整体停掉 docker compose down # 整体启起来保留数据卷 docker compose up -d需要特别提醒docker compose down不会删除数据卷。所以数据文件是安全的。但如果你手动加了-v参数即docker compose down -v那就连数据卷一起删掉这条命令我建议少用尤其在生产环境。第一次执行前可以先备份。3.5 用 Portainer 可视化接管所有容器浏览器打开http://树莓派IP:9000首次访问会要求设置管理员账号密码。设置完成后连接环境选择“本地 Docker”Portainer 会自动检测到/var/run/docker.sock然后你就能在网页上看到全部容器了。Portainer 上比较实用的功能有三个第一首页 Dashboard 能看到 CPU 和内存使用率方便你判断树莓派负载第二Containers 页面能看到每个容器的启动状态、端口映射、挂载卷如果某个容器一直重启可以直接点开看日志第三Stacks 功能支持粘贴 Compose 文件来创建整套服务等于把docker-compose.yml管理从命令行搬到了网页上。不过我的做法是Portainer 只用来观察真正的配置修改还是用命令行和 Compose 文件完成。因为 Compose 文件本身就是“基础设施即代码”一切以文件为准可版本化、可备份。如果只在网页上手动点来点去万一 Portainer 容器挂了你的“管理界面”也没了。4. 常见问题与排查技巧实录4.1 容器一直在 restart怎么定位树莓派上装 Docker 之后最常遇到的就是容器状态显示Restarting或者不断退出重启。这个时候不要慌先看日志docker compose logs --tail100 服务名日志里一般会直接给出原因比如端口被占用、配置文件语法错误、环境变量没设置好。排完日志后如果容器还是不停重启先把该服务停掉再查看详细错误docker compose stop 服务名 docker compose run --rm 服务名用run --rm以前台方式启动可以看到完整报错。这个方法比看日志更直接因为 Compose 的日志有时候会截断关键信息。4.2 镜像拉取超时或失败怎么办树莓派装 Docker 后的网络问题主要集中在镜像拉取上。常见的报错是net/http: TLS handshake timeout或EOF。这和你所在网络到 Docker Hub 的连通性有关工具本身没问题。我的处理经验是分几步走。先用docker pull重试几次有时候只是临时网络抖动。如果频繁失败就在/etc/docker/daemon.json中配置镜像加速地址。配置文件如下{ registry-mirrors: [https://你的加速地址] }改完之后执行sudo systemctl restart docker再重新拉取。如果你有稳定的内网镜像仓库也可以把地址填进去。需要提醒的是加速地址对一部分镜像有效对某些用户上传的镜像可能不生效这时候只能靠重试或者换时间段再试。4.3 MySQL 数据说丢就丢卷权限是隐形杀手有一次我把 MySQL 容器删了重新起结果发现原来的数据库全部没了。排查之后发现问题出在数据目录权限上。MySQL 8 官方镜像在容器内以mysql用户运行这个用户的 UID 是 999。如果宿主机上/opt/docker/mysql/data目录的属主不是 UID 999MySQL 就无法写入数据文件初始化阶段就会失败。解决办法有两种。一种是在宿主机上手动改目录属主sudo chown -R 999:999 /opt/docker/mysql/data另一种更稳妥先把目录变成可写状态让容器自己处理权限问题。不管用哪种方式解决后删掉旧的 MySQL 容器重新创建数据才能正常初始化。4.4 重启之后容器没自启别怀疑镜像树莓派断电重启后如果发现 Docker 容器没有全部起来先看 Docker 服务本身是否启动systemctl status docker如果 Docker 服务正常再看容器启动策略docker inspect --format{{.Name}} - {{.HostConfig.RestartPolicy.Name}} $(docker ps -aq)如果某个容器的重启策略不是unless-stopped或always执行一条命令更新docker update --restart unless-stopped 容器名另外有个小坑如果你在 Compose 文件里写了restart: unless-stopped但执行docker compose up时容器已经被手动 stop 过那么重启后它不会自动起来。这是 Docker 的设计逻辑手动 stop 的容器不会自动启动。解决办法是再执行一次docker compose start。4.5 端口被占用怎么快速找出来启动 Nginx 时如果报bind: address already in use说明 80 端口已经被占用。宿主机上直接查端口占用sudo ss -lntp | grep :80输出里能看到是哪个进程占用了端口。如果是树莓派系统自带的 Apache 或者其他服务在占端口直接停掉并禁用sudo systemctl stop apache2 sudo systemctl disable apache2如果端口是容器占用的可以看docker ps里的端口映射列。多容器之间不要重复映射同一个宿主机端口。比如你在宿主机上映射了3306:3306那么其他容器内部的 3306 端口可以随便用但不能再有一个宿主机3306的映射。4.6 树莓派温度过高导致性能骤降容器跑起来之后树莓派的负载会明显上升。一般 CPU 持续偏高时外壳就会发热。树莓派有温度保护机制超过 80°C 后会自动降频表现就是“服务突然变卡”。想看当前温度执行vcgencmd measure_temp如果长期接近 80°C建议加散热片和风扇。我实测同样一套 Compose 全家桶裸跑和加强制风冷散热最高温度能差十几度。服务器放在封闭弱电箱里的朋友要格外注意通风。软件层面能做的就是限制容器 CPU 占用在 Compose 配置里加services: web: deploy: resources: limits: cpus: 1.0 memory: 512M这样能防止某个失控容器把整台树莓派资源吃光。不过deploy关键字在普通docker compose up下的资源限制严格度有限更精确的限制建议用docker run --cpus --memory参数或者升级到 Docker Swarm / K3s 模式这里不细说了。4.7 之后想加新服务怎么扩展全家桶这套方案最大的价值就在扩展性。想加一个静态博客新建一个目录把 HTML 文件放进去然后在 Compose 文件里加一个服务或者直接挂到 Nginx 容器里映射一个新的 server 块。想加一个 Python API仿照web服务写一段api: image: python:3.11-slim container_name: api restart: unless-stopped working_dir: /app volumes: - ./api:/app command: sh -c pip install -r requirements.txt python main.py environment: - TZAsia/Shanghai networks: - webnet然后docker compose up -d。加服务几乎不影响现有服务Compose 会算出增量变化只启动新增的服务。备份整个环境也很简单就是打包目录加上导出镜像列表tar czf docker_backup_$(date %F).tar.gz /opt/docker docker ps --format {{.Image}} | sort -u images.txt恢复的时候新系统装好 Docker解压备份目录再按images.txt重新拉取镜像最后docker compose up -d就全回来了。这个流程我跑过一遍整体时间比当初手动部署缩短了大概八成。最后提一个我在实际使用中的体会Docker 全家桶整合不只是把软件装进容器这么简单它真正改变的是你对服务器的管理方式。以前我会关心“某个服务怎么装、怎么启动”现在只关心“这个服务的数据放哪、端口怎么暴露”。一旦把问题抽象到这种程度换机器、加服务、迁移数据都变成了复制文件和改几行配置的事树莓派也终于能安安静静地待在角落里当一台家里真正可用的服务器了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价