Ctrl K
ComunidadGuías…

Dockerfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# =============================================================================
# ZhikunCode — Multi-stage Production Docker Build
# Architecture: Single container (Java manages Python as subprocess)
# =============================================================================

# ---- Stage 1: Build Frontend ----
FROM node:22-alpine AS frontend-build
WORKDIR /build/frontend

ARG NPM_REGISTRY=https://registry.npmjs.org/
COPY frontend/package.json frontend/package-lock.json ./
RUN case "${NPM_REGISTRY}" in https://*) ;; \
        *) echo "NPM_REGISTRY must use HTTPS" >&2; exit 2 ;; \
    esac && \
    npm ci --ignore-scripts \
    --registry="${NPM_REGISTRY}" \
    --replace-registry-host=always

COPY frontend/src ./src/
COPY frontend/index.html frontend/vite.config.ts frontend/tsconfig.json ./
COPY frontend/tsconfig.node.json frontend/postcss.config.js frontend/tailwind.config.ts ./
COPY frontend/.env.production ./.env.production
RUN npm run build

# ---- Stage 2: Build Backend ----
FROM eclipse-temurin:21-jdk AS backend-build
WORKDIR /build

ARG MAVEN_REPOSITORY_URL=https://repo.maven.apache.org/maven2

# Cache Maven dependencies (layer caching optimization)
COPY backend/pom.xml ./backend/
COPY backend/.mvn ./backend/.mvn/
COPY backend/mvnw ./backend/
RUN repository="${MAVEN_REPOSITORY_URL%/}" && \
    case "$repository" in https://*) ;; \
        *) echo "MAVEN_REPOSITORY_URL must use HTTPS" >&2; exit 2 ;; \
    esac && \
    repository_path="${repository#https://}" && \
    case "$repository_path" in ""|*[!A-Za-z0-9._~:/-]*) \
        echo "MAVEN_REPOSITORY_URL contains unsupported characters" >&2; exit 2 ;; \
    esac && \
    sed -i \
        "s|https://repo.maven.apache.org/maven2|${repository}|g" \
        backend/.mvn/wrapper/maven-wrapper.properties && \
    grep -Fq "distributionUrl=${repository}/" \
        backend/.mvn/wrapper/maven-wrapper.properties && \
    grep -Fq "wrapperUrl=${repository}/" \
        backend/.mvn/wrapper/maven-wrapper.properties && \
    if [ "$repository" != 'https://repo.maven.apache.org/maven2' ]; then \
        mkdir -p /root/.m2; \
        printf '%s\n' \
            '<?xml version="1.0" encoding="UTF-8"?>' \
            '<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">' \
            '  <mirrors>' \
            '    <mirror>' \
            '      <id>docker-build-mirror</id>' \
            '      <name>Configured build mirror</name>' \
            "      <url>${repository}</url>" \
            '      <mirrorOf>*</mirrorOf>' \
            '    </mirror>' \
            '  </mirrors>' \
            '</settings>' \
            > /root/.m2/settings.xml; \
    fi
RUN cd backend && chmod +x mvnw && ./mvnw dependency:go-offline -B

# Build application JAR
COPY backend/src ./backend/src/
RUN cd backend && ./mvnw package -DskipTests -B \
    && mv target/ai-code-assistant-*.jar target/app.jar

# Official GitHub MCP binary, pinned to a release tag.
FROM ghcr.io/github/github-mcp-server:v1.11.0 AS github-mcp

# Pinned Meoo CLI + Fliggy flyai CLI and glibc Node runtime (not the Alpine frontend build).
FROM node:22.14.0-bookworm-slim AS meoo-cli
ARG NPM_REGISTRY=https://registry.npmjs.org/
RUN case "${NPM_REGISTRY}" in https://*) ;; \
        *) echo "NPM_REGISTRY must use HTTPS" >&2; exit 2 ;; \
    esac && \
    npm install --global @aliyun-meoo/cli@0.5.7 --ignore-scripts --no-audit --no-fund \
        --registry="${NPM_REGISTRY}" \
        --replace-registry-host=always && \
    npm install --global @fly-ai/flyai-cli@1.0.16 --ignore-scripts --no-audit --no-fund \
        --registry="${NPM_REGISTRY}" \
        --replace-registry-host=always

# ---- Stage 3: Production Runtime ----
# Ubuntu 24.04 (noble) provides Python 3.12, matching pyproject.toml's
# supported range (>=3.11,<3.13). Jammy's Python 3.10 is not supported.
FROM eclipse-temurin:21-jre-noble AS runtime

ARG UBUNTU_MIRROR_HOST=
ARG PIP_INDEX_URL=https://pypi.org/simple

LABEL maintainer="ZhikunCode Team"
LABEL org.opencontainers.image.title="ZhikunCode"
LABEL org.opencontainers.image.description="AI-powered code assistant with multi-agent collaboration"
LABEL org.opencontainers.image.vendor="ZhikunCode"
LABEL org.opencontainers.image.source="https://github.com/zhikuncode/zhikuncode"

# Install runtime dependencies:
#   - python3 + venv + libmagic: Python subprocess for analysis/file inspection
#   - ripgrep: GrepTool backend
#   - curl: healthcheck
#   - git: Git tools
#   - tree-sitter runtime handled by python venv
RUN if [ -n "${UBUNTU_MIRROR_HOST}" ]; then \
        case "${UBUNTU_MIRROR_HOST}" in *[!A-Za-z0-9.-]*) \
            echo "UBUNTU_MIRROR_HOST must be a hostname" >&2; exit 2 ;; \
        esac; \
        mirror_replaced=false; \
        for source_file in /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources; do \
            if [ -f "$source_file" ] && grep -Eq 'https?://(archive|security|ports)\.ubuntu\.com' "$source_file"; then \
                sed -E -i \
                    "s#https?://(archive\.ubuntu\.com|security\.ubuntu\.com|ports\.ubuntu\.com)#https://${UBUNTU_MIRROR_HOST}#g" \
                    "$source_file"; \
                grep -Fq "https://${UBUNTU_MIRROR_HOST}/ubuntu" "$source_file" \
                    || { echo "Ubuntu mirror replacement verification failed" >&2; exit 2; }; \
                mirror_replaced=true; \
            fi; \
        done; \
        [ "$mirror_replaced" = true ] \
            || { echo "No supported Ubuntu source entry found" >&2; exit 2; }; \
    fi && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        python3 python3-pip python3-venv libmagic1 \
        ripgrep curl git zip && \
    python3 -c 'import sys; assert (3, 11) <= sys.version_info[:2] < (3, 13), sys.version' && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Preinstall the full capability set so sessions can render/convert without
# runtime root. Split into two layers for Docker cache granularity:
#   layer 1: toolchain (gcc/python headers), media, docs, diagrams, OCR, CJK fonts
#   layer 2: LibreOffice office suite (heaviest; isolated so layer 1 stays cached)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        build-essential python3-dev \
        ffmpeg imagemagick poppler-utils qpdf graphviz pandoc \
        tesseract-ocr tesseract-ocr-chi-sim \
        fonts-wqy-zenhei fonts-noto-cjk && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        libreoffice-impress libreoffice-calc libreoffice-writer && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN groupadd -r zhikun && useradd -r -g zhikun -d /app -s /bin/sh zhikun

WORKDIR /app

COPY --from=meoo-cli /usr/local/bin/node /usr/local/bin/node
COPY --from=meoo-cli /usr/local/lib/node_modules/@aliyun-meoo /usr/local/lib/node_modules/@aliyun-meoo
COPY --from=meoo-cli /usr/local/lib/node_modules/@fly-ai /usr/local/lib/node_modules/@fly-ai
COPY --from=meoo-cli /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
RUN ln -s /usr/local/lib/node_modules/@aliyun-meoo/cli/bin/meoo.js /usr/local/bin/meoo \
    && ln -s /usr/local/lib/node_modules/@fly-ai/flyai-cli/dist/flyai-bundle.cjs /usr/local/bin/flyai \
    && ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
    && ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
    && node --version && meoo --version && npm --version && flyai --help > /dev/null

# Keep dependency layers independent of application source changes.
COPY python-service/requirements.lock ./python-service/
COPY python-service/requirements.txt ./python-service/
COPY python-service/pyproject.toml ./python-service/

# Setup Python virtual environment
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
RUN case "${PIP_INDEX_URL}" in https://*) ;; \
        *) echo "PIP_INDEX_URL must use HTTPS" >&2; exit 2 ;; \
    esac && \
    python3 -m venv /app/python-service/.venv && \
    /app/python-service/.venv/bin/pip install --no-cache-dir \
        --index-url "${PIP_INDEX_URL}" pip==24.0 && \
    /app/python-service/.venv/bin/pip install --no-cache-dir \
        --index-url "${PIP_INDEX_URL}" \
        -r /app/python-service/requirements.lock

# The service runs headless. Cache system libraries separately from browser downloads.
RUN /app/python-service/.venv/bin/python -m playwright install-deps chromium && \
    rm -rf /var/lib/apt/lists/*
RUN /app/python-service/.venv/bin/python -m playwright install --only-shell chromium && \
    chmod -R a+rX /opt/playwright-browsers

COPY --from=backend-build /build/backend/target/app.jar ./app.jar
COPY --from=frontend-build /build/frontend/dist ./static/
# PythonProcessManager starts this service as a subprocess.
COPY python-service/src ./python-service/src/

# Alibaba Cloud Ops has stricter FastMCP/Pydantic pins than the application,
# so isolate it from the Python analysis service.
RUN python3 -m venv /app/mcp-servers/alibaba-cloud-ops && \
    /app/mcp-servers/alibaba-cloud-ops/bin/pip install --no-cache-dir \
        --index-url "${PIP_INDEX_URL}" pip==24.0 && \
    /app/mcp-servers/alibaba-cloud-ops/bin/pip install --no-cache-dir \
        --index-url "${PIP_INDEX_URL}" \
        alibaba-cloud-ops-mcp-server==0.9.27

# Create symlink so PythonProcessManager can resolve 'python' command
RUN ln -sf /app/python-service/.venv/bin/python /usr/local/bin/python

# External MCP runtimes and stable project launchers.
COPY --from=github-mcp /server/github-mcp-server /usr/local/bin/github-mcp-server
COPY scripts/mcp/zhikun-github-mcp scripts/mcp/zhikun-alibaba-cloud-ops-mcp /usr/local/bin/
RUN chmod +x /usr/local/bin/github-mcp-server \
    /usr/local/bin/zhikun-github-mcp \
    /usr/local/bin/zhikun-alibaba-cloud-ops-mcp

# Copy MCP capability registry
COPY configuration/ ./configuration/

# Copy entrypoint
COPY docker-entrypoint.sh ./
RUN chmod +x docker-entrypoint.sh

# Create data and log directories; pre-create the HOME-side .zhikun mountpoint
# tree (owned by zhikun) so deploying a bind mount over /app/.zhikun/skills
# never leaves /app/.zhikun root-owned — the app creates its uploads directory
# under /app/.zhikun at startup and must keep write access there.
RUN mkdir -p /app/data /app/workspace /app/log /app/log/debug /app/log/mcp \
    /app/.zhikun/skills /app/.ai-code-assistant && \
    chown -R zhikun:zhikun /app

USER zhikun

# ---- Environment defaults (overridable via docker-compose / .env) ----
ENV JAVA_OPTS="-Xms256m -Xmx1024m --enable-preview" \
    SPRING_PROFILES_ACTIVE="production" \
    PYTHON_SERVICE_PATH="/app/python-service" \
    PYTHONPATH="/app/python-service/src" \
    MCP_REGISTRY_PATH="/app/configuration/mcp/mcp_capability_registry.json" \
    ALLOW_PRIVATE_NETWORK="true" \
    LOG_DIR="/app/log"

EXPOSE 8080

# Health check using curl (installed above)
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -sf http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["./docker-entrypoint.sh"]