# SQLite 迁移到 PostgreSQL 本文档适用于已有 Gitea 服务仍在使用旧版 Docker Compose 和 SQLite,尚未更新到引入 PostgreSQL 的最新 commit 的场景。 迁移目标是把 `data/gitea/gitea.db` 中的业务数据导入到新的 PostgreSQL 数据库,同时继续复用现有的仓库、LFS、附件和配置目录。 > `gitea migrate` 不是 SQLite 到 PostgreSQL 的数据搬迁工具。它只会对当前 Gitea 配置指向的数据库执行 Gitea schema migration。 > > Gitea 自带的跨数据库导出入口是 `gitea dump --database postgres`。Gitea 没有对应的自动 `gitea restore` 命令,恢复或导入 PostgreSQL 时需要手动解压 dump 包,并用 `psql` 导入其中的 `gitea-db.sql`。 ## 迁移前准备 - 预留维护窗口。迁移期间必须停止 Gitea,避免 SQLite 继续写入。 - 保持 `GITEA_VERSION` 不变。不要在同一次操作中同时升级 Gitea 版本和切换数据库。 - 准备 `.env` 中的 PostgreSQL 配置: ```env POSTGRES_VERSION=16-alpine POSTGRES_DB=gitea POSTGRES_USER=gitea POSTGRES_PASSWORD=<强密码> ``` - 确认可以运行 `sqlite3`、`unzip`,并且 PostgreSQL 容器里可以运行 `psql`。 - 本文档假设 `docker-compose.yml` 把宿主机 `./data` 挂载到 Gitea 容器内的 `/data`。因此 dump 输出到容器内 `/data/gitea-dump-postgres.zip` 时,宿主机对应文件是 `data/gitea-dump-postgres.zip`。 ## 1. 停止旧 Gitea 在仍使用 SQLite 的旧版 compose 下停止 Gitea: ```bash docker compose stop gitea ``` 如果启用了 Caddy,可以保持 Caddy 运行显示维护页,也可以一起停止: ```bash docker compose stop caddy ``` ## 2. 冷备份 SQLite 和数据目录 ```bash ts=$(date +%Y%m%d-%H%M%S) mkdir -p backups/$ts cp data/gitea/gitea.db backups/$ts/gitea.db cp data/gitea/conf/app.ini backups/$ts/app.ini tar -czf backups/$ts/gitea-data.tar.gz data/git data/gitea ``` 备份完成后,记录 SQLite 中的关键表数量,作为迁移后校验基线: ```bash sqlite3 data/gitea/gitea.db "select 'user', count(*) from user union all select 'repository', count(*) from repository union all select 'lfs_meta_object', count(*) from lfs_meta_object union all select 'public_key', count(*) from public_key union all select 'issue', count(*) from issue union all select 'release', count(*) from release;" ``` ## 3. 使用 Gitea dump 导出 PostgreSQL SQL 在仍使用旧 SQLite 配置时运行 `gitea dump`。不要先把 Gitea 配置切到 PostgreSQL,否则 dump 会尝试读取新数据库。 ```bash docker compose run --rm --user git gitea \ gitea dump -c /data/gitea/conf/app.ini \ --database postgres \ --file /data/gitea-dump-postgres.zip ``` 确认宿主机上已经生成 dump 包: ```bash ls -lh data/gitea-dump-postgres.zip ``` 如果当前镜像或环境不能识别 `git` 用户,可以改用官方镜像常见 UID/GID: ```bash docker compose run --rm --user 1000:1000 gitea \ gitea dump -c /data/gitea/conf/app.ini \ --database postgres \ --file /data/gitea-dump-postgres.zip ``` 解压 dump 包,得到 `gitea-db.sql`: ```bash mkdir -p backups/gitea-dump-postgres unzip -o data/gitea-dump-postgres.zip -d backups/gitea-dump-postgres ls -lh backups/gitea-dump-postgres/gitea-db.sql ``` ## 4. 更新代码并只启动 PostgreSQL 更新到包含 PostgreSQL 服务的最新 commit,并补齐 `.env` 中的 PostgreSQL 配置。 ```bash git pull ``` 先只启动 PostgreSQL,不要启动 Gitea: ```bash docker compose up -d postgres ``` 确认 PostgreSQL 可连接: ```bash docker compose exec -T postgres psql -U gitea -d gitea -c "select version();" ``` ## 5. 确认 PostgreSQL 是空库 检查目标库是否已有表: ```bash docker compose exec -T postgres psql -U gitea -d gitea -c '\dt' ``` 如果已经有 Gitea 业务表,先确认这些表不是生产数据。确认可丢弃后,停止 PostgreSQL,备份并移走 `data/postgres/`,再重新启动空库: ```bash docker compose stop postgres ts=$(date +%Y%m%d-%H%M%S) mv data/postgres data/postgres.before-migration-$ts docker compose up -d postgres ``` 再次运行 `\dt`,确认目标库为空。 ## 6. 导入 dump SQL 到 PostgreSQL 使用 `psql` 导入 `gitea-db.sql`: ```bash docker compose exec -T postgres psql -U gitea -d gitea < backups/gitea-dump-postgres/gitea-db.sql ``` 导入完成后,检查目标库关键表数量: ```bash docker compose exec -T postgres psql -U gitea -d gitea -c "select 'user', count(*) from \"user\" union all select 'repository', count(*) from repository union all select 'lfs_meta_object', count(*) from lfs_meta_object union all select 'public_key', count(*) from public_key union all select 'issue', count(*) from issue union all select 'release', count(*) from release;" ``` 这些数量应与第 2 步 SQLite 基线一致。 ## 7. 启动 Gitea 确认 `docker-compose.yml` 已通过 `GITEA__database__*` 环境变量指向 PostgreSQL,然后启动 Gitea: ```bash docker compose up -d gitea docker compose logs -f gitea ``` Gitea 启动时会对 PostgreSQL 执行必要的 schema migration。日志中不应出现数据库连接、类型转换、唯一约束或 migration 错误。 如果使用 Caddy: ```bash docker compose up -d caddy ``` ## 8. 迁移后验证 - 使用原管理员账号登录 Web UI。 - 确认用户、组织、仓库、Issue、Release、SSH Key 等数据存在。 - 执行一次 HTTP 或 HTTPS clone。 - 执行一次 SSH clone。 - 推送一个普通 commit。 - 如果使用 Git LFS,执行一次 LFS push。 - 检查日志: ```bash docker compose logs gitea postgres ``` ## 常见问题 ### Gitea is not supposed to be run as root `docker compose run` 默认可能以 root 用户启动一次性容器,但 Gitea 不允许以 root 运行。给 dump 命令加上 `--user git`: ```bash docker compose run --rm --user git gitea \ gitea dump -c /data/gitea/conf/app.ini \ --database postgres \ --file /data/gitea-dump-postgres.zip ``` 如果 `git` 用户不可用,改用: ```bash docker compose run --rm --user 1000:1000 gitea \ gitea dump -c /data/gitea/conf/app.ini \ --database postgres \ --file /data/gitea-dump-postgres.zip ``` ### Unable to create dump file "/gitea-dump-*.zip": permission denied `gitea dump` 默认会把文件写到容器当前目录。如果当前目录是 `/`,非 root 用户没有写权限。显式指定 `--file /data/gitea-dump-postgres.zip`,让 dump 写入已挂载且可持久化的 `/data` 目录。 ### pgloader 备选方案 如果 `gitea dump --database postgres` 在当前版本或数据集上失败,可以把 `pgloader` 作为备选迁移工具。使用前仍然要停止 Gitea、完成冷备份,并确认 PostgreSQL 目标库为空。 把 `` 替换为 `.env` 中的真实密码: ```bash docker run --rm \ --network gitea_default \ -v "$PWD/data/gitea:/data/gitea:ro" \ dimitri/pgloader:latest \ pgloader sqlite:///data/gitea/gitea.db postgresql://gitea:@postgres:5432/gitea ``` 如果 compose project 网络名不是 `gitea_default`,替换 `--network` 参数。 ## 回滚 如果迁移后验证失败,先停止新 Gitea: ```bash docker compose stop gitea ``` 回滚到旧版 SQLite compose,或临时移除 `docker-compose.yml` 中的 `GITEA__database__*` 环境变量,避免它覆盖 `app.ini`。 恢复备份: ```bash cp backups/<备份时间>/gitea.db data/gitea/gitea.db cp backups/<备份时间>/app.ini data/gitea/conf/app.ini ``` 再启动旧 SQLite 版本: ```bash docker compose up -d gitea ``` 在迁移完全确认前,不要删除 `data/gitea/gitea.db`、`backups/`、`data/gitea-dump-postgres.zip` 或迁移前的 `data/postgres.before-migration-*`。 ## 演练建议 正式迁移前,建议在复制出来的目录中完整演练一次: ```bash cp -a gitea gitea-migration-dry-run cd gitea-migration-dry-run ``` 演练中也要验证 dump 导出、SQL 解压、空库检查、`psql` 导入、表数量对比、Gitea 启动和 Web/Git 操作。演练通过后,再在正式目录执行维护窗口迁移。