1. 项目整体设计与思路拆解
1.1 这套方案解决什么核心问题
先说清楚一件事:树莓派 Docker 化网页服务器,本质上不是新技术,而是把“低功耗迷你主机 + 容器化编排”这两个已经很成熟的思路,组合到一块巴掌大的板子上。
我最早用树莓派跑网页服务,是裸机方案:系统里直接装 Nginx、PHP-FPM、MySQL,再手动配开机启动。前期折腾系统依赖、版本冲突、路径权限,后期每次换机器或者换服务,都要把同样的环境搭建流程再走一遍。中间经历过一次 SD 卡崩溃,系统整个起不来,配置文件、网站数据全得靠备份恢复,那几天的感受就是:这个方案能跑,但不够“稳”。
后来迁移到 Docker 之后,整个体验完全变了。网页服务器变成一个镜像,Nginx、PHP、数据库各自是独立容器,数据目录单独挂载,配置文件通过映射文件管理。换机器的时候,把 compose 文件和挂载目录拷过去,docker compose up -d一条命令就能把整套环境拉起来。这才是这套方案真正的核心价值:环境可复现、故障可恢复、服务可迁移。
文章写成“专栏导览”的定位,也是因为这块内容可以拆成很多期。从硬件准备、系统初始化、Docker 安装,到 Nginx 部署、反向代理、数据持久化、日志处理、安全加固,每一块单独拿出来都值得写一写。这篇作为开篇,先把整套骨架搭好,后面每个方向都可以在这个基础上续起来。
1.2 硬件选型与角色定位
树莓派型号选择,直接影响这套方案的体验上限。
我自己试过的组合里,树莓派 3B+ 也能跑 Docker,只是内存只有 1GB,跑一个 Nginx 加一个轻量数据库就有点吃紧,编译镜像或者跑稍微重一点的容器时卡顿明显。4B 是综合体验最好的选择:2GB 内存版本跑基础网页服务没问题,4GB 版本就比较从容,可以同时跑 Nginx、MySQL、Redis 这类组合。5 代的话性能更强,SD 卡或 NVMe 的读写都更好,但价格也高出不少。
如果你手头已经有树莓派,不用纠结“必须用最新型号”。网页服务器本身是 I/O 密集型应用,瓶颈一般在网络带宽、磁盘读写和内存容量,CPU 反而不是最紧张的部分。我实际测试过 4B 跑一个带数据库的 WordPress 站点,日访问量在几百到一千 IP 的范围内完全扛得住。真正需要注意的是内存:Docker 容器本身有开销,加上 Nginx、PHP-FPM 的进程池,2GB 内存是起步线,4GB 更稳妥。
存储方面,强烈建议优先用 SSD 移动硬盘或者 NVMe HAT,而不是一直依赖 SD 卡。SD 卡的问题我在后面会专门讲,这里先给结论:容器化方案下日志和数据的写放大明显,普通 SD 卡的寿命消耗比裸机方案快得多,做好数据外置是长期稳定运行的前提。
1.3 为什么用容器而不是裸机
很多人的第一反应是:树莓派性能有限,再用 Docker 套一层,会不会很浪费?
我一开始也有这个疑问,实测之后发现这个担心基本不成立。Docker 不是虚拟机,它没有完整的客户机操作系统开销,容器进程直接跑在宿主机内核上,多出来的只是镜像分层、命名空间隔离这些轻量机制。对 Nginx 这种性能敏感的进程来说,容器化和裸机运行之间的性能差距通常在几个百分点以内,日常使用根本感知不到。
更重要的收益在这几个方面:
- 环境隔离:一个容器挂了,不会把整个系统拖垮。比如某个 PHP 应用出了内存泄漏,最多杀掉那个容器,Nginx 和其他服务还能继续响应。
- 依赖自治:不同的网站需要不同的 PHP 版本、不同的扩展,容器化之后各装各的,互不干扰。裸机方案想同时跑两套 PHP 版本,配置起来很痛苦。
- 部署可复制:compose 文件就是环境清单,配合数据目录,整个服务集群可以被完整复制到另一台机器。
- 回滚简单:镜像版本固定,更新出问题直接切回旧镜像,一条命令的事。
成本方面,容器镜像确实会占一部分磁盘空间,但树莓派配合一块 120GB SSD,跑十来个容器镜像绰绰有余。这个账怎么算都是划算的。
2. 树莓派系统基础准备
2.1 系统镜像选择与烧录要点
Docker 化的第一步,是先有一块干净、稳定的系统。
我推荐用树莓派官方的 Raspberry Pi OS Lite 版本,也就是不带桌面环境的精简版。跑服务器不需要图形界面,Lite 版省掉了桌面、浏览器、办公套件这些完全用不到的东西,系统占用更少,更新范围也更小,长期运行更省心。
如果你对系统比较熟,Ubuntu Server 也可以,特别是后续想跑一些需要新内核特性的容器时,Ubuntu 的内核版本通常会新一些。但默认我还是建议原生系统,兼容性最好,社区资料最多,遇到问题查答案最容易。
烧录工具直接用官方 Raspberry Pi Imager 就行。这里有几个细节值得注意:
- 选择镜像后,Imager 支持直接配置 SSH、Wi-Fi、用户名密码,不用像以前那样手动创建 ssh 空文件来开启服务。在烧录前就把这些设置好,插卡通电就能直接连接。
- 如果要用 SSD 启动,先把引导器更新到支持 USB 启动的版本,再把系统烧录到 SSD。树莓派 4B 之后的版本对 USB 启动支持已经很成熟。
- 烧录完成后不要急着拔卡,先把 SD 卡或 SSD 重新挂载,检查一下 config.txt 里的配置。如果用的是树莓派 5,建议确认供电是否足够,很多莫名重启的故障都和电源有关。
2.2 SSH 连接与无屏初始化
树莓派当服务器用,基本都是无屏操作。
通电之前,你要确保自己有办法连上它。最简单的做法是路由器上查树莓派的 IP 地址,然后通过 SSH 连接。如果路由器管理界面不方便看,也可以用 nmap 扫描局域网内开放 22 端口的设备,或者用 mDNS 协议直接通过树莓派主机名.local连接。
我这里说一个我自己常用的技巧:给树莓派设置静态 IP 或者 DHCP 保留地址。服务器和开发板不一样,开发板是临时调试,IP变了就再查一次;服务器是持续提供服务的,IP 一旦变化,所有端口映射、地址转发、定时任务全部失效。在路由器里给树莓派的 MAC 地址绑定一个固定 IP,看起来是小事,实际上能避免后面一大半的“连不上”问题。
连接上之后,第一时间改密码、更新系统。不要跳过这一步,树莓派默认用户名的密码如果还是初始状态,暴露在局域网里风险很大。
2.3 系统更新与源优化
系统装完,先做一次完整的更新:
sudo apt update sudo apt full-upgrade -y sudo reboot这一步把内核、驱动、基础工具都同步到最新,后面的 Docker 安装会更顺利。
如果你觉得官方源下载速度不理想,可以换用适合自己网络环境的镜像源。这一步的操作思路是:备份原配置,修改源列表,更新索引。需要注意一点:树莓派系统源和普通 Ubuntu 源不完全一样,不要拿 Ubuntu 的源字符串随便替换,否则会出现依赖解析失败。换源后用apt update验证是否能正常拉取索引,如果报错就恢复原配置。
我个人还是建议保持官方源,或者至少保留官方源的一个备用入口。下载速度可以接受的话,稳定性优先。
3. Docker 环境搭建与关键概念
3.1 安装 Docker 引擎
树莓派是 ARM 架构,Docker 的安装方式和其他平台稍有区别,但官方提供了完整的支持。
最稳妥的安装方式是用官方脚本:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本会检测系统架构,自动配置 Docker 的软件源并安装对应版本。装完之后,把当前用户加入 docker 组,避免每条命令都用 sudo:
sudo usermod -aG docker $USER退出重新登录,让用户组生效。然后用一个最基础的命令验证 Docker 是否正常工作:
docker run hello-world如果能看到 “Hello from Docker!” 的输出,说明安装成功,容器引擎可以正常拉取镜像并运行容器。
这里多说一句关于 Docker Desktop。很多从 Windows、Mac 平台过来的朋友会下意识去找 Docker Desktop 的 ARM 版。但 Docker Desktop 本质上是一个桌面集成工具,里面额外跑了一层虚拟机,在树莓派这种资源有限的设备上完全没有必要。树莓派上直接用 Docker Engine 就足够了,轻量、零图形依赖、随系统启动。
3.2 Docker Compose 的配置与使用
单跑一个容器不需要编排,但网页服务器通常不是孤立的。一个典型的站点可能要同时跑 Nginx、PHP-FPM、MySQL,甚至再加个 Redis。容器一多,用docker run命令逐个启动就变得很繁琐,参数多不说,还容易漏。
这时候就需要 Docker Compose。它用一份 YAML 文件定义所有服务、网络、数据卷,然后用一条命令启动整个栈。
Compose 插件在安装 Docker 时通常会一起装上,可以用docker compose version验证。项目目录里创建一个docker-compose.yml,把所有服务写进去,之后所有操作都围绕这个文件展开。
我习惯的项目目录结构是这样的:
~/webserver/ ├── docker-compose.yml ├── nginx/ │ ├── conf.d/ │ │ └── default.conf │ └── html/ │ └── index.html ├── mysql/ │ ├── data/ │ └── conf/ └── logs/ ├── nginx/ └── mysql/目录即文档,配置文件和站点文件都挂载到宿主机上,以后备份只需要打包这个目录。这个习惯是我踩过很多次坑之后养成的:容器本身是临时的,数据必须留在宿主机。升级镜像、重建容器都不怕丢数据。
3.3 镜像、容器、数据卷的核心逻辑
很多人刚接触 Docker 时容易被镜像和容器这两个概念绕晕。我用一个生活化的类比:镜像像是光盘里的系统安装盘,容器像是用这张盘装出来的某台电脑。你可以用同一张盘装很多台电脑,每台电脑是独立运行的,装错了直接把那台电脑砸掉重新装就行,不会影响光盘。
数据卷则像是外接硬盘,电脑砸了硬盘还在,数据不丢。
网页服务器场景下,这三者的关系特别清晰:
- 镜像:Nginx、PHP、MySQL 的官方镜像,或者你基于它们做的自定义修改。这些只读,可以随时删除重拉。
- 容器:运行中的服务实例,无状态。任何存储都应该映射到数据卷,容器本身不带任何持久数据。
- 数据卷:宿主机上的目录或者 Docker 管理的卷,数据库文件、网站文件、配置文件、日志都放这里。
理解这个逻辑之后,你会意识到:容器化部署的核心工作其实是“怎么把数据和配置映射进去”,而不是“怎么把服务跑起来”。服务怎么跑是镜像决定好的,你只需要管数据和配置。
4. 网页服务器容器化部署实操
4.1 用 Nginx 跑起第一个站点
基础打好之后,可以直接进入实战。这里我以 Nginx 为例,搭一个最简单的静态站点。
先创建一个项目目录和对应的子目录,然后是docker-compose.yml:
services: nginx: image: nginx:stable-alpine container_name: web-nginx restart: unless-stopped ports: - "80:80" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/html:/usr/share/nginx/html:ro - ./logs/nginx:/var/log/nginx在nginx/html里放一个index.html,在nginx/conf.d里放一个站点配置:
server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ =404; } }然后在项目目录下执行:
docker compose up -d浏览器里打开树莓派的 IP,能看到页面内容,就说明第一个容器化网页服务已经跑起来了。
这里几个细节值得展开:
- 镜像选择
stable-alpine而不是latest或者默认标签,原因是 Alpine 版本体积小、更新节奏稳定,性能上对静态文件服务没有明显差异。 - 配置文件和站点文件都加了
:ro只读挂载。只读是一个很好的约定,容器内部不应该随意修改挂载进来的宿主文件。 restart: unless-stopped意味着容器异常退出时会自动拉起,除非你手动停止它,这对服务器场景至关重要。
4.2 数据持久化:网站文件与配置分离
静态页面只是开胃菜,实际网站总要和数据库之类的有状态服务打交道。这里我以 MySQL 为例,把持久化的完整思路讲透。
services: mysql: image: mysql:8.0 container_name: web-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: change_this_root_password MYSQL_DATABASE: myapp MYSQL_USER: myapp MYSQL_PASSWORD: change_this_user_password volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d ports: - "127.0.0.1:3306:3306"注意几个关键点:
- 数据库数据目录
./mysql/data必须挂载出来。如果忘挂载,容器一删,数据库文件全部消失,这是新手最容易犯的错误。 - 端口绑定写成
127.0.0.1:3306:3306,而不是3306:3306。前者只允许本机访问 MySQL,外部网络访问不到数据库端口。这是安全基线操作。应用容器通过 Docker 内部网络连接 MySQL 时,并不依赖这个端口映射。 - 初始化时的 root 密码和数据库账号要妥善保存。虽然可以之后改,但初始化时的配置最干净。
以 Nginx 加 PHP 为例的完整组合,会在下一篇单独展开。核心原则是:静态文件、动态程序、数据文件三层分离,静态文件走 Nginx 直接服务,动态请求转发给 PHP-FPM 容器,数据库只通过内部网络与 PHP-FPM 通信,所有持久化内容都映射到宿主机目录。
4.3 端口映射、多站点与反向代理
网页服务器运行过程中,多站点是绕不开的。一台树莓派上跑一个博客、一个图床、一个私人工具页面,是很常见的使用方式。
端口映射的思路是:宿主机 80 端口是唯一的对外入口,Nginx 容器负责监听 80,然后根据不同域名转发到不同服务。这样外部访问只暴露一个端口,内部怎么路由由 Nginx 决定。
反向代理配置示例:
server { listen 80; server_name blog.example.com; location / { proxy_pass http://web-php:9000; 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; } } server { listen 80; server_name gallery.example.com; location / { proxy_pass http://gallery-app:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }使用反向代理的好处很明显:不需要为每个站点申请一个端口,统一 80/443 入口;可以在 Nginx 这一层统一处理 SSL 证书、访问日志、访问限制;后续增加新服务时,只需要新增一个 server 块,不用修改其他站点的配置。
域名解析需要提前在 DNS 管理处把blog.example.com的 A 记录指向树莓派的公网 IP 或内网 IP。如果你不想用真实域名,也可以在内网搭建一个 DNS 服务,不过这是另一个话题了。
多站点配置变更后,执行docker compose exec nginx nginx -s reload就能热加载,不需要重启容器。
4.4 加密访问与 HTTPS 基础配置
网站一旦跑起来,下一步自然是上 HTTPS。就算只有一个内网页面,用 HTTPS 也能避免局域网内明文传输的尴尬。
有公网域名的话,直接用 Let's Encrypt 证书,配合 certbot 自动续期。没有公网域名,可以用自签名证书,缺点是对端设备需要手动信任证书;也可以用带信任根证书的服务,比如某些内网 DNS 系统自带的证书签发能力。
在 Nginx 容器里启用 HTTPS 的配置骨架:
server { listen 443 ssl; server_name blog.example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; location / { proxy_pass http://web-php:9000; } } server { listen 80; server_name blog.example.com; return 301 https://$host$request_uri; }证书文件的挂载建议单独一个目录,比如./nginx/certs,只读挂载进容器。续期脚本写入 crontab,定期检测并重载 Nginx 即可。
HTTPS 配置这件事,技术复杂度不高,容易出问题的都在证书路径、权限、续期脚本这些细节上。后面我会单独写一期专门讲容器环境下的证书管理,这里先把骨架搭好。
5. 性能调优与日常运维
5.1 容器资源限制与系统参数调优
树莓派的资源是有限的,运行多个容器时,如果不做限制,某个容器出问题可能会拖垮整个系统。Docker 提供了资源限制机制,可以在 compose 文件里指定 CPU 和内存上限:
services: mysql: image: mysql:8.0 deploy: resources: limits: cpus: '2.0' memory: 1g reservations: memory: 256m这里的含义是:MySQL 容器最多使用 2 个 CPU 核心和 1GB 内存,最低保留 256MB 内存。给关键服务设置资源上限,一方面避免单个容器无限膨胀,另一方面也确保 Nginx、SSH 这些基础服务始终有可用资源。
还有一个容易忽略的系统级参数——内存交换。树莓派默认的 swap 配置在 SD 卡或者 SSD 上,频繁换页会严重拖慢性能。我建议把 swap 调小,同时给 Docker 容器加上内存限制,让进程尽量在物理内存内运行。内存不够用的时候就减少容器数量,而不是依赖 swap 硬撑。
日志驱动也需要调整。容器默认的 json-file 日志会不断累积,时间长了能占好几个 GB。在 compose 文件里限制日志大小:
services: nginx: image: nginx:stable-alpine logging: driver: json-file options: max-size: "10m" max-file: "3"这样单个服务最多保留 30MB 日志,避免日志文件无限增长挤占磁盘。
5.2 定期备份与数据一致性
容器化之后,备份的重点从“备份整个系统”变成了“备份数据目录和配置文件”。这个转变让备份变得非常简单。
最低限度的备份方案:用 cron 定期把项目目录打包到外部存储,同时用mysqldump把数据库逻辑备份导出:
#!/bin/bash BACKUP_DIR="/mnt/backup/webserver" DATE=$(date +%Y%m%d) docker compose -f ~/webserver/docker-compose.yml exec -T mysql \ mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > "$BACKUP_DIR/mysql-$DATE.sql" tar -czf "$BACKUP_DIR/config-$DATE.tar.gz" -C ~ webserver find "$BACKUP_DIR" -type f -mtime +14 -delete这套脚本解决的问题是:配置文件和数据目录随用随备份,超过 14 天的旧备份自动删除。数据库部分是逻辑备份,适合恢复时跨平台、跨版本。如果你有 NVMe SSD 或者移动硬盘,备份目标放在外部存储,避免和系统盘一起故障。
恢复流程也要提前演练。真到机器坏了才摸索恢复,很容易出问题。实际操作中,先把系统重装,安装 Docker 和 Compose,再把备份的webserver目录解压回来,最后启动容器并导入数据库备份。整条链路顺了,才是真的高枕无忧。
5.3 开机自启与系统监控
服务器的一个重要特性是断电恢复后能自动回归服务。这一点主要靠两个层面:
第一,Docker 服务本身设置为开机自启。systemctl enable docker通常装完就默认开启了。可以在systemctl status docker里确认一下。
第二,每个容器设置restart: unless-stopped。这样 Docker 启动后会自动拉起所有配置了这个策略的容器。这里有个细节:不要在容器里自己写 init 脚本去启动其他进程,直接用 Docker 的重启策略,简单可靠。
监控方面,树莓派这类设备不用上特别重的监控系统,一条命令就能看核心状态:
docker stats --no-stream每个容器的 CPU、内存、网络、磁盘占用一目了然。日常巡检时跑一下,用docker logs --tail 50 容器名看异常。系统级别的温度监控可以用vcgencmd measure_temp查看,长期超过 80 度就要检查散热片和风扇了。
6. 常见问题与排查技巧
6.1 容器反复重启怎么办
容器启动后不断重启,是新手遇到最多的问题。排查思路要按顺序来:
先看容器日志,这是最有用的信息:
docker logs web-nginx日志里能看到的常见情况包括:
- 端口被占用:宿主机上的其他进程已经占用了 80 端口。用
sudo lsof -i :80或者sudo ss -tlnp | grep :80查看占用进程。 - 配置文件语法错误:Nginx 启动时校验配置失败。可以在宿主机上用
docker compose exec nginx nginx -t检查配置。 - 挂载目录权限不对:容器内的进程没有权限读写挂载目录。常见于 MySQL 容器,数据目录属主不是 MySQL 用户。解决办法是调整宿主机目录属主或权限。
每次问题解决后,执行docker compose up -d重新创建容器,再用docker ps确认状态变成 Up。
6.2 访问不通和端口映射问题
浏览器打开 IP 无法访问,先分清楚是哪个层面的问题。这个分层排查的思路很管用:
- 先测宿主机:在树莓派本机执行
curl -I http://localhost。通的话,问题在网络层或防火墙。 - 再测容器:检查容器是否在运行,端口映射是否生效:
docker compose ps。 - 然后测网络:从其他设备 ping 树莓派 IP,能通说明二层三层正常。
- 最后查防火墙:确认 80、443 端口是否放行。树莓派默认防火墙没有开启时不受影响,但如果你自己装过 ufw 或者 firewalld,要检查对应规则。
很多时候访问不通,是因为路由器或者光猫没有做端口转发,或者运营商封禁了 80/443 端口。这种情况可以换用非标准端口,或者通过内网隧道、云上中转之类的方案,不过那已经是另一个话题了。
6.3 磁盘写满与 SD 卡寿命
SD 卡是树莓派方案中最脆弱的环节。容器化之后,日志、镜像层、数据卷的写放大问题更加明显。
磁盘写满的常见原因有三个:容器日志无限增长、镜像和悬空层堆积、数据库文件持续增长。排查命令:
df -h docker system dfdocker system df会告诉你镜像、容器、数据卷、构建缓存各自占了多少空间。定期执行docker system prune -af清理无用镜像和构建缓存,能回收不少空间。注意-a会删除所有未被使用的中介镜像,-f跳过确认,执行前确认一下当前有没有需要保留的镜像。
要延长 SD 卡寿命,核心策略就是两个字:外置。把 Docker 的数据目录整体迁移到 SSD 或者移动硬盘上,系统分区保持在 SD 卡,这样日志写入和镜像层操作都发生在 SSD 上。配置方式是在/etc/docker/daemon.json里指定:
{ "data-root": "/mnt/ssd/docker" }改完重启 Docker:sudo systemctl restart docker。迁移时注意先把旧数据复制过去,再启动新 Docker,避免数据丢失。
7. 写在最后:这个系列的后续方向
写到这里,整套“树莓派 Docker 化网页服务器”的骨架已经清楚了:一块树莓派、一个 Linux 系统、一个 Docker 引擎、一组 compose 文件,就能把原本需要一整台服务器才干得动的事搬到桌面上。
我在实际使用中最大的感受是:这套方案的价值不在于性能多强,而在于“省心”。以前每次系统升级都提心吊胆,怕服务挂掉、怕环境不一致;现在升级就是拉新镜像、重建容器,几分钟搞定。
后续这个系列我打算按这几个方向继续往下写:
- WordPress 或 Typecho 这类动态站点的容器化组合,把 Nginx、PHP-FPM、MySQL 的协作讲透。
- 反向代理与多应用网关,用同一台树莓派承载多个独立服务。
- 日志收集与结构化存储,让日志不再只是一堆散文件,而是可查询、可告警的数据。
- 备份与容灾实战,覆盖从日常备份到整机迁移的完整链路。
如果你正准备用它跑点什么,我的建议是:先把这篇文章里的基础环境搭起来,不用急着追新镜像、新工具。容器化这个东西,底层逻辑通了,上层都是套路。下一篇我们就拿一个真实的建站项目开刀,把这套环境用起来。