# syntax=docker/dockerfile:1 # --------------------------------------------------------------------------- # KenApi — GitDataAI 主业务后端(cargo workspace,二进制 app:serve / migrate / openapi) # # docker build -t kenapi:local . # docker run --rm -p 7051:7051 \ # -e APP_DATABASE__URL=postgres://... kenapi:local # # 与仓库内其它镜像共用的约定: # * builder 只负责编译,runtime 只带二进制 + 运行期依赖; # * BuildKit cache mount 复用 cargo registry / git / target,重复构建不重编依赖; # * 非 root 运行(uid/gid 10001)、tini 作 PID 1、SIGTERM 优雅退出; # * 配置从 /app/config 读取,密钥只走环境变量或挂载文件,不烘进镜像层。 # --------------------------------------------------------------------------- ARG RUST_IMAGE=rust:1-bookworm ARG RUNTIME_IMAGE=debian:bookworm-slim FROM node:26-bookworm-slim AS identity-web WORKDIR /web # 前端由 pnpm 管理(pnpm-workspace.yaml / pnpm-lock.yaml);Node 25 起不再自带 corepack。 RUN npm install -g pnpm@12 COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile COPY view ./view COPY packages ./packages COPY openapi.json ./ COPY scripts ./scripts COPY vite.config.ts tsconfig.json tsconfig.app.json tsconfig.node.json \ orval.config.ts steiger.config.ts biome.json ./ # 契约闸门:生成客户端必须与提交的 packages/apiclient 一致。 RUN bash scripts/contract-check.sh --client-only RUN pnpm build # --------------------------------------------------------------------------- # builder # --------------------------------------------------------------------------- FROM ${RUST_IMAGE} AS builder # protoc:lib/proto 的 build.rs 会编译 .proto。 # libssl-dev / pkg-config:native-tls 链(reqwest 等处)需要 OpenSSL 头文件。 ENV PROTOC_INCLUDE=/usr/include \ CARGO_TERM_COLOR=always RUN apt-get update \ && apt-get install -y --no-install-recommends \ pkg-config \ libssl-dev \ cmake \ perl \ git \ curl \ ca-certificates \ unzip \ protobuf-compiler \ libprotobuf-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /build # 工具链由 rust-toolchain.toml 固定(nightly-2026-10-27);rustup 在这一层装好, # 后面所有 cargo 调用都用这个版本,不受基础镜像自带 stable 影响。 COPY rust-toolchain.toml ./ RUN rustup show > /dev/null COPY Cargo.toml Cargo.lock openapi.json ./ COPY lib ./lib COPY app ./app COPY --from=identity-web /web/view/dist ./view/dist RUN --mount=type=cache,id=kenapi-cargo-registry,target=/usr/local/cargo/registry \ --mount=type=cache,id=kenapi-cargo-git,target=/usr/local/cargo/git \ --mount=type=cache,id=kenapi-target,target=/build/target \ cargo build --release --locked --workspace --bins \ && mkdir -p /out \ && cp target/release/app /out/ \ && /out/app openapi --check \ && strip /out/app # --------------------------------------------------------------------------- # runtime # --------------------------------------------------------------------------- FROM ${RUNTIME_IMAGE} AS runtime ARG APP_UID=10001 ARG APP_GID=10001 # libssl3:native-tls 运行期依赖;git:仓库维护/排障用; # curl 只服务 HEALTHCHECK,tini 负责转发 SIGTERM 并回收子进程。 RUN apt-get update \ && apt-get install -y --no-install-recommends \ git \ ca-certificates \ libssl3 \ curl \ tini \ && rm -rf /var/lib/apt/lists/* \ && useradd --system --create-home --uid ${APP_UID} --user-group gitdata \ && mkdir -p /opt/gitdata/repos /opt/gitdata/objects \ && chown -R gitdata:gitdata /opt/gitdata WORKDIR /app # 只带默认配置;config/local/ 与各类密钥由部署方挂载或注入。 COPY --chown=gitdata:gitdata config/default config/default COPY --from=builder /out/app /usr/local/bin/ LABEL org.opencontainers.image.title="KenApi" \ org.opencontainers.image.description="GitDataAI monolith: business API and login web" \ org.opencontainers.image.version="1.0.0" ENV RUST_BACKTRACE=0 USER gitdata EXPOSE 7051 STOPSIGNAL SIGTERM HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD curl -fsS "http://127.0.0.1:${KENAPI_HEALTH_PORT:-7051}/healthz" >/dev/null || exit 1 ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["app", "serve"]