|
| 1 | +# ============================================================ |
| 2 | +# AIX-DB 基础镜像 (Base Image) |
| 3 | +# 包含:Python 依赖 + 前端构建产物 + 运行时服务 |
| 4 | +# |
| 5 | +# 构建时机:当 Python 或前端依赖变更时手动构建 |
| 6 | +# 构建命令:make build-base |
| 7 | +# ============================================================ |
| 8 | + |
| 9 | +# ============ 阶段1: 构建前端 ============ |
| 10 | +FROM node:18-alpine AS frontend-builder |
| 11 | + |
| 12 | +WORKDIR /app/web |
| 13 | + |
| 14 | +# 安装 pnpm |
| 15 | +RUN npm install -g pnpm@latest --no-fund --no-audit && \ |
| 16 | + npm cache clean --force && \ |
| 17 | + rm -rf /root/.npm /tmp/* |
| 18 | + |
| 19 | +# 复制前端依赖文件 |
| 20 | +COPY web/package.json web/pnpm-lock.yaml ./ |
| 21 | + |
| 22 | +# 安装前端依赖并清理 |
| 23 | +RUN pnpm install --no-frozen-lockfile --prod=false --shamefully-hoist && \ |
| 24 | + pnpm store prune && \ |
| 25 | + rm -rf /root/.pnpm-store /tmp/* /root/.cache |
| 26 | + |
| 27 | +# 复制前端源代码 |
| 28 | +COPY web/ ./ |
| 29 | + |
| 30 | +# 构建前端并彻底清理 |
| 31 | +RUN pnpm run build && \ |
| 32 | + rm -rf node_modules .pnpm-store /root/.pnpm-store /tmp/* ~/.cache ~/.npm && \ |
| 33 | + find . -name "*.map" -delete 2>/dev/null || true && \ |
| 34 | + find . -name "*.md" -not -path "./dist/*" -delete 2>/dev/null || true |
| 35 | + |
| 36 | +# ============ 阶段2: 构建 Python 依赖 ============ |
| 37 | +FROM python:3.11-slim-bookworm AS backend-builder |
| 38 | + |
| 39 | +WORKDIR /aix-db |
| 40 | + |
| 41 | +# 设置镜像源和 PATH |
| 42 | +ENV PIP_INDEX_URL="https://mirrors.aliyun.com/pypi/simple" \ |
| 43 | + PIP_EXTRA_INDEX_URL="https://pypi.doubanio.com/simple" \ |
| 44 | + UV_INDEX_URL="https://mirrors.aliyun.com/pypi/simple" \ |
| 45 | + PATH="/root/.local/bin:${PATH}" \ |
| 46 | + PYTHONDONTWRITEBYTECODE=1 \ |
| 47 | + PIP_NO_CACHE_DIR=1 \ |
| 48 | + UV_NO_CACHE=1 |
| 49 | + |
| 50 | +# 只复制依赖文件 |
| 51 | +COPY pyproject.toml uv.lock* requirements.txt* ./ |
| 52 | + |
| 53 | +# 安装系统依赖和 Python 依赖 |
| 54 | +RUN set -eux; \ |
| 55 | + apt-get update && \ |
| 56 | + apt-get install -y --no-install-recommends \ |
| 57 | + gcc \ |
| 58 | + g++ \ |
| 59 | + python3-dev \ |
| 60 | + libpq-dev \ |
| 61 | + libffi-dev \ |
| 62 | + && \ |
| 63 | + pip install --no-cache-dir --no-compile pipx && \ |
| 64 | + pipx install "uv==0.8.0" && \ |
| 65 | + uv venv --clear && \ |
| 66 | + . .venv/bin/activate && \ |
| 67 | + uv sync --no-cache --index-url https://mirrors.aliyun.com/pypi/simple --extra-index-url "" && \ |
| 68 | + # 只进行安全的清理(保留所有元数据和 entry points) |
| 69 | + find .venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \ |
| 70 | + find .venv -type f -name "*.pyc" -delete && \ |
| 71 | + find .venv -type f -name "*.pyo" -delete && \ |
| 72 | + # 清理构建工具 |
| 73 | + apt-get purge -y gcc g++ python3-dev libpq-dev libffi-dev && \ |
| 74 | + apt-get autoremove -y --purge && \ |
| 75 | + apt-get clean && \ |
| 76 | + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/.cache /root/.local/bin/uv |
| 77 | + |
| 78 | +# ============ 阶段3: 基础运行镜像 ============ |
| 79 | +FROM python:3.11-slim-bookworm |
| 80 | + |
| 81 | +# 使用构建参数支持多架构 |
| 82 | +ARG TARGETARCH |
| 83 | +ARG TARGETPLATFORM |
| 84 | + |
| 85 | +WORKDIR /app |
| 86 | + |
| 87 | +# 安装运行时依赖:nginx, supervisor, MinIO, PostgreSQL |
| 88 | +RUN set -eux; \ |
| 89 | + apt-get update && \ |
| 90 | + apt-get install -y --no-install-recommends \ |
| 91 | + nginx \ |
| 92 | + supervisor \ |
| 93 | + libpq5 \ |
| 94 | + wget \ |
| 95 | + ca-certificates \ |
| 96 | + curl \ |
| 97 | + gnupg \ |
| 98 | + lsb-release \ |
| 99 | + locales \ |
| 100 | + gosu \ |
| 101 | + && \ |
| 102 | + # 设置 locale |
| 103 | + sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \ |
| 104 | + locale-gen && \ |
| 105 | + # 添加 PostgreSQL 官方仓库 |
| 106 | + echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ |
| 107 | + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ |
| 108 | + apt-get update && \ |
| 109 | + # 安装 PostgreSQL 17 和 pgvector 扩展 |
| 110 | + apt-get install -y --no-install-recommends \ |
| 111 | + postgresql-17 \ |
| 112 | + postgresql-17-pgvector \ |
| 113 | + && \ |
| 114 | + # 根据架构下载并安装 MinIO deb 包 |
| 115 | + if [ "$TARGETARCH" = "amd64" ]; then \ |
| 116 | + MINIO_DEB_URL="https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20250422221226.0.0_amd64.deb"; \ |
| 117 | + elif [ "$TARGETARCH" = "arm64" ]; then \ |
| 118 | + MINIO_DEB_URL="https://dl.min.io/server/minio/release/linux-arm64/archive/minio_20250422221226.0.0_arm64.deb"; \ |
| 119 | + else \ |
| 120 | + echo "Unsupported architecture: $TARGETARCH"; \ |
| 121 | + exit 1; \ |
| 122 | + fi && \ |
| 123 | + wget -O /tmp/minio.deb "${MINIO_DEB_URL}" && \ |
| 124 | + dpkg -i /tmp/minio.deb && \ |
| 125 | + rm -f /tmp/minio.deb && \ |
| 126 | + # 验证 MinIO 安装 |
| 127 | + minio --version && \ |
| 128 | + # 清理 |
| 129 | + apt-get clean && \ |
| 130 | + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ |
| 131 | + rm -rf /etc/nginx/sites-enabled/default /etc/nginx/sites-available/* && \ |
| 132 | + rm -rf /usr/share/doc /usr/share/man /usr/share/locale && \ |
| 133 | + rm -rf /usr/share/nginx/html/* && \ |
| 134 | + # 创建必要的目录 |
| 135 | + mkdir -p /var/log/aix-db /var/log/nginx /var/log/supervisor /var/log/minio /var/log/postgresql && \ |
| 136 | + mkdir -p /var/run/postgresql && \ |
| 137 | + chown -R postgres:postgres /var/run/postgresql && \ |
| 138 | + # 精简 nginx 配置 |
| 139 | + sed -i '/^#/d' /etc/nginx/nginx.conf 2>/dev/null || true |
| 140 | + |
| 141 | +# 设置 locale 环境变量 |
| 142 | +ENV LANG=en_US.UTF-8 \ |
| 143 | + LANGUAGE=en_US:en \ |
| 144 | + LC_ALL=en_US.UTF-8 |
| 145 | + |
| 146 | +# 从构建阶段复制虚拟环境 |
| 147 | +COPY --from=backend-builder /aix-db/.venv /aix-db/.venv |
| 148 | + |
| 149 | +# 复制前端构建结果 |
| 150 | +COPY --from=frontend-builder /app/web/dist /usr/share/nginx/html |
| 151 | + |
| 152 | +# 复制 nginx 配置 |
| 153 | +COPY docker/nginx.conf.template /etc/nginx/conf.d/default.conf |
| 154 | + |
| 155 | +# 复制 PostgreSQL 初始化脚本 |
| 156 | +COPY docker/init_sql.sql /docker-entrypoint-initdb.d/init.sql |
| 157 | + |
| 158 | +# 创建 PostgreSQL 数据目录并设置权限 |
| 159 | +RUN mkdir -p /var/lib/postgresql/data && \ |
| 160 | + chown -R postgres:postgres /var/lib/postgresql && \ |
| 161 | + chmod 700 /var/lib/postgresql/data |
| 162 | + |
| 163 | +# 创建日志目录 |
| 164 | +RUN mkdir -p /var/log/supervisor /var/log/nginx /var/log/aix-db /var/log/minio /var/log/postgresql /var/run /data |
| 165 | + |
| 166 | +# 复制 supervisor 配置文件 |
| 167 | +COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf |
| 168 | + |
| 169 | +# 修复 opentelemetry entry points 问题 |
| 170 | +RUN echo 'from __future__ import annotations' > /tmp/otel_fix.py && \ |
| 171 | + echo 'import logging' >> /tmp/otel_fix.py && \ |
| 172 | + echo 'import typing' >> /tmp/otel_fix.py && \ |
| 173 | + echo 'from contextvars import Token' >> /tmp/otel_fix.py && \ |
| 174 | + echo 'from os import environ' >> /tmp/otel_fix.py && \ |
| 175 | + echo 'from uuid import uuid4' >> /tmp/otel_fix.py && \ |
| 176 | + echo 'from opentelemetry.context.context import Context, _RuntimeContext' >> /tmp/otel_fix.py && \ |
| 177 | + echo 'from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext' >> /tmp/otel_fix.py && \ |
| 178 | + echo 'logger = logging.getLogger(__name__)' >> /tmp/otel_fix.py && \ |
| 179 | + echo 'def _load_runtime_context() -> _RuntimeContext:' >> /tmp/otel_fix.py && \ |
| 180 | + echo ' return ContextVarsRuntimeContext()' >> /tmp/otel_fix.py && \ |
| 181 | + echo '_RUNTIME_CONTEXT = _load_runtime_context()' >> /tmp/otel_fix.py && \ |
| 182 | + echo 'def create_key(keyname: str) -> str:' >> /tmp/otel_fix.py && \ |
| 183 | + echo ' return str(uuid4())' >> /tmp/otel_fix.py && \ |
| 184 | + echo 'def get_value(key: str, context: typing.Optional[Context] = None) -> typing.Optional[object]:' >> /tmp/otel_fix.py && \ |
| 185 | + echo ' ctx = context if context is not None else get_current()' >> /tmp/otel_fix.py && \ |
| 186 | + echo ' if ctx is not None:' >> /tmp/otel_fix.py && \ |
| 187 | + echo ' return ctx.get(key)' >> /tmp/otel_fix.py && \ |
| 188 | + echo ' return None' >> /tmp/otel_fix.py && \ |
| 189 | + echo 'def set_value(key: str, value: object, context: typing.Optional[Context] = None) -> Context:' >> /tmp/otel_fix.py && \ |
| 190 | + echo ' ctx = context if context is not None else get_current()' >> /tmp/otel_fix.py && \ |
| 191 | + echo ' new_values = {key: value}' >> /tmp/otel_fix.py && \ |
| 192 | + echo ' if ctx is not None:' >> /tmp/otel_fix.py && \ |
| 193 | + echo ' for k, v in ctx.items():' >> /tmp/otel_fix.py && \ |
| 194 | + echo ' if k not in new_values:' >> /tmp/otel_fix.py && \ |
| 195 | + echo ' new_values[k] = v' >> /tmp/otel_fix.py && \ |
| 196 | + echo ' return Context(new_values)' >> /tmp/otel_fix.py && \ |
| 197 | + echo 'def get_current() -> typing.Optional[Context]:' >> /tmp/otel_fix.py && \ |
| 198 | + echo ' ctx = _RUNTIME_CONTEXT.get_current()' >> /tmp/otel_fix.py && \ |
| 199 | + echo ' if ctx is None:' >> /tmp/otel_fix.py && \ |
| 200 | + echo ' ctx = Context()' >> /tmp/otel_fix.py && \ |
| 201 | + echo ' return ctx' >> /tmp/otel_fix.py && \ |
| 202 | + echo 'def attach(context: Context) -> Token[typing.Optional[Context]]:' >> /tmp/otel_fix.py && \ |
| 203 | + echo ' return _RUNTIME_CONTEXT.attach(context)' >> /tmp/otel_fix.py && \ |
| 204 | + echo 'def detach(token: Token[typing.Optional[Context]]) -> None:' >> /tmp/otel_fix.py && \ |
| 205 | + echo ' return _RUNTIME_CONTEXT.detach(token)' >> /tmp/otel_fix.py && \ |
| 206 | + echo '_SUPPRESS_INSTRUMENTATION_KEY = create_key("suppress_instrumentation")' >> /tmp/otel_fix.py && \ |
| 207 | + echo '_SUPPRESS_HTTP_INSTRUMENTATION_KEY = create_key("suppress_http_instrumentation")' >> /tmp/otel_fix.py && \ |
| 208 | + cp /tmp/otel_fix.py /aix-db/.venv/lib/python3.11/site-packages/opentelemetry/context/__init__.py && \ |
| 209 | + rm /tmp/otel_fix.py && \ |
| 210 | + find /aix-db/.venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \ |
| 211 | + find /aix-db/.venv -name "*.pyc" -delete && \ |
| 212 | + find /aix-db/.venv -name "*.pyo" -delete |
| 213 | + |
| 214 | +# 复制启动脚本 |
| 215 | +COPY docker/entrypoint.sh /entrypoint.sh |
| 216 | +RUN chmod +x /entrypoint.sh |
| 217 | + |
| 218 | +# 设置环境变量 |
| 219 | +ENV PATH="/aix-db/.venv/bin:${PATH}" \ |
| 220 | + SERVER_HOST="127.0.0.1" \ |
| 221 | + SERVER_PORT="8088" \ |
| 222 | + PYTHONUNBUFFERED=1 \ |
| 223 | + PYTHONDONTWRITEBYTECODE=1 \ |
| 224 | + PIP_NO_CACHE_DIR=1 \ |
| 225 | + DOCKER_ENV=true \ |
| 226 | + OTEL_PYTHON_CONTEXT=contextvars_context \ |
| 227 | + # PostgreSQL 配置 |
| 228 | + POSTGRES_USER=aix_db \ |
| 229 | + POSTGRES_DB=aix_db \ |
| 230 | + SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://aix_db:1@127.0.0.1:5432/aix_db \ |
| 231 | + # MinIO 配置 |
| 232 | + MINIO_ENDPOINT=127.0.0.1:9000 \ |
| 233 | + MINIO_ACCESS_KEY=admin \ |
| 234 | + MINIO_SECRET_KEY=admin123 \ |
| 235 | + MINIO_DEFAULT_BUCKET=filedata |
| 236 | + |
| 237 | +# 暴露端口 |
| 238 | +EXPOSE 80 8088 5432 9000 9001 |
| 239 | + |
| 240 | +# 数据卷 |
| 241 | +VOLUME ["/var/lib/postgresql/data", "/data"] |
| 242 | + |
| 243 | +# 基础镜像不需要 CMD,由应用镜像指定 |
0 commit comments