diff --git a/.github/workflows/build-and-publish-image.yml b/.github/workflows/build-and-publish-image.yml new file mode 100644 index 00000000..e1869b83 --- /dev/null +++ b/.github/workflows/build-and-publish-image.yml @@ -0,0 +1,65 @@ +name: Build and Publish Docker Image + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + publish: + description: 'Publish to GHCR (only works on main branch)' + type: boolean + default: false + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-to-ghcr: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix=sha-,format=short + + - name: Log in to the Container registry + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true') }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + CLI_VERSION_ARG=${{ github.sha }} diff --git a/Dockerfile b/Dockerfile index a17dc7f3..378880c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,31 @@ +# Build stage +FROM docker.io/library/node:20-slim AS builder + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + make \ + g++ \ + git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Set up npm global package folder +RUN mkdir -p /usr/local/share/npm-global +ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global +ENV PATH=$PATH:/usr/local/share/npm-global/bin + +# Copy source code +COPY . /home/node/app +WORKDIR /home/node/app + +# Install dependencies and build packages +RUN npm ci \ + && npm run build --workspaces \ + && npm pack -w @qwen-code/qwen-code --pack-destination ./packages/cli/dist \ + && npm pack -w @qwen-code/qwen-code-core --pack-destination ./packages/core/dist + +# Runtime stage FROM docker.io/library/node:20-slim ARG SANDBOX_NAME="qwen-code-sandbox" @@ -5,11 +33,9 @@ ARG CLI_VERSION_ARG ENV SANDBOX="$SANDBOX_NAME" ENV CLI_VERSION=$CLI_VERSION_ARG -# install minimal set of packages, then clean up +# Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ - make \ - g++ \ man-db \ curl \ dnsutils \ @@ -29,22 +55,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# set up npm global package folder under /usr/local/share -# give it to non-root user node, already set up in base image -RUN mkdir -p /usr/local/share/npm-global \ - && chown -R node:node /usr/local/share/npm-global +# Set up npm global package folder +RUN mkdir -p /usr/local/share/npm-global ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global ENV PATH=$PATH:/usr/local/share/npm-global/bin -# switch to non-root user node -USER node +# Copy built packages from builder stage +COPY --from=builder /home/node/app/packages/cli/dist/*.tgz /tmp/ +COPY --from=builder /home/node/app/packages/core/dist/*.tgz /tmp/ -# install qwen-code and clean up -COPY packages/cli/dist/qwen-code-*.tgz /usr/local/share/npm-global/qwen-code.tgz -COPY packages/core/dist/qwen-code-qwen-code-core-*.tgz /usr/local/share/npm-global/qwen-code-core.tgz -RUN npm install -g /usr/local/share/npm-global/qwen-code.tgz /usr/local/share/npm-global/qwen-code-core.tgz \ +# Install built packages globally +RUN npm install -g /tmp/*.tgz \ && npm cache clean --force \ - && rm -f /usr/local/share/npm-global/qwen-{code,code-core}.tgz + && rm -rf /tmp/*.tgz -# default entrypoint when none specified -CMD ["qwen"] \ No newline at end of file +# Default entrypoint when none specified +CMD ["qwen"]