把自己的 Homelab 整理成一个 Git 仓库

我为什么把自己的 Homelab 整理成一个 Git 仓库

折腾 Homelab 这件事,一开始往往都挺随意的。

先是一台云服务器,跑个数据库、跑个反向代理,再放几个自己常用的小服务。后来又多了一台便宜 VPS,一台家里的小主机,可能还会临时加一台测试机器。服务也慢慢变多:密码库、内网穿透、文件列表、定时任务、远程桌面、各种自己写的小应用。

刚开始我也觉得,用 Docker Compose 就够了。每个服务一个目录,docker compose up -d,看起来很清爽。

但时间一长,问题就冒出来了。

哪台机器上跑了哪些服务?某个服务的 .env 应该放在哪里?迁移服务器的时候,到底要先备份数据库,还是先停服务?某个应用依赖的 Redis 是本机的,还是另一台机器上的?更要命的是,等真出故障的时候,很多操作全靠当时的记忆,而记忆这东西在凌晨三点通常不太可靠。

所以我后来干脆把 Homelab 当成一个小型基础设施项目来管理:用 Git 记录结构,用 Docker Compose 管服务,用脚本约定部署、备份、恢复和迁移的入口。

这就是这个仓库的由来:

仓库地址:https://github.com/DYS7516461/homelab

它不是那种下载下来就能直接跑满一整套生产环境的项目,更像是一个可扩展的 Homelab 骨架。你可以把它 clone 到自己的服务器上,然后按自己的机器、域名、密钥和服务逐步补齐。

我想解决的不是“怎么启动一个容器”

单个服务启动起来其实不难。

真正麻烦的是服务越来越多之后的日常维护。比如:

  • 新增一台服务器时,基础目录、Docker、Restic、配置文件要怎么准备;

  • 多台服务器分别运行哪些服务,要不要靠 README 里人工记录;

  • MySQL、PostgreSQL、Redis 这种公共服务,是每个应用各起一份,还是统一管理;

  • 真实密码、Token、证书这些东西放在哪里,怎么避免误提交;

  • 更新服务前要不要备份,备份产物放在哪里;

  • 服务器迁移时,怎么保证能按顺序恢复数据、重新部署、再检查状态。

这些问题单独看都不大,但它们加起来就是 Homelab 后期最消耗耐心的地方。

所以这个项目没有把重点放在“提供一个超复杂的自动化平台”上,而是先把几个关键约定固定下来:目录怎么放、主机怎么声明、服务怎么分层、脚本入口叫什么、备份恢复怎么走。

有了这些约定之后,后面接新服务就不再是从零开始琢磨,而是照着同一套结构往里填。

简单说说设计思路

这个仓库的核心思路很朴素:


Git 仓库

  -> hosts/*.yml       记录每台服务器要跑哪些服务

  -> services/shared   放 MySQL、PostgreSQL、Redis 这类公共基础服务

  -> services/apps     放具体应用服务

  -> scripts/*.sh      提供部署、更新、备份、恢复、迁移入口

  -> docs/*.md         记录架构和运维流程

我比较喜欢这种方式,是因为它足够直观。打开仓库,大概扫一眼目录,就能知道这个 Homelab 是怎么组织的。

hosts 目录负责描述主机。比如 hosts/oracle.yml 里写了 Oracle VPS 要运行 shared/mysql、shared/postgres 和 shared/redis;hosts/azure.yml 里则放了 PostgreSQL、Nginx Proxy Manager、Resource Tracker 和 CLI Proxy API。

services/shared 放公共依赖。数据库、缓存、反向代理这类基础能力如果每个应用都各自起一套,后期维护会很碎。把它们作为 shared 服务管理,应用通过 Docker 网络或 Tailscale 内网去连接,迁移时也更容易收口。

services/apps 放具体应用。每个应用目录尽量保持类似结构:


services/apps/example-app/

├── compose.yml 或 docker-compose.yml

├── .env.example

├── backup-exec

├── restore-exec

└── README.md

这里有两个小约定很重要。

第一,仓库只保存模板,不保存真实密钥。生产 .env、Token、证书、私钥都应该放到服务器的 /opt/homelab/config,不要进 Git。

第二,统一脚本只负责编排,具体服务自己负责细节。比如备份时,scripts/backup.sh 读取主机清单,找到服务目录里的 backup-exec,然后把统一的环境变量传进去。至于 MySQL 要 dump 成 all.sql.gz,Redis 要导出 dump.rdb,Vaultwarden 要备份哪个数据目录,这些由服务自己的脚本决定。

这个边界让我后面加服务时轻松很多。全局脚本不用理解每个应用的内部结构,应用也不用知道整个 Homelab 的部署拓扑。

推荐的服务器目录

在服务器上,我建议统一使用下面这套目录:


/opt/homelab/

├── repo/      # 本仓库 clone 位置

├── config/    # 真实环境变量、应用配置、证书、密钥

├── data/      # 数据库、应用持久化数据

├── backup/    # 本地备份产物

└── logs/      # 定时任务和维护日志

这个约定看起来普通,但实际很有用。因为一旦目录固定,备份、恢复、迁移脚本就有了共同语言。以后换机器时,也不需要临时猜某个服务的数据到底被挂到了哪里。

快速使用

先在服务器上准备目录并克隆仓库:


sudo mkdir -p /opt/homelab

sudo chown "$USER":"$USER" /opt/homelab

git clone https://github.com/DYS7516461/homelab.git /opt/homelab/repo

cd /opt/homelab/repo

给脚本加执行权限,然后初始化服务器:


chmod +x scripts/*.sh

./scripts/bootstrap-server.sh --host oracle

如果是中国大陆服务器,可以指定 Docker apt 镜像源:


./scripts/bootstrap-server.sh --host oracle --docker-apt-mirror aliyun

初始化脚本会安装基础工具、Docker、Docker Compose plugin、Restic,并创建 /opt/homelab/{repo,config,data,backup} 等目录。脚本如果把当前用户加入了 docker 用户组,记得退出 SSH 后重新登录一次,让用户组变更生效。

接着准备真实配置。仓库里只放 .env.example,生产配置建议复制到 /opt/homelab/config 后再修改:


cp services/shared/mysql/.env.example /opt/homelab/config/mysql.env

cp services/shared/postgres/.env.example /opt/homelab/config/postgres.env

cp services/shared/redis/.env.example /opt/homelab/config/redis.env

然后编辑这些文件,把里面的 change_me 换成真实密码:


chmod 600 /opt/homelab/config/*.env

部署前可以先 dry-run 看看脚本会做什么:


./scripts/deploy.sh oracle --dry-run

确认没问题后再真正部署:


./scripts/deploy.sh oracle

只部署某个服务也可以:


./scripts/deploy.sh oracle --service shared/mysql

查看服务状态:


./scripts/deploy.sh oracle --action ps

如果只是想手动跑某个 Compose,也没问题:


cd services/shared/mysql

docker compose -f compose.yml up -d

日常更新和备份

日常更新时,可以用:


./scripts/update.sh --host oracle

如果希望更新前先做一次备份:


./scripts/update.sh --host oracle --with-backup

如果已经配置了 Restic,并且希望备份后上传到远端仓库:


./scripts/update.sh --host oracle --with-restic

单独备份某个服务:


./scripts/backup.sh --host oracle --service shared/mysql

备份当前主机清单中的所有可备份服务:


./scripts/backup.sh --host oracle

加上 Restic 上传:


./scripts/backup.sh --host oracle --with-restic

Restic 默认读取:


/opt/homelab/config/restic.env

示例配置长这样:


export RESTIC_REPOSITORY=s3:https://<account-id>.r2.cloudflarestorage.com/homelab

export RESTIC_PASSWORD=change_me

export AWS_ACCESS_KEY_ID=change_me

export AWS_SECRET_ACCESS_KEY=change_me

看快照:


./scripts/restic-maintain.sh --snapshots

预览保留策略:


./scripts/restic-maintain.sh --forget --dry-run

执行保留策略并检查仓库:


./scripts/restic-maintain.sh --forget --check

恢复和迁移

恢复某台主机的服务:


./scripts/restore.sh --host oracle --timestamp 20260623-120000

只恢复某个服务:


./scripts/restore.sh --host oracle --service shared/mysql --timestamp 20260623-120000

如果备份在 Restic 里,可以先从 Restic 恢复备份目录,再执行服务恢复:


./scripts/restore.sh --host oracle --timestamp 20260623-120000 --from-restic

迁移服务器时,可以用迁移脚本把流程拆开:


./scripts/migrate-server.sh --source-host oracle --target-host azure --timestamp 20260623-120000 --phase backup --with-restic

./scripts/migrate-server.sh --source-host oracle --target-host azure --timestamp 20260623-120000 --phase restore --with-restic

./scripts/migrate-server.sh --source-host oracle --target-host azure --timestamp 20260623-120000 --phase deploy

./scripts/migrate-server.sh --source-host oracle --target-host azure --timestamp 20260623-120000 --phase verify

我个人会建议,别等真要迁移时才第一次跑这些命令。找一台临时机器演练一次恢复流程,比写十页文档都管用。

怎么新增服务

新增服务时,我通常按这个顺序来:

  1. 在 services/apps 或 services/shared 下创建服务目录。

  2. 添加 compose.yml 或 docker-compose.yml。

  3. 添加 .env.example,只写示例值,不写真实密钥。

  4. 如果需要统一备份,添加 backup-exec,把备份产物写入 SERVICE_BACKUP_DIR。

  5. 如果需要统一恢复,添加 restore-exec,从 SERVICE_RESTORE_DIR 读取备份产物。

  6. 在目标主机的 hosts/<host>.yml 中加入服务路径。

  7. 用 deploy.sh --dry-run 检查部署计划,再真正启动。

比如:


services/apps/my-app/

├── compose.yml

├── .env.example

├── backup-exec

├── restore-exec

└── README.md

然后在 hosts/tencent.yml 里加:


hostname: tencent

services:

  - apps/my-app

之后就可以:


./scripts/deploy.sh tencent --service apps/my-app --dry-run

./scripts/deploy.sh tencent --service apps/my-app

它目前适合什么人

如果你只是在一台机器上跑两三个容器,这套结构可能会显得有点正式。

但如果你已经有多台服务器,或者准备长期维护一堆自托管服务,我觉得把这些东西早点整理起来是值得的。它不会让运维工作消失,但能减少很多“我当时到底怎么配的”这种低质量焦虑。

这个项目目前还是一个骨架,很多地方故意没有做得太重。比如 host YAML 解析只支持简单列表,脚本默认在目标服务器本机执行,不负责远程 SSH 分发,部分服务模板上线前也还需要按自己的环境补健康检查、安全参数和真实配置。

但我反而喜欢它现在这个状态:够用、清楚、可改。Homelab 最怕的不是不够自动化,而是自动化到最后自己都不敢动。

如果你也在维护自己的小型服务器群,可以直接拿这个仓库改起:

GitHub:https://github.com/DYS7516461/homelab

先从一台机器、一个 shared 服务、一个应用开始。等部署、备份、恢复这条链路跑通之后,再慢慢把更多服务迁进去。Homelab 不一定要一步到位,但最好从第一天开始就给未来的自己留条清楚的路。