# ============================================================ # opsxsql - 单镜像多阶段构建(参照 opsx-loki) # 前端 dist + Go 后端 + gh-ost 合并为一个 alpine 运行时镜像 # 非 root 用户运行;/health 健康检查;数据落 /app/data 卷 # ============================================================ # --------------------------------------------------------------- # Stage 1: 前端构建(Node) # --------------------------------------------------------------- FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci --ignore-scripts && npm cache clean --force COPY frontend/ ./ RUN npm run build # --------------------------------------------------------------- # Stage 2: 后端构建(Go,CGO 因 mattn/go-sqlite3 必开) # --------------------------------------------------------------- FROM golang:1.26-alpine AS backend-builder RUN apk add --no-cache git gcc musl-dev ENV GOTOOLCHAIN=local WORKDIR /app/backend COPY backend/go.* ./ RUN go mod download COPY backend/ ./ RUN CGO_ENABLED=1 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/opsxsql ./cmd/server/ # --------------------------------------------------------------- # Stage 3: gh-ost 在线变更工具(静态二进制,按架构取包) # # 注意:GitHub release 的实际 asset 名不是 gh-ost-linux-,而是: # amd64: gh-ost (裸二进制,18MB) # arm64: gh-ost-binary-linux-arm64-.tar.gz(tar 包) # --------------------------------------------------------------- FROM alpine:3.20 AS ghost ARG TARGETARCH ARG GH_OST_VER=v1.1.9 ARG GH_OST_ARM64_ASSET=gh-ost-binary-linux-arm64-20260501022029.tar.gz RUN mkdir -p /out && \ apk add --no-cache curl tar && \ case "${TARGETARCH}" in \ arm64) \ curl -fsSL -o /tmp/gh.tgz \ "https://github.com/github/gh-ost/releases/download/${GH_OST_VER}/${GH_OST_ARM64_ASSET}" && \ tar -xzf /tmp/gh.tgz -C /tmp && \ mv /tmp/gh-ost-binary-linux-arm64-* /out/gh-ost ;; \ *) \ curl -fsSL -o /out/gh-ost \ "https://github.com/github/gh-ost/releases/download/${GH_OST_VER}/gh-ost" ;; \ esac && \ chmod +x /out/gh-ost && \ ls -lh /out/gh-ost # --------------------------------------------------------------- # Stage 4: 运行时镜像 # --------------------------------------------------------------- FROM alpine:3.20 RUN apk --no-cache add ca-certificates tzdata wget sqlite && \ rm -rf /tmp/* /var/cache/apk/* # 非 root 用户 RUN addgroup -g 1000 opsxsql && \ adduser -u 1000 -G opsxsql -s /bin/sh -D opsxsql WORKDIR /app COPY --from=backend-builder --chown=opsxsql:opsxsql /out/opsxsql ./opsxsql COPY --from=ghost --chown=opsxsql:opsxsql /out/gh-ost /usr/local/bin/gh-ost COPY --from=frontend-builder --chown=opsxsql:opsxsql /app/frontend/dist ./dist RUN mkdir -p /app/data && chown -R opsxsql:opsxsql /app/data && \ chmod +x ./opsxsql ENV DIST_DIR=/app/dist \ DB_PATH=/app/data/sqlite3.db \ PORT=9000 \ DEBUG=false \ GH_OST_PATH=/usr/local/bin/gh-ost \ HOME=/home/opsxsql COPY --chown=opsxsql:opsxsql docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh USER opsxsql EXPOSE 9000 VOLUME ["/app/data"] HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=15s \ CMD wget --no-verbose --tries=1 --spider http://localhost:9000/health || exit 1 ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]