使用场景:在一台机器上构建
tianlu-api镜像,通过文件拷贝到另一台机器,再用docker-compose一键启动整套服务。
命令环境:示例均基于 Windows PowerShell。
目录要求:所有命令请在项目根目录执行(能看到Dockerfile、docker-compose.yaml的目录)。
迁移流程图
flowchart TD
Start([开始]) --> Source[源机器]
Source --> Build[1. 构建镜像<br/>docker build --ssh ...]
Build --> Export[2. 导出镜像<br/>docker save -o tianlu-api.tar]
Export --> Copy[3. 拷贝文件<br/>tianlu-api.tar]
Copy --> Target[目标机器]
Target --> Load[4. 导入镜像<br/>docker load -i tianlu-api.tar]
Load --> Compose[5. 启动服务<br/>docker-compose up -d]
Compose --> Verify[6. 验证服务<br/>检查状态和日志]
Verify --> End([完成])
style Start fill:#e1f5e1
style End fill:#e1f5e1
style Source fill:#fff4e1
style Target fill:#e1f0ff
style Build fill:#ffe1e1
style Export fill:#ffe1e1
style Copy fill:#f0e1ff
style Load fill:#e1f0ff
style Compose fill:#e1f0ff
style Verify fill:#e1f0ff
流程说明:
源机器:构建 Docker 镜像并导出为 tar 文件
文件传输:将 tar 文件拷贝到目标机器(U盘/网络/其他方式)
目标机器:导入镜像并使用 docker-compose 启动服务
1. 在源机器打包镜像
1.1 构建镜像(从 Gitee 拉取私有依赖)
打开 PowerShell,并切换到项目根目录,例如:
cd D:\work\code\tianlu\project-tianlu确认本机已经配置好可用于访问 Gitee 的 SSH 私钥(例如
C:\Users\你的用户名\.ssh\id_ed25519),且该私钥已添加到 SSH agent 中(可执行ssh-add -l检查)。执行构建命令(注意使用双引号和
$()展开变量):docker build --ssh "default=$($env:USERPROFILE)\.ssh\id_ed25519" -f Dockerfile -t tianlu-api:latest .--ssh "default=...":将本机 SSH 私钥以default这一标识挂载到构建容器中,供uv sync访问 Gitee 上的tf-ml、tfcommon等私有仓库。-f Dockerfile:指定使用当前项目中的Dockerfile。-t tianlu-api:latest:为构建出的镜像打上名字和标签,后续docker-compose会直接使用这个镜像名。
构建成功后,可以通过以下命令确认镜像已经存在:
docker images tianlu-api:latest
SSH 相关注意事项(与 Dockerfile 中 known_hosts 配置对应)
必须传入 SSH 凭证:如果不带
--ssh ...参数,构建过程中uv sync无法从 Gitee 拉取私有依赖,会报Host key verification failed或权限错误。私钥路径可调整:如果你的私钥文件名不是
id_ed25519,只需把路径替换为实际的私钥路径即可。安全性说明:
Dockerfile中只写入了gitee.com的known_hosts(主机指纹),不会将你的私钥打进镜像。SSH 私钥仅在构建阶段通过
--ssh临时挂载到构建容器中,最终生成的运行时镜像中不包含任何 SSH 密钥。
不使用 SSH agent 的情况:如果没有启用 SSH agent,也可以直接传文件路径,例如:
docker build --ssh default=C:\Users\你的用户名\.ssh\id_ed25519 -f Dockerfile -t tianlu-api:latest .实际效果与使用
default=$($env:USERPROFILE)\.ssh\id_ed25519类似。
1.2 导出镜像为文件
仍在项目根目录,执行如下命令将镜像打包为一个
.tar文件:docker save -o tianlu-api.tar tianlu-api:latest-o tianlu-api.tar:指定输出文件名为tianlu-api.tar,会保存在当前目录。tianlu-api:latest:要导出的镜像名。
将生成的
tianlu-api.tar拷贝到目标机器,可以通过:U 盘 / 移动硬盘
内网共享目录
网盘 / 文件传输工具等方式
2. 在目标机器导入镜像
将在源机器打包好的
tianlu-api.tar拷贝到目标机器上(建议放到与项目同级目录,便于管理)。打开 PowerShell,切换到
tianlu-api.tar所在目录,执行:docker load -i tianlu-api.tar该命令会将
tianlu-api.tar中的镜像导入到目标机器的本地 Docker 镜像仓库中。
导入完成后,可以执行:
docker images tianlu-api:latest如果能看到
tianlu-api这一镜像,说明导入成功。
3. 使用 docker-compose 启动服务
docker-compose.yaml 已经为你配置好了完整的运行环境,包含:
postgres
使用镜像:
postgres:18数据库名:
tianlu账号密码:
tianlu / tianlu端口映射:宿主机
5432→ 容器5432数据卷:
postgres_data(持久化数据库数据)网络:
tianlu-network
api
使用镜像:
tianlu-api:latest(前面导入的镜像)端口映射:宿主机
8000→ 容器8000环境变量:
DATABASE_URL、SYNC_DATABASE_URL等已指向postgres服务启动命令:先执行
alembic upgrade head迁移数据库,再启动uvicorn服务网络:与
postgres一致,均在tianlu-network上
3.1 启动数据库和 API
在目标机器上,确保已经有
docker-compose.yaml文件(内容参考文档底部的附录)。在该文件所在目录执行:
docker-compose --profile dev up -d--profile dev:启用api服务声明的devprofile。up -d:以后台方式启动所有定义的服务(本项目是postgres和api)。
首次启动时:
Docker 会自动创建网络
tianlu-network和数据卷postgres_data。postgres服务启动后会初始化数据库。api服务在postgres健康检查通过后启动,并自动执行数据库迁移。
3.2 查看服务状态与实时日志
查看当前服务是否都已运行:
docker-compose ps期望看到
postgres和api均为Up状态。
如果需要排查问题,可以查看实时日志:
docker-compose logs -fCtrl + C可退出日志跟随模式,但不会停止容器。
3.3 在浏览器中访问
默认访问地址:
应用入口(前端 + 后端):
http://127.0.0.1:8000/
如果在远程服务器部署,需要将
127.0.0.1替换为服务器 IP。
3.4 停止并清理容器
当不再需要运行服务时,可以在 docker-compose.yaml 所在目录执行:
docker-compose down
该命令会停止并删除
postgres、api容器,但默认不会删除数据卷postgres_data。如果希望连数据卷一起删除(慎用),可执行:
docker-compose down -v
附录:Dockerfile(参考)
# https://docs.astral.sh/uv/guides/integration/docker/#available-images
FROM python:3.10-trixie AS uv
RUN wget -qO- https://astral.sh/uv/install.sh | sh
WORKDIR /workspace
ENV PATH="/root/.local/bin:$PATH" \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
FROM uv AS ci
# 配置 SSH known_hosts 以避免 Host key verification failed
RUN mkdir -p /root/.ssh && \
ssh-keyscan gitee.com >> /root/.ssh/known_hosts
ENV UV_PROJECT_ENVIRONMENT=/root/.cache/uv/venv\
PYTHONUNBUFFERED=1
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv --mount=type=ssh \
uv sync --frozen
FROM uv AS runtime
# 配置 SSH known_hosts 以避免 Host key verification failed
RUN mkdir -p /root/.ssh && \
ssh-keyscan gitee.com >> /root/.ssh/known_hosts
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv --mount=type=ssh \
uv sync --frozen --no-dev
FROM python:3.10-slim-trixie AS app
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /workspace
# 复制虚拟环境并清理缓存文件以减小镜像大小
COPY --from=runtime /workspace/.venv ./.venv
RUN find .venv -type d -name __pycache__ -exec rm -r {} + 2>/dev/null || true && \
find .venv -type f -name "*.pyc" -delete && \
find .venv -type f -name "*.pyo" -delete && \
rm -rf .venv/lib/python*/test .venv/lib/python*/tests .venv/lib/python*/idlelib 2>/dev/null || true && \
rm -rf .venv/lib/python*/ensurepip .venv/lib/python*/pydoc_data 2>/dev/null || true
COPY --chown=appuser:appuser alembic.ini ./
COPY --chown=appuser:appuser alembic ./alembic
COPY --chown=appuser:appuser scripts ./scripts
COPY --chown=appuser:appuser src ./src
ENV PATH="/workspace/.venv/bin:$PATH" \
PORT=8000
USER appuser
EXPOSE $PORT
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:$PORT', timeout=5.0)" || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "3"]
附录:docker-compose.yaml(参考)
name: tianlu-test
services:
postgres:
image: postgres:18 # 使用 PostgreSQL 18 镜像
container_name: tianlu-postgres-test
environment:
POSTGRES_DB: tianlu # 数据库名称
POSTGRES_USER: tianlu # 数据库用户
POSTGRES_PASSWORD: tianlu # 数据库密码
ports:
- "5432:5432" # PostgreSQL 默认端口 前面本地端口 后面容器端口
volumes:
- postgres_data:/var/lib/postgresql # 数据卷
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tianlu -d tianlu"] # PostgreSQL 健康检查
interval: 10s
timeout: 5s
retries: 5
networks:
- tianlu-network
api:
profiles:
- dev
image: tianlu-api:latest
container_name: tianlu-api-test
environment:
DATABASE_URL: "postgresql+asyncpg://tianlu:tianlu@postgres:5432/tianlu" # 更新为 PostgreSQL 连接字符串
SYNC_DATABASE_URL: "postgresql+psycopg2://tianlu:tianlu@postgres:5432/tianlu"
SECRET_KEY: "your-secret-key-change-in-production"
DEBUG: "false"
CORS_ORIGINS: '["http://localhost:8000"]'
command: ["sh", "-c", "alembic upgrade head && python -m uvicorn src.main:app --host 0.0.0.0 --port 8000 --workers 3"]
ports:
- "8000:8000"
depends_on:
postgres: # 依赖 postgres 服务
condition: service_healthy
networks:
- tianlu-network
restart: unless-stopped
volumes:
postgres_data: # 更新数据卷名称
networks:
tianlu-network:
driver: bridge